pax_global_header00006660000000000000000000000064152316632660014524gustar00rootroot0000000000000052 comment=d33876eddd3062f3f748e4879b0efe3990afdc73 cowlib-2.19.0/000077500000000000000000000000001523166326600130745ustar00rootroot00000000000000cowlib-2.19.0/.github/000077500000000000000000000000001523166326600144345ustar00rootroot00000000000000cowlib-2.19.0/.github/workflows/000077500000000000000000000000001523166326600164715ustar00rootroot00000000000000cowlib-2.19.0/.github/workflows/ci.yaml000066400000000000000000000011631523166326600177510ustar00rootroot00000000000000## Use workflows from ninenines/ci.erlang.mk to test Cowlib. name: Check Cowlib on: push: branches: - master pull_request: schedule: ## Every Monday at 2am. - cron: 0 2 * * 1 env: CI_ERLANG_MK: 1 jobs: check: name: Check uses: ninenines/ci.erlang.mk/.github/workflows/ci.yaml@master perfs: name: Run performance tests runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Install latest Erlang/OTP uses: erlef/setup-beam@v1 with: otp-version: '> 0' - name: Run perfs run: make perfs cowlib-2.19.0/.gitignore000066400000000000000000000002051523166326600150610ustar00rootroot00000000000000.cowlib.plt .erlang.mk _rel cowlib.d deps doc/man3 doc/man7 doc/markdown ebin/*.beam ebin/test examples/*/ebin logs relx test/*.beam cowlib-2.19.0/AGENTS.md000066400000000000000000000070371523166326600144060ustar00rootroot00000000000000# AGENTS.md Cowlib is a support library for Cowboy and Gun. See README.asciidoc. ## Commands - `make` - build - `make eunit` - unit tests - `make proper` - property tests - `make perfs` - performance tests - `make dialyzer` - static analysis Always use `make` to build, even if wanting to build a single module. ## Layout - `src/` - .erl files - Tests live inside modules under `-ifdef(TEST).` - `include/` - public .hrl files - `doc/src/` - documentation of public interface (in Asciidoc) ## Modules - `cow_base64url` - base64url encoder - `cow_capsule` - Capsule protocol parser - `cow_cookie` - Cookie/SetCookie parser - `cow_date` - HTTP date parser - `cow_deflate` - Safe zlib:inflate function - `cow_hpack` - HPACK encoder - `cow_http1` - HTTP/1 parser - `cow_http2` - HTTP/2 parser - `cow_http2_machine` - HTTP/2 state machine - `cow_http3` - HTTP/3 parser - `cow_http3_machine` - HTTP/3 state machine - `cow_http` - Common HTTP functions and types - `cow_http_hd` - HTTP header parsers - `cow_http_struct_hd` - Structured HTTP header parser - `cow_http_te` - HTTP/1 transfer coding parsers - `cow_iolists` - Utility function to split iolists - `cow_link` - Link header parser - `cow_mimetypes` - Find mime type from file name (automatically generated) - `cow_multipart` - Multipart message parser - `cow_qpack` - QPACK encoder - `cow_qs` - application/x-www-form-urlencoded parser and urlencoder - `cow_sse` - Server-sent events parser - `cow_uri` - URI urlencoder - `cow_uri_template` - URI template parsing and expansion - `cow_ws` - Websocket parser and other functions Modules implementing encoders and parsers typically include decoders and builders. ## When doing any sort of development Don't include unnecessary comments. Comments are only useful when the code is not obvious. Do not remove existing comments. ### When implementing features Always include tests for both normal cases (including available examples if implementing from a design document such as an RFC) and for edge cases. Do not include documentation unless requested. ### When fixing bugs Always write one or more tests before modifying the code. There must be at least one test that fails before and succeeds after. ### When writing tests We want to test both success and failure conditions. Make sure the tests are not only naive tests. It's OK to have naive tests, but there must be other more subtle tests. Make sure to be thorough. We want tests that cover all possible scenarios, not just a handful. There should be tests that exercise limits but are expected to pass. For example if limiting a component to at most 128 characters, there must tests for 128 and 129 characters at a minimum. If a value ranges from 0 to 128, there must be tests around the boundaries (-1, 0, 128, 129 are good candidates) with success expected for values within and failures otherwise. ### When optimising performance Same as for fixing bugs, first write horse tests and then ensure they run faster after doing the changes. As performance tests are fiddle a few test runs may be necessary to be sure. ### When you think you are done Review the changes and ensure all changes are necessary. Discard any *change* that is not then run the tests again. You can review changes with `git diff`. ## When doing code analysis Most of the code in this project is a private library common to Cowboy and Gun. Analysis of Cowlib alone is generally not useful. Cowlib is typically strict at parsing but lax at building protocol output. It is the responsibility of the caller to sanitize the data given to Cowlib to build protocol output. cowlib-2.19.0/LICENSE000066400000000000000000000013651523166326600141060ustar00rootroot00000000000000Copyright (c) 2013-2026, 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. cowlib-2.19.0/Makefile000066400000000000000000000063251523166326600145420ustar00rootroot00000000000000# See LICENSE for licensing information. PROJECT = cowlib PROJECT_DESCRIPTION = Support library for manipulating Web protocols. PROJECT_VERSION = 2.19.0 # Options. #ERLC_OPTS += +bin_opt_info DIALYZER_OPTS = -Werror_handling -Wunmatched_returns # Dependencies. LOCAL_DEPS = crypto DOC_DEPS = asciideck TEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) base32 horse proper jsx \ decimal structured-header-tests uritemplate-tests dep_base32 = git https://github.com/dnsimple/base32_erlang v1.0.0 dep_horse = git https://github.com/ninenines/horse.git master dep_jsx = git https://github.com/talentdeficit/jsx v2.10.0 dep_decimal = git https://github.com/egobrain/decimal 0.6.2 dep_structured-header-tests = git https://github.com/httpwg/structured-header-tests faed1f92942abd4fb5d61b1f9f0dc359f499f1d7 dep_uritemplate-tests = git https://github.com/uri-templates/uritemplate-test master # CI configuration. dep_ci.erlang.mk = git https://github.com/ninenines/ci.erlang.mk master DEP_EARLY_PLUGINS = ci.erlang.mk AUTO_CI_OTP ?= OTP-LATEST-24+ AUTO_CI_WINDOWS ?= OTP-LATEST-24+ # Hex configuration. define HEX_TARBALL_EXTRA_METADATA #{ licenses => [<<"ISC">>], links => #{ <<"Function reference">> => <<"https://ninenines.eu/docs/en/cowlib/2.19/manual/">>, <<"GitHub">> => <<"https://github.com/ninenines/cowlib">>, <<"Sponsor">> => <<"https://github.com/sponsors/essen">> } } endef # Standard targets. include erlang.mk # Always rebuild from scratch in CI because OTP-25.0+ can't use the older build. ci-setup:: distclean-deps -$(verbose) rm -rf $(ERLANG_MK_TMP)/rebar # Compile options. TEST_ERLC_OPTS += +'{parse_transform, eunit_autoexport}' +'{parse_transform, horse_autoexport}' # Mimetypes module generator. GEN_URL = http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types GEN_SRC = src/cow_mimetypes.erl.src GEN_OUT = src/cow_mimetypes.erl .PHONY: gen gen: $(gen_verbose) cat $(GEN_SRC) \ | head -n `grep -n "%% GENERATED" $(GEN_SRC) | cut -d : -f 1` \ > $(GEN_OUT) $(gen_verbose) wget -qO - $(GEN_URL) \ | grep -v ^# \ | awk '{for (i=2; i<=NF; i++) if ($$i != "") { \ split($$1, a, "/"); \ print "all_ext(<<\"" $$i "\">>) -> {<<\"" \ a[1] "\">>, <<\"" a[2] "\">>, []};"}}' \ | sort \ | uniq -w 25 \ >> $(GEN_OUT) $(gen_verbose) cat $(GEN_SRC) \ | tail -n +`grep -n "%% GENERATED" $(GEN_SRC) | cut -d : -f 1` \ >> $(GEN_OUT) # Performance testing. ifeq ($(MAKECMDGOALS),perfs) .NOTPARALLEL: endif .PHONY: perfs perfs: test-build $(gen_verbose) erl -noshell -pa ebin -eval 'horse:app_perf($(PROJECT)), erlang:halt().' # Prepare for the release. prepare_tag: $(verbose) $(warning Hex metadata: $(HEX_TARBALL_EXTRA_METADATA)) $(verbose) echo $(verbose) echo -n "Most recent tag: " $(verbose) git tag --sort taggerdate | tail -n1 $(verbose) git verify-tag `git tag --sort taggerdate | tail -n1` $(verbose) echo -n "MAKEFILE: " $(verbose) grep -m1 PROJECT_VERSION Makefile $(verbose) echo -n "APP: " $(verbose) grep -m1 vsn ebin/$(PROJECT).app | sed 's/ //g' $(verbose) echo $(verbose) echo -n "LICENSE: " ; head -n1 LICENSE $(verbose) echo $(verbose) echo "Dependencies:" $(verbose) grep ^DEPS Makefile || echo "DEPS =" $(verbose) grep ^dep_ Makefile || true cowlib-2.19.0/README.asciidoc000066400000000000000000000007341523166326600155350ustar00rootroot00000000000000= 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!] cowlib-2.19.0/doc/000077500000000000000000000000001523166326600136415ustar00rootroot00000000000000cowlib-2.19.0/doc/src/000077500000000000000000000000001523166326600144305ustar00rootroot00000000000000cowlib-2.19.0/doc/src/manual/000077500000000000000000000000001523166326600157055ustar00rootroot00000000000000cowlib-2.19.0/doc/src/manual/cow_cookie.asciidoc000066400000000000000000000060661523166326600215360ustar00rootroot00000000000000= 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] cowlib-2.19.0/doc/src/manual/cow_cookie.cookie.asciidoc000066400000000000000000000016401523166326600227770ustar00rootroot00000000000000= 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. Note that this function expects the `Name` and `Value` to be safe, meaning they do not contain invalid characters according to RFC6265 section 4.2.1. == 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)] cowlib-2.19.0/doc/src/manual/cow_cookie.parse_cookie.asciidoc000066400000000000000000000023221523166326600241670ustar00rootroot00000000000000= cow_cookie:parse_cookie(3) == Name cow_cookie:parse_cookie - Parse a cookie header == Description [source,erlang] ---- parse_cookie(Cookie :: binary()) -> parse_cookie(Cookie, #{}) parse_cookie(Cookie :: binary(), Opts) -> [{binary(), binary()}] Opts :: #{max_cookies => non_neg_integer()} ---- 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.19*: Function `parse_cookie/2` introduced with a `max_cookies` option. * *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)] cowlib-2.19.0/doc/src/manual/cow_cookie.parse_set_cookie.asciidoc000066400000000000000000000021151523166326600250420ustar00rootroot00000000000000= 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)] cowlib-2.19.0/doc/src/manual/cow_cookie.setcookie.asciidoc000066400000000000000000000022561523166326600235170ustar00rootroot00000000000000= 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. Note that this function expects the `Name`, `Value` arguments as well as the `domain` or `path` options to be safe, meaning they do not contain invalid characters according to RFC6265 section 4.1.1. == 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)] cowlib-2.19.0/doc/src/manual/cowlib_app.asciidoc000066400000000000000000000014501523166326600215240ustar00rootroot00000000000000= 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)] cowlib-2.19.0/ebin/000077500000000000000000000000001523166326600140115ustar00rootroot00000000000000cowlib-2.19.0/ebin/cowlib.app000066400000000000000000000010741523166326600157740ustar00rootroot00000000000000{application, 'cowlib', [ {description, "Support library for manipulating Web protocols."}, {vsn, "2.19.0"}, {modules, ['cow_base64url','cow_capsule','cow_cookie','cow_date','cow_deflate','cow_hpack','cow_http','cow_http1','cow_http2','cow_http2_machine','cow_http3','cow_http3_machine','cow_http_hd','cow_http_struct_hd','cow_http_te','cow_iolists','cow_link','cow_mimetypes','cow_multipart','cow_qpack','cow_qs','cow_sse','cow_uri','cow_uri_template','cow_ws']}, {registered, []}, {applications, [kernel,stdlib,crypto]}, {optional_applications, []}, {env, []} ]}.cowlib-2.19.0/erlang.mk000066400000000000000000004606011523166326600147040ustar00rootroot00000000000000# Copyright (c) 2013-2016, 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. .PHONY: all app apps deps search rel relup docs install-docs check tests clean distclean help erlang-mk ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST))) export ERLANG_MK_FILENAME ERLANG_MK_VERSION = efb0d04 ERLANG_MK_WITHOUT = # Make 3.81 and 3.82 are deprecated. ifeq ($(MAKELEVEL)$(MAKE_VERSION),03.81) $(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html) endif ifeq ($(MAKELEVEL)$(MAKE_VERSION),03.82) $(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html) endif # Core configuration. PROJECT ?= $(notdir $(CURDIR)) PROJECT := $(strip $(PROJECT)) PROJECT_VERSION ?= rolling PROJECT_MOD ?= PROJECT_ENV ?= [] # Verbosity. V ?= 0 verbose_0 = @ verbose_2 = set -x; verbose = $(verbose_$(V)) ifeq ($V,3) SHELL := $(SHELL) -x endif gen_verbose_0 = @echo " GEN " $@; gen_verbose_2 = set -x; gen_verbose = $(gen_verbose_$(V)) gen_verbose_esc_0 = @echo " GEN " $$@; gen_verbose_esc_2 = set -x; gen_verbose_esc = $(gen_verbose_esc_$(V)) # Temporary files directory. ERLANG_MK_TMP ?= $(CURDIR)/.erlang.mk export ERLANG_MK_TMP # "erl" command. ERL = erl -noinput -boot no_dot_erlang -kernel start_distribution false +P 1024 +Q 1024 # Platform detection. ifeq ($(PLATFORM),) UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) PLATFORM = linux else ifeq ($(UNAME_S),Darwin) PLATFORM = darwin else ifeq ($(UNAME_S),SunOS) PLATFORM = solaris else ifeq ($(UNAME_S),GNU) PLATFORM = gnu else ifeq ($(UNAME_S),FreeBSD) PLATFORM = freebsd else ifeq ($(UNAME_S),NetBSD) PLATFORM = netbsd else ifeq ($(UNAME_S),OpenBSD) PLATFORM = openbsd else ifeq ($(UNAME_S),DragonFly) PLATFORM = dragonfly else ifeq ($(shell uname -o),Msys) PLATFORM = msys2 else $(error Unable to detect platform. Please open a ticket with the output of uname -a.) endif export PLATFORM endif # Core targets. all:: deps app rel # Noop to avoid a Make warning when there's nothing to do. rel:: $(verbose) : relup:: deps app check:: tests clean:: clean-crashdump clean-crashdump: ifneq ($(wildcard erl_crash.dump),) $(gen_verbose) rm -f erl_crash.dump endif distclean:: clean distclean-tmp $(ERLANG_MK_TMP): $(verbose) mkdir -p $(ERLANG_MK_TMP) distclean-tmp: $(gen_verbose) rm -rf $(ERLANG_MK_TMP) help:: $(verbose) printf "%s\n" \ "erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License." \ "Copyright (c) 2013-2016 Loïc Hoguin " \ "" \ "Usage: [V=1] $(MAKE) [target]..." \ "" \ "Core targets:" \ " all Run deps, app and rel targets in that order" \ " app Compile the project" \ " deps Fetch dependencies (if needed) and compile them" \ " fetch-deps Fetch dependencies recursively (if needed) without compiling them" \ " list-deps List dependencies recursively on stdout" \ " search q=... Search for a package in the built-in index" \ " rel Build a release for this project, if applicable" \ " docs Build the documentation for this project" \ " install-docs Install the man pages for this project" \ " check Compile and run all tests and analysis for this project" \ " tests Run the tests for this project" \ " clean Delete temporary and output files from most targets" \ " distclean Delete all temporary and output files" \ " help Display this help and exit" \ " erlang-mk Update erlang.mk to the latest version" # Core functions. empty := space := $(empty) $(empty) tab := $(empty) $(empty) comma := , define newline endef define comma_list $(subst $(space),$(comma),$(strip $1)) endef define escape_dquotes $(subst ",\",$1) endef # Adding erlang.mk to make Erlang scripts who call init:get_plain_arguments() happy. define erlang $(ERL) $2 -pz $(ERLANG_MK_TMP)/rebar3/_build/prod/lib/*/ebin/ -eval "$(subst $(newline),,$(call escape_dquotes,$1))" -- erlang.mk endef ifeq ($(PLATFORM),msys2) core_native_path = $(shell cygpath -m $1) else core_native_path = $1 endif core_http_get = curl -Lf$(if $(filter-out 0,$V),,s)o $(call core_native_path,$1) $2 core_eq = $(and $(findstring $1,$2),$(findstring $2,$1)) # We skip files that contain spaces because they end up causing issues. # Files that begin with a dot are already ignored by the wildcard function. core_find = $(foreach f,$(wildcard $(1:%/=%)/*),$(if $(wildcard $f/.),$(call core_find,$f,$2),$(if $(filter $(subst *,%,$2),$f),$(if $(wildcard $f),$f)))) core_lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1)))))))))))))))))))))))))) core_ls = $(filter-out $1,$(shell echo $1)) # @todo Use a solution that does not require using perl. core_relpath = $(shell perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $1 $2) define core_render printf -- '$(subst $(newline),\n,$(subst %,%%,$(subst ','\'',$(subst $(tab),$(WS),$(call $1)))))\n' > $2 endef # Automated update. ERLANG_MK_REPO ?= https://github.com/ninenines/erlang.mk ERLANG_MK_COMMIT ?= ERLANG_MK_BUILD_CONFIG ?= build.config ERLANG_MK_BUILD_DIR ?= .erlang.mk.build erlang-mk: WITHOUT ?= $(ERLANG_MK_WITHOUT) erlang-mk: ifdef ERLANG_MK_COMMIT $(verbose) git clone $(ERLANG_MK_REPO) $(ERLANG_MK_BUILD_DIR) $(verbose) cd $(ERLANG_MK_BUILD_DIR) && git checkout $(ERLANG_MK_COMMIT) else $(verbose) git clone --depth 1 $(ERLANG_MK_REPO) $(ERLANG_MK_BUILD_DIR) endif $(verbose) if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR)/build.config; fi $(gen_verbose) $(MAKE) --no-print-directory -C $(ERLANG_MK_BUILD_DIR) WITHOUT='$(strip $(WITHOUT))' UPGRADE=1 $(verbose) cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk $(verbose) rm -rf $(ERLANG_MK_BUILD_DIR) $(verbose) rm -rf $(ERLANG_MK_TMP) # The erlang.mk package index is bundled in the default erlang.mk build. # Search for the string "copyright" to skip to the rest of the code. # Copyright (c) 2015-2017, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-kerl KERL_INSTALL_DIR ?= $(HOME)/erlang ifeq ($(strip $(KERL)),) KERL := $(ERLANG_MK_TMP)/kerl/kerl endif KERL_DIR = $(ERLANG_MK_TMP)/kerl export KERL KERL_GIT ?= https://github.com/kerl/kerl KERL_COMMIT ?= master KERL_MAKEFLAGS ?= OTP_GIT ?= https://github.com/erlang/otp define kerl_otp_target $(KERL_INSTALL_DIR)/$1: $(KERL) $(verbose) if [ ! -d $$@ ]; then \ MAKEFLAGS="$(KERL_MAKEFLAGS)" $(KERL) build git $(OTP_GIT) $1 $1; \ $(KERL) install $1 $(KERL_INSTALL_DIR)/$1; \ fi endef $(KERL): $(KERL_DIR) $(KERL_DIR): | $(ERLANG_MK_TMP) $(gen_verbose) git clone --depth 1 $(KERL_GIT) $(ERLANG_MK_TMP)/kerl $(verbose) cd $(ERLANG_MK_TMP)/kerl && git checkout $(KERL_COMMIT) $(verbose) chmod +x $(KERL) distclean:: distclean-kerl distclean-kerl: $(gen_verbose) rm -rf $(KERL_DIR) # Allow users to select which version of Erlang/OTP to use for a project. ifneq ($(strip $(LATEST_ERLANG_OTP)),) # In some environments it is necessary to filter out master. ERLANG_OTP := $(notdir $(lastword $(sort\ $(filter-out $(KERL_INSTALL_DIR)/master $(KERL_INSTALL_DIR)/OTP_R%,\ $(filter-out %-rc1 %-rc2 %-rc3,$(wildcard $(KERL_INSTALL_DIR)/*[^-native])))))) endif ERLANG_OTP ?= # Use kerl to enforce a specific Erlang/OTP version for a project. ifneq ($(strip $(ERLANG_OTP)),) export PATH := $(KERL_INSTALL_DIR)/$(ERLANG_OTP)/bin:$(PATH) SHELL := env PATH=$(PATH) $(SHELL) $(eval $(call kerl_otp_target,$(ERLANG_OTP))) # Build Erlang/OTP only if it doesn't already exist. ifeq ($(wildcard $(KERL_INSTALL_DIR)/$(ERLANG_OTP))$(BUILD_ERLANG_OTP),) $(info Building Erlang/OTP $(ERLANG_OTP)... Please wait...) $(shell $(MAKE) $(KERL_INSTALL_DIR)/$(ERLANG_OTP) ERLANG_OTP=$(ERLANG_OTP) BUILD_ERLANG_OTP=1 >&2) endif endif PACKAGES += asciideck pkg_asciideck_name = asciideck pkg_asciideck_description = Asciidoc for Erlang. pkg_asciideck_homepage = https://ninenines.eu pkg_asciideck_fetch = git pkg_asciideck_repo = https://github.com/ninenines/asciideck pkg_asciideck_commit = master PACKAGES += cowboy pkg_cowboy_name = cowboy pkg_cowboy_description = Small, fast and modular HTTP server. pkg_cowboy_homepage = http://ninenines.eu pkg_cowboy_fetch = git pkg_cowboy_repo = https://github.com/ninenines/cowboy pkg_cowboy_commit = master PACKAGES += cowlib pkg_cowlib_name = cowlib pkg_cowlib_description = Support library for manipulating Web protocols. pkg_cowlib_homepage = http://ninenines.eu pkg_cowlib_fetch = git pkg_cowlib_repo = https://github.com/ninenines/cowlib pkg_cowlib_commit = master PACKAGES += elixir pkg_elixir_name = elixir pkg_elixir_description = Elixir is a dynamic, functional language for building scalable and maintainable applications. pkg_elixir_homepage = https://elixir-lang.org pkg_elixir_fetch = git pkg_elixir_repo = https://github.com/elixir-lang/elixir pkg_elixir_commit = main PACKAGES += erlydtl pkg_erlydtl_name = erlydtl pkg_erlydtl_description = Django Template Language for Erlang. pkg_erlydtl_homepage = https://github.com/erlydtl/erlydtl pkg_erlydtl_fetch = git pkg_erlydtl_repo = https://github.com/erlydtl/erlydtl pkg_erlydtl_commit = master PACKAGES += gpb pkg_gpb_name = gpb pkg_gpb_description = A Google Protobuf implementation for Erlang pkg_gpb_homepage = https://github.com/tomas-abrahamsson/gpb pkg_gpb_fetch = git pkg_gpb_repo = https://github.com/tomas-abrahamsson/gpb pkg_gpb_commit = master PACKAGES += gun pkg_gun_name = gun pkg_gun_description = HTTP/1.1, HTTP/2, Websocket client (and more) for Erlang/OTP. pkg_gun_homepage = http//ninenines.eu pkg_gun_fetch = git pkg_gun_repo = https://github.com/ninenines/gun pkg_gun_commit = master PACKAGES += hex_core pkg_hex_core_name = hex_core pkg_hex_core_description = Reference implementation of Hex specifications pkg_hex_core_homepage = https://github.com/hexpm/hex_core pkg_hex_core_fetch = git HEX_CORE_GIT ?= https://github.com/hexpm/hex_core pkg_hex_core_repo = $(HEX_CORE_GIT) pkg_hex_core_commit = e57b4fb15cde710b3ae09b1d18f148f6999a63cc PACKAGES += proper pkg_proper_name = proper pkg_proper_description = PropEr: a QuickCheck-inspired property-based testing tool for Erlang. pkg_proper_homepage = http://proper.softlab.ntua.gr pkg_proper_fetch = git pkg_proper_repo = https://github.com/manopapad/proper pkg_proper_commit = master PACKAGES += ranch pkg_ranch_name = ranch pkg_ranch_description = Socket acceptor pool for TCP protocols. pkg_ranch_homepage = http://ninenines.eu pkg_ranch_fetch = git pkg_ranch_repo = https://github.com/ninenines/ranch pkg_ranch_commit = master PACKAGES += relx pkg_relx_name = relx pkg_relx_description = Sane, simple release creation for Erlang pkg_relx_homepage = https://github.com/erlware/relx pkg_relx_fetch = git pkg_relx_repo = https://github.com/erlware/relx pkg_relx_commit = main PACKAGES += triq pkg_triq_name = triq pkg_triq_description = Trifork QuickCheck pkg_triq_homepage = https://triq.gitlab.io pkg_triq_fetch = git pkg_triq_repo = https://gitlab.com/triq/triq.git pkg_triq_commit = master # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: search define pkg_print $(verbose) printf "%s\n" \ $(if $(call core_eq,$1,$(pkg_$(1)_name)),,"Pkg name: $1") \ "App name: $(pkg_$(1)_name)" \ "Description: $(pkg_$(1)_description)" \ "Home page: $(pkg_$(1)_homepage)" \ "Fetch with: $(pkg_$(1)_fetch)" \ "Repository: $(pkg_$(1)_repo)" \ "Commit: $(pkg_$(1)_commit)" \ "" endef search: ifdef q $(foreach p,$(PACKAGES), \ $(if $(findstring $(call core_lc,$q),$(call core_lc,$(pkg_$(p)_name) $(pkg_$(p)_description))), \ $(call pkg_print,$p))) else $(foreach p,$(PACKAGES),$(call pkg_print,$p)) endif # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-deps clean-tmp-deps.log # Configuration. ifdef OTP_DEPS $(warning The variable OTP_DEPS is deprecated in favor of LOCAL_DEPS.) endif IGNORE_DEPS ?= export IGNORE_DEPS APPS_DIR ?= $(CURDIR)/apps export APPS_DIR DEPS_DIR ?= $(CURDIR)/deps export DEPS_DIR REBAR_DEPS_DIR = $(DEPS_DIR) export REBAR_DEPS_DIR # When testing Erlang.mk and updating these, make sure # to delete test/test_rebar_git before running tests again. REBAR3_GIT ?= https://github.com/erlang/rebar3 REBAR3_COMMIT ?= 619df55d9dab109cbd1b5d9ed1d45c93f5450e24 # 3.27.0 CACHE_DEPS ?= 0 CACHE_DIR ?= $(if $(XDG_CACHE_HOME),$(XDG_CACHE_HOME),$(HOME)/.cache)/erlang.mk export CACHE_DIR HEX_CONFIG ?= define hex_config.erl begin Config0 = hex_core:default_config(), Config0$(HEX_CONFIG) end endef # External "early" plugins (see core/plugins.mk for regular plugins). # They both use the core_dep_plugin macro. define core_dep_plugin ifeq ($2,$(PROJECT)) -include $$(patsubst $(PROJECT)/%,%,$1) else -include $(DEPS_DIR)/$1 $(DEPS_DIR)/$1: $(DEPS_DIR)/$2 ; endif endef DEP_EARLY_PLUGINS ?= $(foreach p,$(DEP_EARLY_PLUGINS),\ $(eval $(if $(findstring /,$p),\ $(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\ $(call core_dep_plugin,$p/early-plugins.mk,$p)))) # Query functions. query_fetch_method = $(if $(dep_$(1)),$(call _qfm_dep,$(word 1,$(dep_$(1)))),$(call _qfm_pkg,$1)) _qfm_dep = $(if $(dep_fetch_$(1)),$1,fail) _qfm_pkg = $(if $(pkg_$(1)_fetch),$(pkg_$(1)_fetch),fail) query_name = $(if $(dep_$(1)),$1,$(if $(pkg_$(1)_name),$(pkg_$(1)_name),$1)) query_repo = $(call _qr,$1,$(call query_fetch_method,$1)) _qr = $(if $(query_repo_$(2)),$(call query_repo_$(2),$1),$(call query_repo_git,$1)) query_repo_default = $(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_repo)) query_repo_git = $(patsubst git://github.com/%,https://github.com/%,$(call query_repo_default,$1)) query_repo_git-subfolder = $(call query_repo_git,$1) query_repo_git-submodule = - query_repo_hg = $(call query_repo_default,$1) query_repo_svn = $(call query_repo_default,$1) query_repo_cp = $(call query_repo_default,$1) query_repo_ln = $(call query_repo_default,$1) query_repo_hex = https://hex.pm/packages/$(if $(word 3,$(dep_$(1))),$(word 3,$(dep_$(1))),$1) query_repo_fail = - query_version = $(call _qv,$1,$(call query_fetch_method,$1)) _qv = $(if $(query_version_$(2)),$(call query_version_$(2),$1),$(call query_version_default,$1)) query_version_default = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 3,$(dep_$(1))),$(pkg_$(1)_commit))) query_version_git = $(call query_version_default,$1) query_version_git-subfolder = $(call query_version_default,$1) query_version_git-submodule = - query_version_hg = $(call query_version_default,$1) query_version_svn = - query_version_cp = - query_version_ln = - query_version_hex = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_commit))) query_version_fail = - query_extra = $(call _qe,$1,$(call query_fetch_method,$1)) _qe = $(if $(query_extra_$(2)),$(call query_extra_$(2),$1),-) query_extra_git = - query_extra_git-subfolder = $(if $(dep_$(1)),subfolder=$(word 4,$(dep_$(1))),-) query_extra_git-submodule = - query_extra_hg = - query_extra_svn = - query_extra_cp = - query_extra_ln = - query_extra_hex = $(if $(dep_$(1)),package-name=$(word 3,$(dep_$(1))),-) query_extra_fail = - query_absolute_path = $(addprefix $(DEPS_DIR)/,$(call query_name,$1)) # Deprecated legacy query function. Used by RabbitMQ and its third party plugins. # Can be removed once RabbitMQ has been updated and enough time has passed. dep_name = $(call query_name,$(1)) # Application directories. LOCAL_DEPS_DIRS = $(foreach a,$(LOCAL_DEPS),$(if $(wildcard $(APPS_DIR)/$a),$(APPS_DIR)/$a)) # Elixir is handled specially as it must be built before all other deps # when Mix autopatching is necessary. ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(BUILD_DEPS) $(DEPS)),$(call query_name,$(dep)))) # When we are calling an app directly we don't want to include it here # otherwise it'll be treated both as an apps and a top-level project. ALL_APPS_DIRS = $(if $(wildcard $(APPS_DIR)/),$(filter-out $(APPS_DIR),$(shell find $(APPS_DIR) -maxdepth 1 -type d))) ifdef ROOT_DIR ifndef IS_APP ALL_APPS_DIRS := $(filter-out $(APPS_DIR)/$(notdir $(CURDIR)),$(ALL_APPS_DIRS)) endif endif ifeq ($(filter $(APPS_DIR) $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),) ifeq ($(ERL_LIBS),) ERL_LIBS = $(APPS_DIR):$(DEPS_DIR) else ERL_LIBS := $(ERL_LIBS):$(APPS_DIR):$(DEPS_DIR) endif endif export ERL_LIBS export NO_AUTOPATCH # Elixir. # Elixir is automatically enabled in all cases except when # an Erlang project uses an Elixir dependency. In that case # $(ELIXIR) must be set explicitly. ELIXIR ?= $(if $(filter elixir,$(BUILD_DEPS) $(DEPS)),dep,$(if $(EX_FILES),system,disable)) export ELIXIR # Verbosity. dep_verbose_0 = @echo " DEP $1 ($(call query_version,$1))"; dep_verbose_2 = set -x; dep_verbose = $(dep_verbose_$(V)) # Core targets. ALL_APPS_DIRS_TO_BUILD = $(if $(LOCAL_DEPS_DIRS)$(IS_APP),$(LOCAL_DEPS_DIRS),$(ALL_APPS_DIRS)) apps:: $(ALL_APPS_DIRS) clean-tmp-deps.log | $(ERLANG_MK_TMP) # Create ebin directory for all apps to make sure Erlang recognizes them # as proper OTP applications when using -include_lib. This is a temporary # fix, a proper fix would be to compile apps/* in the right order. ifndef IS_APP ifneq ($(ALL_APPS_DIRS),) $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \ mkdir -p $$dep/ebin; \ done endif endif # At the toplevel: if LOCAL_DEPS is defined with at least one local app, only # compile that list of apps. Otherwise, compile everything. # Within an app: compile all LOCAL_DEPS that are (uncompiled) local apps. ifneq ($(ALL_APPS_DIRS_TO_BUILD),) $(verbose) set -e; for dep in $(ALL_APPS_DIRS_TO_BUILD); do \ if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/apps.log; then \ :; \ else \ echo $$dep >> $(ERLANG_MK_TMP)/apps.log; \ $(MAKE) -C $$dep $(if $(IS_TEST),test-build-app) IS_APP=1; \ fi \ done endif clean-tmp-deps.log: ifeq ($(IS_APP)$(IS_DEP),) $(verbose) rm -f $(ERLANG_MK_TMP)/apps.log $(ERLANG_MK_TMP)/deps.log endif # Erlang.mk does not rebuild dependencies after they were compiled # once. If a developer is working on the top-level project and some # dependencies at the same time, he may want to change this behavior. # There are two solutions: # 1. Set `FULL=1` so that all dependencies are visited and # recursively recompiled if necessary. # 2. Set `FORCE_REBUILD=` to the specific list of dependencies that # should be recompiled (instead of the whole set). FORCE_REBUILD ?= ifeq ($(origin FULL),undefined) ifneq ($(strip $(force_rebuild_dep)$(FORCE_REBUILD)),) define force_rebuild_dep echo "$(FORCE_REBUILD)" | grep -qw "$$(basename "$1")" endef endif endif $(ERLANG_MK_TMP)/dep_built: $(verbose) mkdir -p $(ERLANG_MK_TMP)/dep_built ifneq ($(SKIP_DEPS),) deps:: else ALL_DEPS_DIRS_TO_BUILD = $(if $(filter-out $(DEPS_DIR)/elixir,$(ALL_DEPS_DIRS)),$(filter-out $(DEPS_DIR)/elixir,$(ALL_DEPS_DIRS)),$(ALL_DEPS_DIRS)) deps:: $(ALL_DEPS_DIRS_TO_BUILD) apps clean-tmp-deps.log | $(ERLANG_MK_TMP)/dep_built ifneq ($(ALL_DEPS_DIRS_TO_BUILD),) $(verbose) set -e; for dep in $(ALL_DEPS_DIRS_TO_BUILD); do \ if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/deps.log; then \ :; \ else \ echo $$dep >> $(ERLANG_MK_TMP)/deps.log; \ if [ -z "$(strip $(FULL))" ] $(if $(force_rebuild_dep),&& ! ($(call force_rebuild_dep,$$dep)),) && [ ! -L $$dep ] && [ -f $(ERLANG_MK_TMP)/dep_built/`basename $$dep` ]; then \ :; \ elif [ "$$dep" = "$(DEPS_DIR)/hut" -a "$(HUT_PATCH)" ]; then \ $(MAKE) -C $$dep app IS_DEP=1; \ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $(ERLANG_MK_TMP)/dep_built/`basename $$dep`; fi; \ elif [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ]; then \ $(MAKE) -C $$dep IS_DEP=1; \ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $(ERLANG_MK_TMP)/dep_built/`basename $$dep`; fi; \ else \ echo "Error: No Makefile to build dependency $$dep." >&2; \ exit 2; \ fi \ fi \ done endif endif # Deps related targets. autopatch_verbose_0 = @echo " PATCH " $(subst autopatch-,,$@) "(method: $(AUTOPATCH_METHOD))"; autopatch_verbose_2 = set -x; autopatch_verbose = $(autopatch_verbose_$(V)) define dep_autopatch_detect if [ -f $(DEPS_DIR)/$1/erlang.mk ]; then \ echo erlang.mk; \ elif [ -f $(DEPS_DIR)/$1/mix.exs -a -d $(DEPS_DIR)/$1/lib ]; then \ if [ "$(ELIXIR)" != "disable" ]; then \ echo mix; \ elif [ -f $(DEPS_DIR)/$1/rebar.lock -o -f $(DEPS_DIR)/$1/rebar.config ]; then \ echo rebar3; \ elif [ -f $(DEPS_DIR)/$1/Makefile ]; then \ echo noop; \ else \ exit 99; \ fi \ elif [ -f $(DEPS_DIR)/$1/Makefile ]; then \ if [ -f $(DEPS_DIR)/$1/rebar.lock ]; then \ echo rebar3; \ elif [ 0 != \`grep -c "include ../\w*\.mk" $(DEPS_DIR)/$1/Makefile\` ]; then \ echo rebar3; \ elif [ 0 != \`grep -ci "^[^#].*rebar" $(DEPS_DIR)/$1/Makefile\` ]; then \ echo rebar3; \ elif [ -n "\`find $(DEPS_DIR)/$1/ -type f -name \*.mk -not -name erlang.mk -exec grep -i "^[^#].*rebar" '{}' \;\`" ]; then \ echo rebar3; \ else \ echo noop; \ fi \ elif [ ! -d $(DEPS_DIR)/$1/src/ ]; then \ echo noop; \ else \ echo rebar3; \ fi endef define dep_autopatch_for_erlang.mk rm -rf $(DEPS_DIR)/$1/ebin/; \ $(call erlang,$(call dep_autopatch_appsrc.erl,$1)); \ $(call dep_autopatch_erlang_mk,$1) endef define dep_autopatch_for_rebar3 ! test -f $(DEPS_DIR)/$1/ebin/$1.app || \ mv -n $(DEPS_DIR)/$1/ebin/$1.app $(DEPS_DIR)/$1/src/$1.app.src; \ rm -f $(DEPS_DIR)/$1/ebin/$1.app; \ if [ -f $(DEPS_DIR)/$1/src/$1.app.src.script ]; then \ $(call erlang,$(call dep_autopatch_appsrc_script.erl,$1)); \ fi; \ $(call erlang,$(call dep_autopatch_appsrc.erl,$1)); \ if [ -f $(DEPS_DIR)/$1/rebar -o -f $(DEPS_DIR)/$1/rebar.config -o -f $(DEPS_DIR)/$1/rebar.config.script -o -f $(DEPS_DIR)/$1/rebar.lock ]; then \ $(call dep_autopatch_fetch_rebar); \ $(call dep_autopatch_rebar,$1); \ else \ $(call dep_autopatch_gen,$1); \ fi endef define dep_autopatch_for_mix $(call dep_autopatch_mix,$1) endef define dep_autopatch_for_noop test -f $(DEPS_DIR)/$1/Makefile || printf "noop:\n" > $(DEPS_DIR)/$1/Makefile endef define maybe_flock if command -v flock >/dev/null; then \ flock $1 sh -c "$2"; \ elif command -v lockf >/dev/null; then \ lockf -k $1 sh -c "$2"; \ else \ $2; \ fi endef # Replace "include erlang.mk" with a line that will load the parent Erlang.mk # if given. Do it for all 3 possible Makefile file names. ifeq ($(NO_AUTOPATCH_ERLANG_MK),) define dep_autopatch_erlang_mk for f in Makefile makefile GNUmakefile; do \ if [ -f $(DEPS_DIR)/$1/$$f ]; then \ sed -i.bak s/'include *erlang.mk'/'include $$(if $$(ERLANG_MK_FILENAME),$$(ERLANG_MK_FILENAME),erlang.mk)'/ $(DEPS_DIR)/$1/$$f; \ fi \ done endef else define dep_autopatch_erlang_mk : endef endif define dep_autopatch_gen printf "%s\n" \ "ERLC_OPTS = +debug_info" \ "include ../../erlang.mk" > $(DEPS_DIR)/$1/Makefile endef # We use flock/lockf when available to avoid concurrency issues. define dep_autopatch_fetch_rebar $(call maybe_flock,$(ERLANG_MK_TMP)/rebar.lock,$(call dep_autopatch_fetch_rebar2)) endef define dep_autopatch_fetch_rebar2 if [ ! -d $(ERLANG_MK_TMP)/rebar3 ]; then \ git clone -q -n -- $(REBAR3_GIT) $(ERLANG_MK_TMP)/rebar3; \ cd $(ERLANG_MK_TMP)/rebar3; \ git checkout -q $(REBAR3_COMMIT); \ ./bootstrap; \ cd -; \ fi endef define dep_autopatch_rebar if [ -f $(DEPS_DIR)/$1/Makefile ]; then \ mv $(DEPS_DIR)/$1/Makefile $(DEPS_DIR)/$1/Makefile.orig.mk; \ fi; \ $(call erlang,$(call dep_autopatch_rebar.erl,$1)); \ rm -f $(DEPS_DIR)/$1/ebin/$1.app endef define dep_autopatch_rebar.erl application:load(rebar), application:set_env(rebar, log_level, debug), {module, rebar3} = c:l(rebar3), Conf1 = case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config)") of {ok, Conf0} -> Conf0; _ -> [] end, {Conf, OsEnv} = fun() -> case filelib:is_file("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)") of false -> {Conf1, []}; true -> Bindings0 = erl_eval:new_bindings(), Bindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0), Bindings = erl_eval:add_binding('SCRIPT', "$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)", Bindings1), Before = os:getenv(), {ok, Conf2} = file:script("$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)", Bindings), {Conf2, lists:foldl(fun(E, Acc) -> lists:delete(E, Acc) end, os:getenv(), Before)} end end(), Write = fun (Text) -> file:write_file("$(call core_native_path,$(DEPS_DIR)/$1/Makefile)", Text, [append]) end, Escape = fun (Text) -> re:replace(Text, "\\\\$$", "\$$$$", [global, {return, list}]) end, Write("IGNORE_DEPS += edown eper eunit_formatters meck node_package " "rebar_lock_deps_plugin rebar_vsn_plugin reltool_util\n"), Write("C_SRC_DIR = /path/do/not/exist\n"), Write("C_SRC_TYPE = rebar\n"), Write("DRV_CFLAGS = -fPIC\nexport DRV_CFLAGS\n"), Write(["ERLANG_ARCH = ", rebar_utils:wordsize(), "\nexport ERLANG_ARCH\n"]), ToList = fun (V) when is_atom(V) -> atom_to_list(V); (V) when is_list(V) -> "'\\"" ++ V ++ "\\"'" end, fun() -> Write("ERLC_OPTS = +debug_info\n"), case lists:keyfind(erl_opts, 1, Conf) of false -> ok; {_, ErlOpts} -> lists:foreach(fun ({d, D}) -> Write("ERLC_OPTS += -D" ++ ToList(D) ++ "=1\n"); ({d, DKey, DVal}) -> Write("ERLC_OPTS += -D" ++ ToList(DKey) ++ "=" ++ ToList(DVal) ++ "\n"); ({i, I}) -> Write(["ERLC_OPTS += -I ", I, "\n"]); ({platform_define, Regex, D}) -> case rebar_utils:is_arch(Regex) of true -> Write("ERLC_OPTS += -D" ++ ToList(D) ++ "=1\n"); false -> ok end; ({parse_transform, PT}) -> Write("ERLC_OPTS += +'{parse_transform, " ++ ToList(PT) ++ "}'\n"); (_) -> ok end, ErlOpts) end, Write("\n") end(), GetHexVsn2 = fun(N, NP) -> case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.lock)") of {ok, Lock} -> LockPkgs = case lists:keyfind("1.2.0", 1, Lock) of {_, LP} -> LP; _ -> case lists:keyfind("1.1.0", 1, Lock) of {_, LP} -> LP; _ -> false end end, if is_list(LockPkgs) -> case lists:keyfind(atom_to_binary(N, latin1), 1, LockPkgs) of {_, {pkg, _, Vsn}, _} -> {N, {hex, NP, binary_to_list(Vsn)}}; _ -> false end; true -> false end; _ -> false end end, GetHexVsn3Common = fun(N, NP, S0) -> case GetHexVsn2(N, NP) of false -> S2 = case S0 of " " ++ S1 -> S1; _ -> S0 end, S = case length([ok || $$. <- S2]) of 0 -> S2 ++ ".0.0"; 1 -> S2 ++ ".0"; _ -> S2 end, {N, {hex, NP, S}}; NameSource -> NameSource end end, GetHexVsn3 = fun (N, NP, "~>" ++ S0) -> GetHexVsn3Common(N, NP, S0); (N, NP, ">=" ++ S0) -> GetHexVsn3Common(N, NP, S0); (N, NP, S) -> {N, {hex, NP, S}} end, ConvertCommit = fun ({branch, C}) -> C; ({ref, C}) -> C; ({tag, C}) -> C; (C) -> C end, fun() -> File = case lists:keyfind(deps, 1, Conf) of false -> []; {_, Deps} -> [begin case case Dep of N when is_atom(N) -> GetHexVsn2(N, N); {N, S} when is_atom(N), is_list(S) -> GetHexVsn3(N, N, S); {N, {pkg, NP}} when is_atom(N) -> GetHexVsn2(N, NP); {N, S, {pkg, NP}} -> GetHexVsn3(N, NP, S); {N, S} when is_tuple(S) -> {N, S}; {N, _, S} -> {N, S}; {N, _, S, _} -> {N, S}; _ -> false end of false -> ok; {Name, {git_subdir, Repo, Commit, SubDir}} -> Write(io_lib:format("DEPS += ~s\ndep_~s = git-subfolder ~s ~s ~s~n", [Name, Name, Repo, ConvertCommit(Commit), SubDir])); {Name, Source} -> {Method, Repo, Commit} = case Source of {hex, NPV, V} -> {hex, V, NPV}; {git, R} -> {git, R, master}; {M, R, C} -> {M, R, C} end, Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, ConvertCommit(Commit)])) end end || Dep <- Deps] end end(), fun() -> case lists:keyfind(erl_first_files, 1, Conf) of false -> ok; {_, Files0} -> Files = [begin hd(filelib:wildcard("$(call core_native_path,$(DEPS_DIR)/$1/src/)**/" ++ filename:rootname(F) ++ ".*rl")) end || "src/" ++ F <- Files0], Names = [[" ", case lists:reverse(F) of "lre." ++ Elif -> lists:reverse(Elif); "lrx." ++ Elif -> lists:reverse(Elif); "lry." ++ Elif -> lists:reverse(Elif); Elif -> lists:reverse(Elif) end] || "$(call core_native_path,$(DEPS_DIR)/$1/src/)" ++ F <- Files], Write(io_lib:format("COMPILE_FIRST +=~s\n", [Names])) end end(), Write("\n\nrebar_dep: preprocess pre-deps deps pre-app app post-app\n"), Write("\npreprocess::\n"), Write("\npre-deps::\n"), Write("\npre-app::\n"), Write("\npost-app::\n"), PatchHook = fun(Cmd) -> Cmd2 = re:replace(Cmd, "^([g]?make)(.*)( -C.*)", "\\\\1\\\\3\\\\2", [{return, list}]), case Cmd2 of "make -C" ++ Cmd1 -> "$$\(MAKE) -C" ++ Escape(Cmd1); "gmake -C" ++ Cmd1 -> "$$\(MAKE) -C" ++ Escape(Cmd1); "make " ++ Cmd1 -> "$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1); "gmake " ++ Cmd1 -> "$$\(MAKE) -f Makefile.orig.mk " ++ Escape(Cmd1); _ -> Escape(Cmd) end end, fun() -> case lists:keyfind(pre_hooks, 1, Conf) of false -> ok; {_, Hooks} -> [case H of {'get-deps', Cmd} -> Write("\npre-deps::\n\t" ++ PatchHook(Cmd) ++ "\n"); {compile, Cmd} -> Write("\npre-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); {{pc, compile}, Cmd} -> Write("\npre-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); {Regex, compile, Cmd} -> case rebar_utils:is_arch(Regex) of true -> Write("\npre-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); false -> ok end; _ -> ok end || H <- Hooks] end end(), fun() -> case lists:keyfind(post_hooks, 1, Conf) of false -> ok; {_, Hooks} -> [case H of {compile, Cmd} -> Write("\npost-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); {{pc, compile}, Cmd} -> Write("\npost-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); {Regex, compile, Cmd} -> case rebar_utils:is_arch(Regex) of true -> Write("\npost-app::\n\tCC=$$\(CC) " ++ PatchHook(Cmd) ++ "\n"); false -> ok end; _ -> ok end || H <- Hooks] end end(), ShellToMk = fun(V0) -> V1 = re:replace(V0, "[$$][(]", "$$\(shell ", [global]), V = re:replace(V1, "([$$])(?![(])(\\\\w*)", "\\\\1(\\\\2)", [global]), re:replace(V, "-Werror\\\\b", "", [{return, list}, global]) end, PortSpecs = fun() -> case lists:keyfind(port_specs, 1, Conf) of false -> case filelib:is_dir("$(call core_native_path,$(DEPS_DIR)/$1/c_src)") of false -> []; true -> [{"priv/" ++ proplists:get_value(so_name, Conf, "$(1)_drv.so"), proplists:get_value(port_sources, Conf, ["c_src/*.c"]), []}] end; {_, Specs} -> lists:flatten([case S of {Output, Input} -> {ShellToMk(Output), Input, []}; {Regex, Output, Input} -> case rebar_utils:is_arch(Regex) of true -> {ShellToMk(Output), Input, []}; false -> [] end; {Regex, Output, Input, [{env, Env}]} -> case rebar_utils:is_arch(Regex) of true -> {ShellToMk(Output), Input, Env}; false -> [] end end || S <- Specs]) end end(), PortSpecWrite = fun (Text) -> file:write_file("$(call core_native_path,$(DEPS_DIR)/$1/c_src/Makefile.erlang.mk)", Text, [append]) end, case PortSpecs of [] -> ok; _ -> Write("\npre-app::\n\t@$$\(MAKE) --no-print-directory -f c_src/Makefile.erlang.mk\n"), PortSpecWrite(io_lib:format("ERL_CFLAGS ?= -finline-functions -Wall -fPIC -I \\"~s/erts-~s/include\\" -I \\"~s\\"\n", [code:root_dir(), erlang:system_info(version), code:lib_dir(erl_interface, include)])), PortSpecWrite(io_lib:format("ERL_LDFLAGS ?= -L \\"~s\\" -lei\n", [code:lib_dir(erl_interface, lib)])), [PortSpecWrite(["\n", E, "\n"]) || E <- OsEnv], FilterEnv = fun(Env) -> lists:flatten([case E of {_, _} -> E; {Regex, K, V} -> case rebar_utils:is_arch(Regex) of true -> {K, V}; false -> [] end end || E <- Env]) end, MergeEnv = fun(Env) -> lists:foldl(fun ({K, V}, Acc) -> case lists:keyfind(K, 1, Acc) of false -> [{K, rebar_utils:expand_env_variable(V, K, "")}|Acc]; {_, V0} -> [{K, rebar_utils:expand_env_variable(V, K, V0)}|Acc] end end, [], Env) end, PortEnv = case lists:keyfind(port_env, 1, Conf) of false -> []; {_, PortEnv0} -> FilterEnv(PortEnv0) end, PortSpec = fun ({Output, Input0, Env}) -> filelib:ensure_dir("$(call core_native_path,$(DEPS_DIR)/$1/)" ++ Output), Input = [[" ", I] || I <- Input0], PortSpecWrite([ [["\n", K, " = ", ShellToMk(V)] || {K, V} <- lists:reverse(MergeEnv(PortEnv))], case $(PLATFORM) of darwin -> "\n\nLDFLAGS += -flat_namespace -undefined suppress"; _ -> "" end, "\n\nall:: ", Output, "\n\t@:\n\n", "%.o: %.c\n\t$$\(CC) -c -o $$\@ $$\< $$\(CFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", "%.o: %.C\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", "%.o: %.cc\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", "%.o: %.cpp\n\t$$\(CXX) -c -o $$\@ $$\< $$\(CXXFLAGS) $$\(ERL_CFLAGS) $$\(DRV_CFLAGS) $$\(EXE_CFLAGS)\n\n", [[Output, ": ", K, " += ", ShellToMk(V), "\n"] || {K, V} <- lists:reverse(MergeEnv(FilterEnv(Env)))], Output, ": $$\(foreach ext,.c .C .cc .cpp,", "$$\(patsubst %$$\(ext),%.o,$$\(filter %$$\(ext),$$\(wildcard", Input, "))))\n", "\t$$\(CC) -o $$\@ $$\? $$\(LDFLAGS) $$\(ERL_LDFLAGS) $$\(DRV_LDFLAGS) $$\(LDLIBS) $$\(EXE_LDFLAGS)", case {filename:extension(Output), $(PLATFORM)} of {[], _} -> "\n"; {".so", darwin} -> " -shared\n"; {".dylib", darwin} -> " -shared\n"; {_, darwin} -> "\n"; _ -> " -shared\n" end]) end, [PortSpec(S) || S <- PortSpecs] end, fun() -> case lists:keyfind(plugins, 1, Conf) of false -> ok; {_, Plugins0} -> Plugins = [P || P <- Plugins0, is_tuple(P)], case lists:keyfind('lfe-compile', 1, Plugins) of false -> ok; _ -> Write("\nBUILD_DEPS = lfe lfe.mk\ndep_lfe.mk = git https://github.com/ninenines/lfe.mk master\nDEP_PLUGINS = lfe.mk\n") end end end(), Write("\ninclude $$\(if $$\(ERLANG_MK_FILENAME),$$\(ERLANG_MK_FILENAME),erlang.mk)"), RunPlugin = fun(Plugin, Step) -> case erlang:function_exported(Plugin, Step, 2) of false -> ok; true -> c:cd("$(call core_native_path,$(DEPS_DIR)/$1/)"), Ret = Plugin:Step({config, "", Conf, dict:new(), dict:new(), dict:new(), dict:store(base_dir, "", dict:new())}, undefined), io:format("rebar plugin ~p step ~p ret ~p~n", [Plugin, Step, Ret]) end end, fun() -> case lists:keyfind(plugins, 1, Conf) of false -> ok; {_, Plugins0} -> Plugins = [P || P <- Plugins0, is_atom(P)], [begin case lists:keyfind(deps, 1, Conf) of false -> ok; {_, Deps} -> case lists:keyfind(P, 1, Deps) of false -> ok; _ -> Path = "$(call core_native_path,$(DEPS_DIR)/)" ++ atom_to_list(P), io:format("~s", [os:cmd("$(MAKE) -C $(call core_native_path,$(DEPS_DIR)/$1) " ++ Path)]), io:format("~s", [os:cmd("$(MAKE) -C " ++ Path ++ " IS_DEP=1")]), code:add_patha(Path ++ "/ebin") end end end || P <- Plugins], [case code:load_file(P) of {module, P} -> ok; _ -> case lists:keyfind(plugin_dir, 1, Conf) of false -> ok; {_, PluginsDir} -> ErlFile = "$(call core_native_path,$(DEPS_DIR)/$1/)" ++ PluginsDir ++ "/" ++ atom_to_list(P) ++ ".erl", {ok, P, Bin} = compile:file(ErlFile, [binary]), {module, P} = code:load_binary(P, ErlFile, Bin) end end || P <- Plugins], [RunPlugin(P, preprocess) || P <- Plugins], [RunPlugin(P, pre_compile) || P <- Plugins], [RunPlugin(P, compile) || P <- Plugins] end end(), halt() endef define dep_autopatch_appsrc_script.erl AppSrc = "$(call core_native_path,$(DEPS_DIR)/$1/src/$1.app.src)", AppSrcScript = AppSrc ++ ".script", Conf1 = case file:consult(AppSrc) of {ok, Conf0} -> Conf0; {error, enoent} -> [] end, Bindings0 = erl_eval:new_bindings(), Bindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0), Bindings = erl_eval:add_binding('SCRIPT', AppSrcScript, Bindings1), Conf = case file:script(AppSrcScript, Bindings) of {ok, [C]} -> C; {ok, C} -> C end, ok = file:write_file(AppSrc, io_lib:format("~p.~n", [Conf])), halt() endef define dep_autopatch_appsrc.erl AppSrcOut = "$(call core_native_path,$(DEPS_DIR)/$1/src/$1.app.src)", AppSrcIn = case filelib:is_regular(AppSrcOut) of false -> "$(call core_native_path,$(DEPS_DIR)/$1/ebin/$1.app)"; true -> AppSrcOut end, case filelib:is_regular(AppSrcIn) of false -> ok; true -> {ok, [{application, $1, L0}]} = file:consult(AppSrcIn), L1 = lists:keystore(modules, 1, L0, {modules, []}), L2 = case lists:keyfind(vsn, 1, L1) of {_, git} -> lists:keyreplace(vsn, 1, L1, {vsn, lists:droplast(os:cmd("git -C $(DEPS_DIR)/$1 describe --dirty --tags --always"))}); {_, {cmd, _}} -> lists:keyreplace(vsn, 1, L1, {vsn, "cmd"}); _ -> L1 end, L3 = case lists:keyfind(registered, 1, L2) of false -> [{registered, []}|L2]; _ -> L2 end, ok = file:write_file(AppSrcOut, io_lib:format("~p.~n", [{application, $1, L3}])), case AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end end, halt() endef ifeq ($(CACHE_DEPS),1) define dep_cache_fetch_git mkdir -p $(CACHE_DIR)/git; \ if test -d "$(join $(CACHE_DIR)/git/,$(call query_name,$1))"; then \ cd $(join $(CACHE_DIR)/git/,$(call query_name,$1)); \ if ! git checkout -q $(call query_version,$1); then \ git remote set-url origin $(call query_repo_git,$1) && \ git pull --all && \ git cat-file -e $(call query_version_git,$1) 2>/dev/null; \ fi; \ else \ git clone -q -n -- $(call query_repo_git,$1) $(join $(CACHE_DIR)/git/,$(call query_name,$1)); \ fi; \ git clone -q --single-branch -- $(join $(CACHE_DIR)/git/,$(call query_name,$1)) $2; \ cd $2 && git checkout -q $(call query_version_git,$1) endef define dep_fetch_git $(call dep_cache_fetch_git,$1,$(DEPS_DIR)/$(call query_name,$1)); endef define dep_fetch_git-subfolder mkdir -p $(ERLANG_MK_TMP)/git-subfolder; \ $(call dep_cache_fetch_git,$1,$(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1)); \ ln -s $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1)/$(word 4,$(dep_$1)) \ $(DEPS_DIR)/$(call query_name,$1); endef else define dep_fetch_git git clone -q -n -- $(call query_repo_git,$1) $(DEPS_DIR)/$(call query_name,$1); \ cd $(DEPS_DIR)/$(call query_name,$1) && git checkout -q $(call query_version_git,$1); endef define dep_fetch_git-subfolder mkdir -p $(ERLANG_MK_TMP)/git-subfolder; \ git clone -q -n -- $(call query_repo_git-subfolder,$1) \ $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1); \ cd $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1) \ && git checkout -q $(call query_version_git-subfolder,$1); \ ln -s $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1)/$(word 4,$(dep_$1)) \ $(DEPS_DIR)/$(call query_name,$1); endef endif define dep_fetch_git-submodule git submodule update --init -- $(DEPS_DIR)/$1; endef define dep_fetch_hg hg clone -q -U $(call query_repo_hg,$1) $(DEPS_DIR)/$(call query_name,$1); \ cd $(DEPS_DIR)/$(call query_name,$1) && hg update -q $(call query_version_hg,$1); endef define dep_fetch_svn svn checkout -q $(call query_repo_svn,$1) $(DEPS_DIR)/$(call query_name,$1); endef define dep_fetch_cp cp -R $(call query_repo_cp,$1) $(DEPS_DIR)/$(call query_name,$1); endef define dep_fetch_ln ln -s $(call query_repo_ln,$1) $(DEPS_DIR)/$(call query_name,$1); endef define hex_get_tarball.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), case hex_repo:get_tarball(Config, <<"$1">>, <<"$(strip $2)">>) of {ok, {200, _, Tarball}} -> ok = file:write_file("$(call core_native_path,$3)", Tarball), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(79) end endef ifeq ($(CACHE_DEPS),1) # Hex only has a package version. No need to look in the Erlang.mk packages. define dep_fetch_hex mkdir -p $(CACHE_DIR)/hex $(DEPS_DIR)/$1; \ $(eval hex_pkg_name := $(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1)) \ $(eval hex_tar_name := $(hex_pkg_name)-$(strip $(word 2,$(dep_$1))).tar) \ $(if $(wildcard $(CACHE_DIR)/hex/$(hex_tar_name)),,\ $(call erlang,$(call hex_get_tarball.erl,$(hex_pkg_name),$(word 2,$(dep_$1)),$(CACHE_DIR)/hex/$(hex_tar_name)));) \ tar -xOf $(CACHE_DIR)/hex/$(hex_tar_name) contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -; endef else # Hex only has a package version. No need to look in the Erlang.mk packages. define dep_fetch_hex mkdir -p $(ERLANG_MK_TMP)/hex $(DEPS_DIR)/$1; \ $(call erlang,$(call hex_get_tarball.erl,$(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1),$(word 2,$(dep_$1)),$(ERLANG_MK_TMP)/hex/$1.tar)); \ tar -xOf $(ERLANG_MK_TMP)/hex/$1.tar contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -; endef endif define dep_fetch_fail echo "Error: Unknown or invalid dependency: $1." >&2; \ exit 78; endef define dep_target $(DEPS_DIR)/$(call query_name,$1): $(if $(filter elixir,$(BUILD_DEPS) $(DEPS)),$(if $(filter-out elixir,$1),$(ERLANG_MK_TMP)/dep_built/elixir)) $(if $(filter hex,$(call query_fetch_method,$1)),$(if $(wildcard $(DEPS_DIR)/$(call query_name,$1)),,$(ERLANG_MK_TMP)/dep_built/hex_core)) | $(ERLANG_MK_TMP) $(eval DEP_NAME := $(call query_name,$1)) $(eval DEP_STR := $(if $(filter $1,$(DEP_NAME)),$1,"$1 ($(DEP_NAME))")) $(verbose) if test -d $(APPS_DIR)/$(DEP_NAME); then \ echo "Error: Dependency" $(DEP_STR) "conflicts with application found in $(APPS_DIR)/$(DEP_NAME)." >&2; \ exit 17; \ fi $(verbose) mkdir -p $(DEPS_DIR) $(dep_verbose) $(call dep_fetch_$(strip $(call query_fetch_method,$1)),$1) $(verbose) if [ -f $(DEPS_DIR)/$1/configure.ac -o -f $(DEPS_DIR)/$1/configure.in ] \ && [ ! -f $(DEPS_DIR)/$1/configure ]; then \ echo " AUTO " $(DEP_STR); \ cd $(DEPS_DIR)/$1 && autoreconf -Wall -vif -I m4; \ fi - $(verbose) if [ -f $(DEPS_DIR)/$(DEP_NAME)/configure ]; then \ echo " CONF " $(DEP_STR); \ cd $(DEPS_DIR)/$(DEP_NAME) && ./configure; \ fi ifeq ($(filter $1,$(NO_AUTOPATCH)),) $(verbose) AUTOPATCH_METHOD=`$(call dep_autopatch_detect,$1)`; \ if [ $$$$? -eq 99 ]; then \ echo "Elixir is currently disabled. Please set 'ELIXIR = system' in the Makefile to enable"; \ exit 99; \ fi; \ $$(MAKE) --no-print-directory autopatch-$(DEP_NAME) AUTOPATCH_METHOD=$$$$AUTOPATCH_METHOD endif .PHONY: autopatch-$(call query_name,$1) ifeq ($1,elixir) autopatch-elixir:: $$(verbose) ln -s lib/elixir/ebin $(DEPS_DIR)/elixir/ else autopatch-$(call query_name,$1):: $$(autopatch_verbose) $$(call dep_autopatch_for_$(AUTOPATCH_METHOD),$(call query_name,$1)) endif endef # We automatically depend on hex_core when the project isn't already. $(if $(filter hex_core,$(DEPS) $(BUILD_DEPS) $(DOC_DEPS) $(REL_DEPS) $(TEST_DEPS)),,\ $(eval $(call dep_target,hex_core))) $(ERLANG_MK_TMP)/dep_built/hex_core: | $(ERLANG_MK_TMP) $(verbose) $(call maybe_flock,$(ERLANG_MK_TMP)/hex_core.lock,\ if [ ! -e $(ERLANG_MK_TMP)/dep_built/hex_core ]; then \ $(MAKE) $(DEPS_DIR)/hex_core; \ $(MAKE) -C $(DEPS_DIR)/hex_core IS_DEP=1; \ mkdir -p $(ERLANG_MK_TMP)/dep_built; \ touch $(ERLANG_MK_TMP)/dep_built/hex_core; \ fi) $(ERLANG_MK_TMP)/dep_built/elixir: | $(ERLANG_MK_TMP) $(verbose) $(call maybe_flock,$(ERLANG_MK_TMP)/elixir.lock,\ if [ ! -e $(ERLANG_MK_TMP)/dep_built/elixir ]; then \ $(MAKE) $(DEPS_DIR)/elixir; \ $(MAKE) -C $(DEPS_DIR)/elixir; \ mkdir -p $(ERLANG_MK_TMP)/dep_built; \ touch $(ERLANG_MK_TMP)/dep_built/elixir; \ fi) $(foreach dep,$(BUILD_DEPS) $(DEPS),$(eval $(call dep_target,$(dep)))) ifndef IS_APP clean:: clean-apps clean-apps: $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \ $(MAKE) -C $$dep clean IS_APP=1; \ done distclean:: distclean-apps distclean-apps: $(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \ $(MAKE) -C $$dep distclean IS_APP=1; \ done endif ifndef SKIP_DEPS distclean:: distclean-deps distclean-deps: $(gen_verbose) rm -rf $(DEPS_DIR) endif ifeq ($(CACHE_DEPS),1) cacheclean:: cacheclean-git cacheclean-hex cacheclean-git: $(gen_verbose) rm -rf $(CACHE_DIR)/git cacheclean-hex: $(gen_verbose) rm -rf $(CACHE_DIR)/hex endif # Forward-declare variables used in core/deps-tools.mk. This is required # in case plugins use them. ERLANG_MK_RECURSIVE_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-deps-list.log ERLANG_MK_RECURSIVE_DOC_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-doc-deps-list.log ERLANG_MK_RECURSIVE_REL_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-rel-deps-list.log ERLANG_MK_RECURSIVE_TEST_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-test-deps-list.log ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-shell-deps-list.log ERLANG_MK_QUERY_DEPS_FILE = $(ERLANG_MK_TMP)/query-deps.log ERLANG_MK_QUERY_DOC_DEPS_FILE = $(ERLANG_MK_TMP)/query-doc-deps.log ERLANG_MK_QUERY_REL_DEPS_FILE = $(ERLANG_MK_TMP)/query-rel-deps.log ERLANG_MK_QUERY_TEST_DEPS_FILE = $(ERLANG_MK_TMP)/query-test-deps.log ERLANG_MK_QUERY_SHELL_DEPS_FILE = $(ERLANG_MK_TMP)/query-shell-deps.log # Copyright (c) 2024, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: beam-cache-restore-app beam-cache-restore-test clean-beam-cache distclean-beam-cache BEAM_CACHE_DIR ?= $(ERLANG_MK_TMP)/beam-cache PROJECT_BEAM_CACHE_DIR = $(BEAM_CACHE_DIR)/$(PROJECT) clean:: clean-beam-cache clean-beam-cache: $(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR) distclean:: distclean-beam-cache $(PROJECT_BEAM_CACHE_DIR): $(verbose) mkdir -p $(PROJECT_BEAM_CACHE_DIR) distclean-beam-cache: $(gen_verbose) rm -rf $(BEAM_CACHE_DIR) beam-cache-restore-app: | $(PROJECT_BEAM_CACHE_DIR) $(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR)/ebin-test ifneq ($(wildcard ebin/),) $(verbose) mv ebin/ $(PROJECT_BEAM_CACHE_DIR)/ebin-test endif ifneq ($(wildcard $(PROJECT_BEAM_CACHE_DIR)/ebin-app),) $(gen_verbose) mv $(PROJECT_BEAM_CACHE_DIR)/ebin-app ebin/ else $(verbose) $(MAKE) --no-print-directory clean-app endif $(verbose) rm $(ERLANG_MK_TMP)/$(PROJECT).test beam-cache-restore-test: | $(PROJECT_BEAM_CACHE_DIR) $(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR)/ebin-app ifneq ($(wildcard ebin/),) $(verbose) mv ebin/ $(PROJECT_BEAM_CACHE_DIR)/ebin-app endif ifneq ($(wildcard $(PROJECT_BEAM_CACHE_DIR)/ebin-test),) $(gen_verbose) mv $(PROJECT_BEAM_CACHE_DIR)/ebin-test ebin/ else $(verbose) $(MAKE) --no-print-directory clean-app endif # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: clean-app # Configuration. ERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \ +warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec COMPILE_FIRST ?= COMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST))) ERLC_EXCLUDE ?= ERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE))) ERLC_ASN1_OPTS ?= ERLC_MIB_OPTS ?= COMPILE_MIB_FIRST ?= COMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST))) # Verbosity. app_verbose_0 = @echo " APP " $(PROJECT); app_verbose_2 = set -x; app_verbose = $(app_verbose_$(V)) appsrc_verbose_0 = @echo " APP " $(PROJECT).app.src; appsrc_verbose_2 = set -x; appsrc_verbose = $(appsrc_verbose_$(V)) makedep_verbose_0 = @echo " DEPEND" $(PROJECT).d; makedep_verbose_2 = set -x; makedep_verbose = $(makedep_verbose_$(V)) erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\ $(filter %.erl %.core,$(?F))); erlc_verbose_2 = set -x; erlc_verbose = $(erlc_verbose_$(V)) xyrl_verbose_0 = @echo " XYRL " $(filter %.xrl %.yrl,$(?F)); xyrl_verbose_2 = set -x; xyrl_verbose = $(xyrl_verbose_$(V)) asn1_verbose_0 = @echo " ASN1 " $(filter %.asn1,$(?F)); asn1_verbose_2 = set -x; asn1_verbose = $(asn1_verbose_$(V)) mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F)); mib_verbose_2 = set -x; mib_verbose = $(mib_verbose_$(V)) ifneq ($(wildcard src/)$(wildcard lib/),) # Targets. app:: $(if $(wildcard $(ERLANG_MK_TMP)/$(PROJECT).test),beam-cache-restore-app) deps $(verbose) $(MAKE) --no-print-directory $(PROJECT).d $(verbose) $(MAKE) --no-print-directory app-build PROJECT_MOD := $(if $(PROJECT_MOD),$(PROJECT_MOD),$(if $(wildcard src/$(PROJECT)_app.erl),$(PROJECT)_app)) define app_file {application, '$(PROJECT)', [ {description, "$(PROJECT_DESCRIPTION)"}, {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP), {id$(comma)$(space)"$1"}$(comma)) {modules, [$(call comma_list,$2)]}, {registered, [$(if $(PROJECT_MOD),$(call comma_list,$(if $(filter $(PROJECT_MOD),$(PROJECT)_app),$(PROJECT)_sup) $(PROJECT_REGISTERED)))]}, {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(OPTIONAL_DEPS) $(foreach dep,$(DEPS),$(call query_name,$(dep))))]}, {optional_applications, [$(call comma_list,$(OPTIONAL_DEPS))]},$(if $(PROJECT_MOD), {mod$(comma)$(space){$(patsubst %,'%',$(PROJECT_MOD))$(comma)$(space)[]}}$(comma)) {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),) ]}. endef app-build: ebin/$(PROJECT).app $(verbose) : # Source files. ALL_SRC_FILES := $(sort $(call core_find,src/,*)) ERL_FILES := $(filter %.erl,$(ALL_SRC_FILES)) CORE_FILES := $(filter %.core,$(ALL_SRC_FILES)) ALL_LIB_FILES := $(sort $(call core_find,lib/,*)) EX_FILES := $(filter-out lib/mix/%,$(filter %.ex,$(ALL_SRC_FILES) $(ALL_LIB_FILES))) # ASN.1 files. ifneq ($(wildcard asn1/),) ASN1_FILES = $(sort $(call core_find,asn1/,*.asn1)) ERL_FILES += $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES)))) define compile_asn1 $(verbose) mkdir -p include/ $(asn1_verbose) erlc -v -I include/ -o asn1/ +noobj $(ERLC_ASN1_OPTS) $1 $(verbose) mv asn1/*.erl src/ -$(verbose) mv asn1/*.hrl include/ $(verbose) mv asn1/*.asn1db include/ endef $(PROJECT).d:: $(ASN1_FILES) $(if $(strip $?),$(call compile_asn1,$?)) endif # SNMP MIB files. ifneq ($(wildcard mibs/),) MIB_FILES = $(sort $(call core_find,mibs/,*.mib)) $(PROJECT).d:: $(COMPILE_MIB_FIRST_PATHS) $(MIB_FILES) $(verbose) mkdir -p include/ priv/mibs/ $(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ -I priv/mibs/ $? $(mib_verbose) erlc -o include/ -- $(addprefix priv/mibs/,$(patsubst %.mib,%.bin,$(notdir $?))) endif # Leex and Yecc files. XRL_FILES := $(filter %.xrl,$(ALL_SRC_FILES)) XRL_ERL_FILES = $(addprefix src/,$(patsubst %.xrl,%.erl,$(notdir $(XRL_FILES)))) ERL_FILES += $(XRL_ERL_FILES) YRL_FILES := $(filter %.yrl,$(ALL_SRC_FILES)) YRL_ERL_FILES = $(addprefix src/,$(patsubst %.yrl,%.erl,$(notdir $(YRL_FILES)))) ERL_FILES += $(YRL_ERL_FILES) $(PROJECT).d:: $(XRL_FILES) $(YRL_FILES) $(if $(strip $?),$(xyrl_verbose) erlc -v -o src/ $(YRL_ERLC_OPTS) $?) # Erlang and Core Erlang files. define makedep.erl E = ets:new(makedep, [bag]), G = digraph:new([acyclic]), ErlFiles = lists:usort(string:tokens("$(ERL_FILES)", " ")), DepsDir = "$(call core_native_path,$(DEPS_DIR))", AppsDir = "$(call core_native_path,$(APPS_DIR))", DepsDirsSrc = "$(if $(wildcard $(DEPS_DIR)/*/src), $(call core_native_path,$(wildcard $(DEPS_DIR)/*/src)))", DepsDirsInc = "$(if $(wildcard $(DEPS_DIR)/*/include), $(call core_native_path,$(wildcard $(DEPS_DIR)/*/include)))", AppsDirsSrc = "$(if $(wildcard $(APPS_DIR)/*/src), $(call core_native_path,$(wildcard $(APPS_DIR)/*/src)))", AppsDirsInc = "$(if $(wildcard $(APPS_DIR)/*/include), $(call core_native_path,$(wildcard $(APPS_DIR)/*/include)))", DepsDirs = lists:usort(string:tokens(DepsDirsSrc++DepsDirsInc, " ")), AppsDirs = lists:usort(string:tokens(AppsDirsSrc++AppsDirsInc, " ")), Modules = [{list_to_atom(filename:basename(F, ".erl")), F} || F <- ErlFiles], Add = fun (Mod, Dep) -> case lists:keyfind(Dep, 1, Modules) of false -> ok; {_, DepFile} -> {_, ModFile} = lists:keyfind(Mod, 1, Modules), ets:insert(E, {ModFile, DepFile}), digraph:add_vertex(G, Mod), digraph:add_vertex(G, Dep), digraph:add_edge(G, Mod, Dep) end end, AddHd = fun (F, Mod, DepFile) -> case file:open(DepFile, [read]) of {error, enoent} -> ok; {ok, Fd} -> {_, ModFile} = lists:keyfind(Mod, 1, Modules), case ets:match(E, {ModFile, DepFile}) of [] -> ets:insert(E, {ModFile, DepFile}), F(F, Fd, Mod,0); _ -> ok end end end, SearchHrl = fun F(_Hrl, []) -> {error,enoent}; F(Hrl, [Dir|Dirs]) -> HrlF = filename:join([Dir,Hrl]), case filelib:is_file(HrlF) of true -> {ok, HrlF}; false -> F(Hrl,Dirs) end end, Attr = fun (_F, Mod, behavior, Dep) -> Add(Mod, Dep); (_F, Mod, behaviour, Dep) -> Add(Mod, Dep); (_F, Mod, compile, {parse_transform, Dep}) -> Add(Mod, Dep); (_F, Mod, compile, Opts) when is_list(Opts) -> case proplists:get_value(parse_transform, Opts) of undefined -> ok; Dep -> Add(Mod, Dep) end; (F, Mod, include, Hrl) -> case SearchHrl(Hrl, ["src", "include",AppsDir,DepsDir]++AppsDirs++DepsDirs) of {ok, FoundHrl} -> AddHd(F, Mod, FoundHrl); {error, _} -> false end; (F, Mod, include_lib, Hrl) -> case SearchHrl(Hrl, ["src", "include",AppsDir,DepsDir]++AppsDirs++DepsDirs) of {ok, FoundHrl} -> AddHd(F, Mod, FoundHrl); {error, _} -> false end; (F, Mod, import, {Imp, _}) -> IsFile = case lists:keyfind(Imp, 1, Modules) of false -> false; {_, FilePath} -> filelib:is_file(FilePath) end, case IsFile of false -> ok; true -> Add(Mod, Imp) end; (_, _, _, _) -> ok end, MakeDepend = fun (F, Fd, Mod, StartLocation) -> case io:parse_erl_form(Fd, undefined, StartLocation) of {ok, AbsData, EndLocation} -> case AbsData of {attribute, _, Key, Value} -> Attr(F, Mod, Key, Value), F(F, Fd, Mod, EndLocation); _ -> F(F, Fd, Mod, EndLocation) end; {eof, _ } -> file:close(Fd); {error, ErrorDescription } -> file:close(Fd); {error, ErrorInfo, ErrorLocation} -> F(F, Fd, Mod, ErrorLocation) end, ok end, [begin Mod = list_to_atom(filename:basename(F, ".erl")), case file:open(F, [read]) of {ok, Fd} -> MakeDepend(MakeDepend, Fd, Mod,0); {error, enoent} -> ok end end || F <- ErlFiles], Depend = sofs:to_external(sofs:relation_to_family(sofs:relation(ets:tab2list(E)))), CompileFirst = [X || X <- lists:reverse(digraph_utils:topsort(G)), [] =/= digraph:in_neighbours(G, X)], TargetPath = fun(Target) -> case lists:keyfind(Target, 1, Modules) of false -> ""; {_, DepFile} -> DirSubname = tl(string:tokens(filename:dirname(DepFile), "/")), string:join(DirSubname ++ [atom_to_list(Target)], "/") end end, Output0 = [ "# Generated by Erlang.mk. Edit at your own risk!\n\n", [[F, "::", [[" ", D] || D <- Deps], "; @touch \$$@\n"] || {F, Deps} <- Depend], "\nCOMPILE_FIRST +=", [[" ", TargetPath(CF)] || CF <- CompileFirst], "\n" ], Output = case "é" of [233] -> unicode:characters_to_binary(Output0); _ -> Output0 end, ok = file:write_file("$1", Output), halt() endef ifeq ($(if $(NO_MAKEDEP),$(wildcard $(PROJECT).d),),) $(PROJECT).d:: $(ERL_FILES) $(EX_FILES) $(call core_find,include/,*.hrl) $(MAKEFILE_LIST) # Rebuild everything when the .d file does not exist. # We touch $@ to make sure the command doesn't fail in empty projects. # The file will be generated with content immediately after. $(verbose) if ! test -e $@; then \ touch $@ $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES); \ fi $(makedep_verbose) $(call erlang,$(call makedep.erl,$@)) endif ifeq ($(IS_APP)$(IS_DEP),) ifneq ($(words $(ERL_FILES) $(EX_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES) $(EX_FILES)),0) # Rebuild everything when the Makefile changes. $(ERLANG_MK_TMP)/last-makefile-change: $(MAKEFILE_LIST) | $(ERLANG_MK_TMP) $(verbose) if test -f $@; then \ touch $(ERL_FILES) $(EX_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES) $(EX_FILES); \ touch -c $(PROJECT).d; \ fi $(verbose) touch $@ $(ERL_FILES) $(EX_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(ERLANG_MK_TMP)/last-makefile-change ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change endif endif $(PROJECT).d:: $(verbose) : include $(wildcard $(PROJECT).d) ebin/$(PROJECT).app:: ebin/ ebin/: $(verbose) mkdir -p ebin/ define compile_erl $(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \ -pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),$(COMPILE_FIRST_PATHS) $1) endef define validate_app_file case file:consult("ebin/$(PROJECT).app") of {ok, _} -> halt(); _ -> halt(1) end endef ebin/$(PROJECT).app:: $(ERL_FILES) $(CORE_FILES) $(wildcard src/$(PROJECT).app.src) $(EX_FILES) $(eval FILES_TO_COMPILE := $(filter-out $(EX_FILES) src/$(PROJECT).app.src,$?)) $(if $(strip $(FILES_TO_COMPILE)),$(call compile_erl,$(FILES_TO_COMPILE))) $(if $(filter $(ELIXIR),disable),,$(if $(filter $?,$(EX_FILES)),$(elixirc_verbose) $(eval MODULES := $(shell $(call erlang,$(call compile_ex.erl,$(EX_FILES))))))) $(eval ELIXIR_COMP_FAILED := $(if $(filter _ERROR_,$(firstword $(MODULES))),true,false)) # Older git versions do not have the --first-parent flag. Do without in that case. $(verbose) if $(ELIXIR_COMP_FAILED); then exit 1; fi $(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null \ || git describe --dirty --abbrev=7 --tags --always 2>/dev/null || true)) $(eval MODULES := $(sort $(MODULES) $(patsubst %,'%',$(notdir $(basename \ $(filter-out $(ERLC_EXCLUDE_PATHS),$(ERL_FILES) $(CORE_FILES) $(BEAM_FILES))))))) ifeq ($(wildcard src/$(PROJECT).app.src),) $(app_verbose) printf '$(subst %,%%,$(subst $(newline),\n,$(subst ','\'',$(call app_file,$(GITDESCRIBE),$(MODULES)))))' \ > ebin/$(PROJECT).app $(verbose) if ! $(call erlang,$(call validate_app_file)); then \ echo "The .app file produced is invalid. Please verify the value of PROJECT_ENV." >&2; \ exit 1; \ fi else $(verbose) if [ -z "$$(grep -e '^[^%]*{\s*modules\s*,' src/$(PROJECT).app.src)" ]; then \ echo "Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk documentation for instructions." >&2; \ exit 1; \ fi $(appsrc_verbose) cat src/$(PROJECT).app.src \ | sed "s/{[[:space:]]*modules[[:space:]]*,[[:space:]]*\[\]}/{modules, \[$(call comma_list,$(MODULES))\]}/" \ | sed "s/{id,[[:space:]]*\"git\"}/{id, \"$(subst /,\/,$(GITDESCRIBE))\"}/" \ > ebin/$(PROJECT).app endif ifneq ($(wildcard src/$(PROJECT).appup),) $(verbose) cp src/$(PROJECT).appup ebin/ endif clean:: clean-app clean-app: $(gen_verbose) rm -rf $(PROJECT).d ebin/ priv/mibs/ $(XRL_ERL_FILES) $(YRL_ERL_FILES) \ $(addprefix include/,$(patsubst %.mib,%.hrl,$(notdir $(MIB_FILES)))) \ $(addprefix include/,$(patsubst %.asn1,%.hrl,$(notdir $(ASN1_FILES)))) \ $(addprefix include/,$(patsubst %.asn1,%.asn1db,$(notdir $(ASN1_FILES)))) \ $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES)))) endif # Copyright (c) 2024, Tyler Hughes # Copyright (c) 2024, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifeq ($(ELIXIR),system) # We expect 'elixir' to be on the path. ELIXIR_BIN ?= $(shell readlink -f `which elixir`) ELIXIR_LIBS ?= $(abspath $(dir $(ELIXIR_BIN))/../lib) # Fallback in case 'elixir' is a shim. ifeq ($(wildcard $(ELIXIR_LIBS)/elixir/),) ELIXIR_LIBS = $(abspath $(shell elixir -e 'IO.puts(:code.lib_dir(:elixir))')/../) endif ELIXIR_LIBS := $(ELIXIR_LIBS) export ELIXIR_LIBS ERL_LIBS := $(ERL_LIBS):$(ELIXIR_LIBS) else ifeq ($(ELIXIR),dep) ERL_LIBS := $(ERL_LIBS):$(DEPS_DIR)/elixir/lib/ endif endif elixirc_verbose_0 = @echo " EXC $(words $(EX_FILES)) files"; elixirc_verbose_2 = set -x; elixirc_verbose = $(elixirc_verbose_$(V)) # Unfortunately this currently requires Elixir. # https://github.com/jelly-beam/verl is a good choice # for an Erlang implementation, but we already have to # pull hex_core and Rebar3 so adding yet another pull # is annoying, especially one that would be necessary # every time we autopatch Rebar projects. Wait and see. define hex_version_resolver.erl HexVersionResolve = fun(Name, Req) -> application:ensure_all_started(ssl), application:ensure_all_started(inets), Config = $(hex_config.erl), case hex_repo:get_package(Config, atom_to_binary(Name)) of {ok, {200, _RespHeaders, Package}} -> #{releases := List} = Package, {value, #{version := Version}} = lists:search(fun(#{version := Vsn}) -> M = list_to_atom("Elixir.Version"), F = list_to_atom("match?"), M:F(Vsn, Req) end, List), {ok, Version}; {ok, {Status, _, Errors}} -> {error, Status, Errors} end end, HexVersionResolveAndPrint = fun(Name, Req) -> case HexVersionResolve(Name, Req) of {ok, Version} -> io:format("~s", [Version]), halt(0); {error, Status, Errors} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(77) end end endef define dep_autopatch_mix.erl $(call hex_version_resolver.erl), {ok, _} = application:ensure_all_started(elixir), {ok, _} = application:ensure_all_started(mix), MixFile = <<"$(call core_native_path,$(DEPS_DIR)/$1/mix.exs)">>, {Mod, Bin} = case elixir_compiler:file(MixFile, fun(_File, _LexerPid) -> ok end) of [{T = {_, _}, _CheckerPid}] -> T; [T = {_, _}] -> T end, {module, Mod} = code:load_binary(Mod, binary_to_list(MixFile), Bin), Project = Mod:project(), Application = try Mod:application() catch error:undef -> [] end, StartMod = case lists:keyfind(mod, 1, Application) of {mod, {StartMod0, _StartArgs}} -> atom_to_list(StartMod0); _ -> "" end, Write = fun (Text) -> file:write_file("$(call core_native_path,$(DEPS_DIR)/$1/Makefile)", Text, [append]) end, Write([ "PROJECT = ", atom_to_list(proplists:get_value(app, Project)), "\n" "PROJECT_DESCRIPTION = ", proplists:get_value(description, Project, ""), "\n" "PROJECT_VERSION = ", proplists:get_value(version, Project, ""), "\n" "PROJECT_MOD = ", StartMod, "\n" "define PROJECT_ENV\n", io_lib:format("~p", [proplists:get_value(env, Application, [])]), "\n" "endef\n\n"]), ExtraApps = lists:usort([eex, elixir, logger, mix] ++ proplists:get_value(extra_applications, Application, [])), Write(["LOCAL_DEPS += ", lists:join(" ", [atom_to_list(App) || App <- ExtraApps]), "\n\n"]), Deps = proplists:get_value(deps, Project, []) -- [elixir_make], IsRequiredProdDep = fun(Opts) -> (proplists:get_value(optional, Opts) =/= true) andalso case proplists:get_value(only, Opts, prod) of prod -> true; L when is_list(L) -> lists:member(prod, L); _ -> false end end, lists:foreach(fun ({Name, Req}) when is_binary(Req) -> {ok, Vsn} = HexVersionResolve(Name, Req), Write(["DEPS += ", atom_to_list(Name), "\n"]), Write(["dep_", atom_to_list(Name), " = hex ", Vsn, " ", atom_to_list(Name), "\n"]); ({Name, Opts}) when is_list(Opts) -> Path = proplists:get_value(path, Opts), case IsRequiredProdDep(Opts) of true when Path =/= undefined -> Write(["DEPS += ", atom_to_list(Name), "\n"]), Write(["dep_", atom_to_list(Name), " = ln ", Path, "\n"]); true when Path =:= undefined -> Write(["DEPS += ", atom_to_list(Name), "\n"]), io:format(standard_error, "Warning: No version given for ~p.", [Name]); false -> ok end; ({Name, Req, Opts}) -> case IsRequiredProdDep(Opts) of true -> {ok, Vsn} = HexVersionResolve(Name, Req), Write(["DEPS += ", atom_to_list(Name), "\n"]), Write(["dep_", atom_to_list(Name), " = hex ", Vsn, " ", atom_to_list(Name), "\n"]); false -> ok end; (_) -> ok end, Deps), case lists:member(elixir_make, proplists:get_value(compilers, Project, [])) of false -> ok; true -> Write("# https://hexdocs.pm/elixir_make/Mix.Tasks.Compile.ElixirMake.html\n"), MakeVal = fun(Key, Proplist, DefaultVal, DefaultReplacement) -> case proplists:get_value(Key, Proplist, DefaultVal) of DefaultVal -> DefaultReplacement; Value -> Value end end, MakeMakefile = binary_to_list(MakeVal(make_makefile, Project, default, <<"Makefile">>)), MakeExe = MakeVal(make_executable, Project, default, "$$\(MAKE)"), MakeCwd = MakeVal(make_cwd, Project, undefined, <<".">>), MakeTargets = MakeVal(make_targets, Project, [], []), MakeArgs = MakeVal(make_args, Project, undefined, []), case file:rename("$(DEPS_DIR)/$1/" ++ MakeMakefile, "$(DEPS_DIR)/$1/elixir_make.mk") of ok -> ok; Err = {error, _} -> io:format(standard_error, "Failed to copy Makefile with error ~p~n", [Err]), halt(90) end, Write(["app::\n" "\t", MakeExe, " -C ", MakeCwd, " -f $(DEPS_DIR)/$1/elixir_make.mk", lists:join(" ", MakeTargets), lists:join(" ", MakeArgs), "\n\n"]), case MakeVal(make_clean, Project, nil, undefined) of undefined -> ok; Clean -> Write(["clean::\n\t", Clean, "\n\n"]) end end, Write("ERLC_OPTS = +debug_info\n\n"), Write("include $$\(if $$\(ERLANG_MK_FILENAME),$$\(ERLANG_MK_FILENAME),erlang.mk)"), halt() endef define dep_autopatch_mix sed 's|\(defmodule.*do\)|\1\n try do\n Code.compiler_options(on_undefined_variable: :warn)\n rescue _ -> :ok\n end\n|g' $(DEPS_DIR)/$(1)/mix.exs > $(DEPS_DIR)/$(1)/mix.exs.new; \ mv $(DEPS_DIR)/$(1)/mix.exs.new $(DEPS_DIR)/$(1)/mix.exs; \ $(MAKE) $(ERLANG_MK_TMP)/dep_built/hex_core; \ MIX_ENV="$(if $(MIX_ENV),$(strip $(MIX_ENV)),prod)" \ $(call erlang,$(call dep_autopatch_mix.erl,$1)) endef # We change the group leader so the Elixir io:format output # isn't captured as we need to either print the modules on # success, or print _ERROR_ on failure. define compile_ex.erl {ok, _} = application:ensure_all_started(elixir), {ok, _} = application:ensure_all_started(mix), $(foreach dep,$(LOCAL_DEPS),_ = application:load($(dep)),) ModCode = list_to_atom("Elixir.Code"), ModCode:put_compiler_option(ignore_module_conflict, true), ModComp = list_to_atom("Elixir.Kernel.ParallelCompiler"), ModMixProject = list_to_atom("Elixir.Mix.Project"), erlang:group_leader(whereis(standard_error), self()), ModMixProject:in_project($(PROJECT), ".", [], fun(_MixFile) -> case ModComp:compile_to_path([$(call comma_list,$(patsubst %,<<"%">>,$1))], <<"ebin/">>) of {ok, Modules, _} -> lists:foreach(fun(E) -> io:format(user, "~p ", [E]) end, Modules), halt(0); {error, _ErroredModules, _WarnedModules} -> io:format(user, "_ERROR_", []), halt(1) end end) endef # Copyright (c) 2016, Loïc Hoguin # Copyright (c) 2015, Viktor Söderqvist # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: docs-deps # Configuration. ALL_DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS)) # Targets. $(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep)))) ifneq ($(SKIP_DEPS),) doc-deps: else doc-deps: $(ALL_DOC_DEPS_DIRS) $(verbose) set -e; for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep IS_DEP=1; done endif # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: rel-deps # Configuration. ALL_REL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(REL_DEPS)) # Targets. $(foreach dep,$(REL_DEPS),$(eval $(call dep_target,$(dep)))) ifneq ($(SKIP_DEPS),) rel-deps: else rel-deps: $(ALL_REL_DEPS_DIRS) $(verbose) set -e; for dep in $(ALL_REL_DEPS_DIRS) ; do $(MAKE) -C $$dep; done endif # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: test-deps test-dir test-build clean-test-dir # Configuration. TEST_DIR ?= $(CURDIR)/test ALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS)) TEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard TEST_ERLC_OPTS += -DTEST=1 # Targets. $(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep)))) ifneq ($(SKIP_DEPS),) test-deps: else test-deps: $(ALL_TEST_DEPS_DIRS) | $(ERLANG_MK_TMP)/dep_built $(verbose) set -e; for dep in $(ALL_TEST_DEPS_DIRS) ; do \ if [ -z "$(strip $(FULL))" ] && [ ! -L $$dep ] && [ -f $(ERLANG_MK_TMP)/dep_built/`basename $$dep` ]; then \ :; \ else \ $(MAKE) -C $$dep IS_DEP=1; \ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $(ERLANG_MK_TMP)/dep_built/`basename $$dep`; fi; \ fi \ done endif ifneq ($(wildcard $(TEST_DIR)),) test-dir: $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build @: test_erlc_verbose_0 = @echo " ERLC " $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\ $(filter %.erl %.core,$(notdir $(FILES_TO_COMPILE)))); test_erlc_verbose_2 = set -x; test_erlc_verbose = $(test_erlc_verbose_$(V)) define compile_test_erl $(test_erlc_verbose) erlc -v $(TEST_ERLC_OPTS) -o $(TEST_DIR) \ -pa ebin/ -I include/ $1 endef ERL_TEST_FILES = $(call core_find,$(TEST_DIR)/,*.erl) $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build: $(ERL_TEST_FILES) $(MAKEFILE_LIST) # When we have to recompile files in src/ the .d file always gets rebuilt. # Therefore we want to ignore it when rebuilding test files. $(eval FILES_TO_COMPILE := $(if $(filter $(filter-out $(PROJECT).d,$(MAKEFILE_LIST)),$?),$(filter $(ERL_TEST_FILES),$^),$(filter $(ERL_TEST_FILES),$?))) $(if $(strip $(FILES_TO_COMPILE)),$(call compile_test_erl,$(FILES_TO_COMPILE)) && touch $@) endif test-build:: IS_TEST=1 test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS) test-build:: $(if $(wildcard src),$(if $(wildcard $(ERLANG_MK_TMP)/$(PROJECT).test),,beam-cache-restore-test)) $(if $(IS_APP),,deps test-deps) # We already compiled everything when IS_APP=1. ifndef IS_APP ifneq ($(wildcard src),) $(verbose) $(MAKE) --no-print-directory $(PROJECT).d ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" $(verbose) $(MAKE) --no-print-directory app-build ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" $(gen_verbose) touch $(ERLANG_MK_TMP)/$(PROJECT).test endif ifneq ($(wildcard $(TEST_DIR)),) $(verbose) $(MAKE) --no-print-directory test-dir ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" endif endif # Roughly the same as test-build, but when IS_APP=1. # We only care about compiling the current application. ifdef IS_APP test-build-app:: ERLC_OPTS=$(TEST_ERLC_OPTS) test-build-app:: deps test-deps ifneq ($(wildcard src),) $(verbose) $(MAKE) --no-print-directory $(PROJECT).d ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" $(verbose) $(MAKE) --no-print-directory app-build ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" $(gen_verbose) touch $(ERLANG_MK_TMP)/$(PROJECT).test endif ifneq ($(wildcard $(TEST_DIR)),) $(verbose) $(MAKE) --no-print-directory test-dir ERLC_OPTS="$(call escape_dquotes,$(TEST_ERLC_OPTS))" endif endif clean:: clean-test-dir clean-test-dir: ifneq ($(wildcard $(TEST_DIR)/*.beam),) $(gen_verbose) rm -f $(TEST_DIR)/*.beam $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build endif # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: rebar.config compat_ref = {$(shell (git -C $(DEPS_DIR)/$1 show-ref -q --verify "refs/heads/$2" && echo branch) || (git -C $(DEPS_DIR)/$1 show-ref -q --verify "refs/tags/$2" && echo tag) || echo ref),"$2"} # We strip out -Werror because we don't want to fail due to # warnings when used as a dependency. compat_prepare_erlc_opts = $(shell echo "$1" | sed 's/, */,/g') define compat_convert_erlc_opts $(if $(filter-out -Werror,$1),\ $(if $(findstring +,$1),\ $(shell echo $1 | cut -b 2-))) endef define compat_erlc_opts_to_list [$(call comma_list,$(foreach o,$(call compat_prepare_erlc_opts,$1),$(call compat_convert_erlc_opts,$o)))] endef define compat_rebar_config {deps, [ $(call comma_list,$(foreach d,$(DEPS),\ $(if $(filter hex,$(call query_fetch_method,$d)),\ {$(call query_name,$d)$(comma)"$(call query_version_hex,$d)"},\ {$(call query_name,$d)$(comma)".*"$(comma){git,"$(call query_repo,$d)"$(comma)$(call compat_ref,$(call query_name,$d),$(call query_version,$d))}}))) ]}. {erl_opts, $(call compat_erlc_opts_to_list,$(ERLC_OPTS))}. endef rebar.config: deps $(gen_verbose) $(call core_render,compat_rebar_config,rebar.config) define tpl_application.app.src {application, project_name, [ {description, ""}, {vsn, "0.1.0"}, {id, "git"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib ]}, {mod, {project_name_app, []}}, {env, []} ]}. endef define tpl_application -module(project_name_app). -behaviour(application). -export([start/2]). -export([stop/1]). start(_Type, _Args) -> project_name_sup:start_link(). stop(_State) -> ok. endef define tpl_apps_Makefile PROJECT = project_name PROJECT_DESCRIPTION = New project PROJECT_VERSION = 0.1.0 template_sp # Make sure we know where the applications are located. ROOT_DIR ?= rel_root_dir APPS_DIR ?= .. DEPS_DIR ?= rel_deps_dir include rel_root_dir/erlang.mk endef define tpl_cowboy_http_h -module(template_name). -behaviour(cowboy_http_handler). -export([init/3]). -export([handle/2]). -export([terminate/3]). -record(state, { }). init(_, Req, _Opts) -> {ok, Req, #state{}}. handle(Req, State=#state{}) -> {ok, Req2} = cowboy_req:reply(200, Req), {ok, Req2, State}. terminate(_Reason, _Req, _State) -> ok. endef define tpl_cowboy_loop_h -module(template_name). -behaviour(cowboy_loop_handler). -export([init/3]). -export([info/3]). -export([terminate/3]). -record(state, { }). init(_, Req, _Opts) -> {loop, Req, #state{}, 5000, hibernate}. info(_Info, Req, State) -> {loop, Req, State, hibernate}. terminate(_Reason, _Req, _State) -> ok. endef define tpl_cowboy_rest_h -module(template_name). -export([init/3]). -export([content_types_provided/2]). -export([get_html/2]). init(_, _Req, _Opts) -> {upgrade, protocol, cowboy_rest}. content_types_provided(Req, State) -> {[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}. get_html(Req, State) -> {<<"This is REST!">>, Req, State}. endef define tpl_cowboy_websocket_h -module(template_name). -behaviour(cowboy_websocket_handler). -export([init/3]). -export([websocket_init/3]). -export([websocket_handle/3]). -export([websocket_info/3]). -export([websocket_terminate/3]). -record(state, { }). init(_, _, _) -> {upgrade, protocol, cowboy_websocket}. websocket_init(_, Req, _Opts) -> Req2 = cowboy_req:compact(Req), {ok, Req2, #state{}}. websocket_handle({text, Data}, Req, State) -> {reply, {text, Data}, Req, State}; websocket_handle({binary, Data}, Req, State) -> {reply, {binary, Data}, Req, State}; websocket_handle(_Frame, Req, State) -> {ok, Req, State}. websocket_info(_Info, Req, State) -> {ok, Req, State}. websocket_terminate(_Reason, _Req, _State) -> ok. endef define tpl_gen_fsm -module(template_name). -behaviour(gen_fsm). %% API. -export([start_link/0]). %% gen_fsm. -export([init/1]). -export([state_name/2]). -export([handle_event/3]). -export([state_name/3]). -export([handle_sync_event/4]). -export([handle_info/3]). -export([terminate/3]). -export([code_change/4]). -record(state, { }). %% API. -spec start_link() -> {ok, pid()}. start_link() -> gen_fsm:start_link(?MODULE, [], []). %% gen_fsm. init([]) -> {ok, state_name, #state{}}. state_name(_Event, StateData) -> {next_state, state_name, StateData}. handle_event(_Event, StateName, StateData) -> {next_state, StateName, StateData}. state_name(_Event, _From, StateData) -> {reply, ignored, state_name, StateData}. handle_sync_event(_Event, _From, StateName, StateData) -> {reply, ignored, StateName, StateData}. handle_info(_Info, StateName, StateData) -> {next_state, StateName, StateData}. terminate(_Reason, _StateName, _StateData) -> ok. code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. endef define tpl_gen_server -module(template_name). -behaviour(gen_server). %% API. -export([start_link/0]). %% gen_server. -export([init/1]). -export([handle_call/3]). -export([handle_cast/2]). -export([handle_info/2]). -export([terminate/2]). -export([code_change/3]). -record(state, { }). %% API. -spec start_link() -> {ok, pid()}. start_link() -> gen_server:start_link(?MODULE, [], []). %% gen_server. init([]) -> {ok, #state{}}. handle_call(_Request, _From, State) -> {reply, ignored, State}. handle_cast(_Msg, State) -> {noreply, State}. handle_info(_Info, State) -> {noreply, State}. terminate(_Reason, _State) -> ok. code_change(_OldVsn, State, _Extra) -> {ok, State}. endef define tpl_gen_statem -module(template_name). -behaviour(gen_statem). %% API. -export([start_link/0]). %% gen_statem. -export([callback_mode/0]). -export([init/1]). -export([state_name/3]). -export([handle_event/4]). -export([terminate/3]). -export([code_change/4]). -record(state, { }). %% API. -spec start_link() -> {ok, pid()}. start_link() -> gen_statem:start_link(?MODULE, [], []). %% gen_statem. callback_mode() -> state_functions. init([]) -> {ok, state_name, #state{}}. state_name(_EventType, _EventData, StateData) -> {next_state, state_name, StateData}. handle_event(_EventType, _EventData, StateName, StateData) -> {next_state, StateName, StateData}. terminate(_Reason, _StateName, _StateData) -> ok. code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. endef define tpl_library.app.src {application, project_name, [ {description, ""}, {vsn, "0.1.0"}, {id, "git"}, {modules, []}, {registered, []}, {applications, [ kernel, stdlib ]} ]}. endef define tpl_module -module(template_name). -export([]). endef define tpl_ranch_protocol -module(template_name). -behaviour(ranch_protocol). -export([start_link/4]). -export([init/4]). -type opts() :: []. -export_type([opts/0]). -record(state, { socket :: inet:socket(), transport :: module() }). start_link(Ref, Socket, Transport, Opts) -> Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), {ok, Pid}. -spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok. init(Ref, Socket, Transport, _Opts) -> ok = ranch:accept_ack(Ref), loop(#state{socket=Socket, transport=Transport}). loop(State) -> loop(State). endef define tpl_relx.config {release, {project_name_release, "1"}, [project_name, sasl, runtime_tools]}. {dev_mode, false}. {include_erts, true}. {extended_start_script, true}. {sys_config, "config/sys.config"}. {vm_args, "config/vm.args"}. endef define tpl_supervisor -module(template_name). -behaviour(supervisor). -export([start_link/0]). -export([init/1]). start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> Procs = [], {ok, {{one_for_one, 1, 5}, Procs}}. endef define tpl_sys.config [ ]. endef define tpl_top_Makefile PROJECT = project_name PROJECT_DESCRIPTION = New project PROJECT_VERSION = 0.1.0 template_sp include erlang.mk endef define tpl_vm.args -name project_name@127.0.0.1 -setcookie project_name -heart endef # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifeq ($(filter asciideck,$(DEPS) $(DOC_DEPS)),asciideck) .PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc-guide distclean-asciidoc-manual # Core targets. docs:: asciidoc distclean:: distclean-asciidoc-guide distclean-asciidoc-manual # Plugin-specific targets. asciidoc: asciidoc-guide asciidoc-manual # User guide. ifeq ($(wildcard doc/src/guide/book.asciidoc),) asciidoc-guide: else asciidoc-guide: distclean-asciidoc-guide doc-deps a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/ distclean-asciidoc-guide: $(gen_verbose) rm -rf doc/html/ doc/guide.pdf endif # Man pages. ASCIIDOC_MANUAL_FILES := $(wildcard doc/src/manual/*.asciidoc) ifeq ($(ASCIIDOC_MANUAL_FILES),) asciidoc-manual: else # Configuration. MAN_INSTALL_PATH ?= /usr/local/share/man MAN_SECTIONS ?= 3 7 MAN_PROJECT ?= $(shell echo $(PROJECT) | awk '{ print toupper(substr($$0,1,1)) substr($$0,2) }') MAN_VERSION ?= $(PROJECT_VERSION) # Plugin-specific targets. define asciidoc2man.erl try [begin io:format(" ADOC ~s~n", [F]), ok = asciideck:to_manpage(asciideck:parse_file(F), #{ compress => gzip, outdir => filename:dirname(F), extra2 => "$(MAN_PROJECT) $(MAN_VERSION)", extra3 => "$(MAN_PROJECT) Function Reference" }) end || F <- [$(shell echo $(addprefix $(comma)\",$(addsuffix \",$1)) | sed 's/^.//')]], halt(0) catch C:E$(if $V,:S) -> io:format("Exception: ~p:~p~n$(if $V,Stacktrace: ~p~n)", [C, E$(if $V,$(comma) S)]), halt(1) end. endef asciidoc-manual:: doc-deps asciidoc-manual:: $(ASCIIDOC_MANUAL_FILES) $(gen_verbose) $(call erlang,$(call asciidoc2man.erl,$?)) $(verbose) $(foreach s,$(MAN_SECTIONS),mkdir -p doc/man$s/ && mv doc/src/manual/*.$s.gz doc/man$s/;) install-docs:: install-asciidoc install-asciidoc: asciidoc-manual $(foreach s,$(MAN_SECTIONS),\ mkdir -p $(MAN_INSTALL_PATH)/man$s/ && \ install -g `id -g` -o `id -u` -m 0644 doc/man$s/*.gz $(MAN_INSTALL_PATH)/man$s/;) distclean-asciidoc-manual: $(gen_verbose) rm -rf $(addprefix doc/man,$(MAN_SECTIONS)) endif endif # Copyright (c) 2014-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates # Core targets. help:: $(verbose) printf "%s\n" "" \ "Bootstrap targets:" \ " bootstrap Generate a skeleton of an OTP application" \ " bootstrap-lib Generate a skeleton of an OTP library" \ " bootstrap-rel Generate the files needed to build a release" \ " new-app in=NAME Create a new local OTP application NAME" \ " new-lib in=NAME Create a new local OTP library NAME" \ " new t=TPL n=NAME Generate a module NAME based on the template TPL" \ " new t=T n=N in=APP Generate a module NAME based on the template TPL in APP" \ " list-templates List available templates" # Plugin-specific targets. ifndef WS ifdef SP WS = $(subst a,,a $(wordlist 1,$(SP),a a a a a a a a a a a a a a a a a a a a)) else WS = $(tab) endif endif ifdef SP define template_sp # By default templates indent with a single tab per indentation # level. Set this variable to the number of spaces you prefer: SP = $(SP) endef else template_sp = endif # @todo Additional template placeholders could be added. subst_template = $(subst rel_root_dir,$(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app),$(subst rel_deps_dir,$(call core_relpath,$(DEPS_DIR),$(APPS_DIR)/app),$(subst template_sp,$(template_sp),$(subst project_name,$p,$(subst template_name,$n,$1))))) define core_render_template $(eval define _tpl_$(1)$(newline)$(call subst_template,$(tpl_$(1)))$(newline)endef) $(verbose) $(call core_render,_tpl_$(1),$2) endef bootstrap: ifneq ($(wildcard src/),) $(error Error: src/ directory already exists) endif $(eval p := $(PROJECT)) $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(eval n := $(PROJECT)_sup) $(verbose) $(call core_render_template,top_Makefile,Makefile) $(verbose) mkdir src/ ifdef LEGACY $(verbose) $(call core_render_template,application.app.src,src/$(PROJECT).app.src) endif $(verbose) $(call core_render_template,application,src/$(PROJECT)_app.erl) $(verbose) $(call core_render_template,supervisor,src/$(PROJECT)_sup.erl) bootstrap-lib: ifneq ($(wildcard src/),) $(error Error: src/ directory already exists) endif $(eval p := $(PROJECT)) $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(verbose) $(call core_render_template,top_Makefile,Makefile) $(verbose) mkdir src/ ifdef LEGACY $(verbose) $(call core_render_template,library.app.src,src/$(PROJECT).app.src) endif bootstrap-rel: ifneq ($(wildcard relx.config),) $(error Error: relx.config already exists) endif ifneq ($(wildcard config/),) $(error Error: config/ directory already exists) endif $(eval p := $(PROJECT)) $(verbose) $(call core_render_template,relx.config,relx.config) $(verbose) mkdir config/ $(verbose) $(call core_render_template,sys.config,config/sys.config) $(verbose) $(call core_render_template,vm.args,config/vm.args) $(verbose) awk '/^include erlang.mk/ && !ins {print "REL_DEPS += relx";ins=1};{print}' Makefile > Makefile.bak $(verbose) mv Makefile.bak Makefile new-app: ifndef in $(error Usage: $(MAKE) new-app in=APP) endif ifneq ($(wildcard $(APPS_DIR)/$in),) $(error Error: Application $in already exists) endif $(eval p := $(in)) $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(eval n := $(in)_sup) $(verbose) mkdir -p $(APPS_DIR)/$p/src/ $(verbose) $(call core_render_template,apps_Makefile,$(APPS_DIR)/$p/Makefile) ifdef LEGACY $(verbose) $(call core_render_template,application.app.src,$(APPS_DIR)/$p/src/$p.app.src) endif $(verbose) $(call core_render_template,application,$(APPS_DIR)/$p/src/$p_app.erl) $(verbose) $(call core_render_template,supervisor,$(APPS_DIR)/$p/src/$p_sup.erl) new-lib: ifndef in $(error Usage: $(MAKE) new-lib in=APP) endif ifneq ($(wildcard $(APPS_DIR)/$in),) $(error Error: Application $in already exists) endif $(eval p := $(in)) $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(verbose) mkdir -p $(APPS_DIR)/$p/src/ $(verbose) $(call core_render_template,apps_Makefile,$(APPS_DIR)/$p/Makefile) ifdef LEGACY $(verbose) $(call core_render_template,library.app.src,$(APPS_DIR)/$p/src/$p.app.src) endif # These are not necessary because we don't expose those as "normal" templates. BOOTSTRAP_TEMPLATES = apps_Makefile top_Makefile \ application.app.src library.app.src application \ relx.config sys.config vm.args # Templates may override the path they will be written to when using 'new'. # Only special template paths must be listed. Default is src/template_name.erl # Substitution is also applied to the paths. Examples: # #tplp_top_Makefile = Makefile #tplp_application.app.src = src/project_name.app.src #tplp_application = src/project_name_app.erl #tplp_relx.config = relx.config # Erlang.mk bundles its own templates at build time into the erlang.mk file. new: $(if $(t),,$(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP])) $(if $(n),,$(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP])) $(if $(tpl_$(t)),,$(error Error: $t template does not exist; try $(Make) list-templates)) $(eval dest := $(if $(in),$(APPS_DIR)/$(in)/)$(call subst_template,$(if $(tplp_$(t)),$(tplp_$(t)),src/template_name.erl))) $(if $(wildcard $(dir $(dest))),,$(error Error: $(dir $(dest)) directory does not exist)) $(if $(wildcard $(dest)),$(error Error: The file $(dest) already exists)) $(eval p := $(PROJECT)) $(call core_render_template,$(t),$(dest)) list-templates: $(verbose) @echo Available templates: $(verbose) printf " %s\n" $(sort $(filter-out $(BOOTSTRAP_TEMPLATES),$(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES))))) # Copyright (c) 2014-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: clean-c_src distclean-c_src-env # Configuration. C_SRC_DIR ?= $(CURDIR)/c_src C_SRC_ENV ?= $(C_SRC_DIR)/env.mk C_SRC_OUTPUT ?= $(CURDIR)/priv/$(PROJECT) C_SRC_TYPE ?= shared # System type and C compiler/flags. ifeq ($(PLATFORM),msys2) C_SRC_OUTPUT_EXECUTABLE_EXTENSION ?= .exe C_SRC_OUTPUT_SHARED_EXTENSION ?= .dll C_SRC_OUTPUT_STATIC_EXTENSION ?= .lib else C_SRC_OUTPUT_EXECUTABLE_EXTENSION ?= C_SRC_OUTPUT_SHARED_EXTENSION ?= .so C_SRC_OUTPUT_STATIC_EXTENSION ?= .a endif ifeq ($(C_SRC_TYPE),shared) C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_SHARED_EXTENSION) else ifeq ($(C_SRC_TYPE),static) C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_STATIC_EXTENSION) else C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_EXECUTABLE_EXTENSION) endif RANLIB ?= ranlib ARFLAGS ?= cr ifeq ($(PLATFORM),msys2) # We hardcode the compiler used on MSYS2. The default CC=cc does # not produce working code. The "gcc" MSYS2 package also doesn't. CC = /mingw64/bin/gcc export CC CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes CXXFLAGS ?= -O3 -finline-functions -Wall else ifeq ($(PLATFORM),darwin) CC ?= cc CFLAGS ?= -O3 -std=c99 -Wall -Wmissing-prototypes CXXFLAGS ?= -O3 -Wall LDFLAGS ?= -flat_namespace -undefined suppress else ifeq ($(PLATFORM),freebsd) CC ?= cc CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes CXXFLAGS ?= -O3 -finline-functions -Wall else ifeq ($(PLATFORM),linux) CC ?= gcc CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes CXXFLAGS ?= -O3 -finline-functions -Wall endif ifneq ($(PLATFORM),msys2) CFLAGS += -fPIC CXXFLAGS += -fPIC endif ifeq ($(C_SRC_TYPE),static) CFLAGS += -DSTATIC_ERLANG_NIF=1 CXXFLAGS += -DSTATIC_ERLANG_NIF=1 endif CFLAGS += -I"$(ERTS_INCLUDE_DIR)" -I"$(ERL_INTERFACE_INCLUDE_DIR)" CXXFLAGS += -I"$(ERTS_INCLUDE_DIR)" -I"$(ERL_INTERFACE_INCLUDE_DIR)" LDLIBS += -L"$(ERL_INTERFACE_LIB_DIR)" -lei # Verbosity. c_verbose_0 = @echo " C " $(filter-out $(notdir $(MAKEFILE_LIST) $(C_SRC_ENV)),$(^F)); c_verbose = $(c_verbose_$(V)) cpp_verbose_0 = @echo " CPP " $(filter-out $(notdir $(MAKEFILE_LIST) $(C_SRC_ENV)),$(^F)); cpp_verbose = $(cpp_verbose_$(V)) link_verbose_0 = @echo " LD " $(@F); link_verbose = $(link_verbose_$(V)) ar_verbose_0 = @echo " AR " $(@F); ar_verbose = $(ar_verbose_$(V)) ranlib_verbose_0 = @echo " RANLIB" $(@F); ranlib_verbose = $(ranlib_verbose_$(V)) # Targets. ifeq ($(wildcard $(C_SRC_DIR)),) else ifneq ($(wildcard $(C_SRC_DIR)/Makefile),) app:: app-c_src test-build:: app-c_src app-c_src: $(MAKE) -C $(C_SRC_DIR) clean:: $(MAKE) -C $(C_SRC_DIR) clean else ifeq ($(SOURCES),) SOURCES := $(sort $(foreach pat,*.c *.C *.cc *.cpp,$(call core_find,$(C_SRC_DIR)/,$(pat)))) endif OBJECTS = $(addsuffix .o, $(basename $(SOURCES))) COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c app:: $(C_SRC_ENV) $(C_SRC_OUTPUT_FILE) test-build:: $(C_SRC_ENV) $(C_SRC_OUTPUT_FILE) ifneq ($(C_SRC_TYPE),static) $(C_SRC_OUTPUT_FILE): $(OBJECTS) $(verbose) mkdir -p $(dir $@) $(link_verbose) $(CC) $(OBJECTS) \ $(LDFLAGS) $(if $(filter $(C_SRC_TYPE),shared),-shared) $(LDLIBS) \ -o $(C_SRC_OUTPUT_FILE) else $(C_SRC_OUTPUT_FILE): $(OBJECTS) $(verbose) mkdir -p $(dir $@) $(ar_verbose) $(AR) $(ARFLAGS) $(C_SRC_OUTPUT_FILE) $(OBJECTS) $(ranlib_verbose) $(RANLIB) $(C_SRC_OUTPUT_FILE) endif $(OBJECTS): $(MAKEFILE_LIST) $(C_SRC_ENV) %.o: %.c $(COMPILE_C) $(OUTPUT_OPTION) $< %.o: %.cc $(COMPILE_CPP) $(OUTPUT_OPTION) $< %.o: %.C $(COMPILE_CPP) $(OUTPUT_OPTION) $< %.o: %.cpp $(COMPILE_CPP) $(OUTPUT_OPTION) $< clean:: clean-c_src clean-c_src: $(gen_verbose) rm -f $(C_SRC_OUTPUT_FILE) $(OBJECTS) endif ifneq ($(wildcard $(C_SRC_DIR)),) ERL_ERTS_DIR = $(shell $(ERL) -eval 'io:format("~s~n", [code:lib_dir(erts)]), halt().') $(C_SRC_ENV): $(verbose) $(ERL) -eval "file:write_file(\"$(call core_native_path,$(C_SRC_ENV))\", \ io_lib:format( \ \"# Generated by Erlang.mk. Edit at your own risk!~n~n\" \ \"ERTS_INCLUDE_DIR ?= ~s/erts-~s/include/~n\" \ \"ERL_INTERFACE_INCLUDE_DIR ?= ~s~n\" \ \"ERL_INTERFACE_LIB_DIR ?= ~s~n\" \ \"ERTS_DIR ?= $(ERL_ERTS_DIR)~n\", \ [code:root_dir(), erlang:system_info(version), \ code:lib_dir(erl_interface, include), \ code:lib_dir(erl_interface, lib)])), \ halt()." distclean:: distclean-c_src-env distclean-c_src-env: $(gen_verbose) rm -f $(C_SRC_ENV) -include $(C_SRC_ENV) ifneq ($(ERL_ERTS_DIR),$(ERTS_DIR)) $(shell rm -f $(C_SRC_ENV)) endif endif # Templates. define bs_c_nif #include "erl_nif.h" static int loads = 0; static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) { /* Initialize private data. */ *priv_data = NULL; loads++; return 0; } static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info) { /* Convert the private data to the new version. */ *priv_data = *old_priv_data; loads++; return 0; } static void unload(ErlNifEnv* env, void* priv_data) { if (loads == 1) { /* Destroy the private data. */ } loads--; } static ERL_NIF_TERM hello(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) { if (enif_is_atom(env, argv[0])) { return enif_make_tuple2(env, enif_make_atom(env, "hello"), argv[0]); } return enif_make_tuple2(env, enif_make_atom(env, "error"), enif_make_atom(env, "badarg")); } static ErlNifFunc nif_funcs[] = { {"hello", 1, hello} }; ERL_NIF_INIT($n, nif_funcs, load, NULL, upgrade, unload) endef define bs_erl_nif -module($n). -export([hello/1]). -on_load(on_load/0). on_load() -> PrivDir = case code:priv_dir(?MODULE) of {error, _} -> AppPath = filename:dirname(filename:dirname(code:which(?MODULE))), filename:join(AppPath, "priv"); Path -> Path end, erlang:load_nif(filename:join(PrivDir, atom_to_list(?MODULE)), 0). hello(_) -> erlang:nif_error({not_loaded, ?MODULE}). endef new-nif: ifneq ($(wildcard $(C_SRC_DIR)/$n.c),) $(error Error: $(C_SRC_DIR)/$n.c already exists) endif ifneq ($(wildcard src/$n.erl),) $(error Error: src/$n.erl already exists) endif ifndef n $(error Usage: $(MAKE) new-nif n=NAME [in=APP]) endif ifdef in $(verbose) $(MAKE) -C $(APPS_DIR)/$(in)/ new-nif n=$n in= else $(verbose) mkdir -p $(C_SRC_DIR) src/ $(verbose) $(call core_render,bs_c_nif,$(C_SRC_DIR)/$n.c) $(verbose) $(call core_render,bs_erl_nif,src/$n.erl) endif # Copyright (c) 2015-2017, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: ci ci-prepare ci-setup CI_OTP ?= ifeq ($(strip $(CI_OTP)),) ci:: else ci:: $(addprefix ci-,$(CI_OTP)) ci-prepare: $(addprefix ci-prepare-,$(CI_OTP)) ci-setup:: $(verbose) : ci-extra:: $(verbose) : ci_verbose_0 = @echo " CI " $1; ci_verbose = $(ci_verbose_$(V)) define ci_target ci-prepare-$1: $(KERL_INSTALL_DIR)/$2 $(verbose) : ci-$1: ci-prepare-$1 $(verbose) $(MAKE) --no-print-directory clean $(ci_verbose) \ PATH="$(KERL_INSTALL_DIR)/$2/bin:$(PATH)" \ CI_OTP_RELEASE="$1" \ CT_OPTS="-label $1" \ CI_VM="$3" \ $(MAKE) ci-setup tests $(verbose) $(MAKE) --no-print-directory ci-extra endef $(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp),$(otp),otp))) $(foreach otp,$(filter-out $(ERLANG_OTP),$(CI_OTP)),$(eval $(call kerl_otp_target,$(otp)))) help:: $(verbose) printf "%s\n" "" \ "Continuous Integration targets:" \ " ci Run '$(MAKE) tests' on all configured Erlang versions." \ "" \ "The CI_OTP variable must be defined with the Erlang versions" \ "that must be tested. For example: CI_OTP = OTP-17.3.4 OTP-17.5.3" endif # Copyright (c) 2020, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifdef CONCUERROR_TESTS .PHONY: concuerror distclean-concuerror # Configuration CONCUERROR_LOGS_DIR ?= $(CURDIR)/logs CONCUERROR_OPTS ?= # Core targets. check:: concuerror ifndef KEEP_LOGS distclean:: distclean-concuerror endif # Plugin-specific targets. $(ERLANG_MK_TMP)/Concuerror/bin/concuerror: | $(ERLANG_MK_TMP) $(verbose) git clone https://github.com/parapluu/Concuerror $(ERLANG_MK_TMP)/Concuerror $(verbose) $(MAKE) -C $(ERLANG_MK_TMP)/Concuerror $(CONCUERROR_LOGS_DIR): $(verbose) mkdir -p $(CONCUERROR_LOGS_DIR) define concuerror_html_report Concuerror HTML report

Concuerror HTML report

Generated on $(concuerror_date)

    $(foreach t,$(concuerror_targets),
  • $(t)
  • )
endef concuerror: $(addprefix concuerror-,$(subst :,-,$(CONCUERROR_TESTS))) $(eval concuerror_date := $(shell date)) $(eval concuerror_targets := $^) $(verbose) $(call core_render,concuerror_html_report,$(CONCUERROR_LOGS_DIR)/concuerror.html) define concuerror_target .PHONY: concuerror-$1-$2 concuerror-$1-$2: test-build | $(ERLANG_MK_TMP)/Concuerror/bin/concuerror $(CONCUERROR_LOGS_DIR) $(ERLANG_MK_TMP)/Concuerror/bin/concuerror \ --pa $(CURDIR)/ebin --pa $(TEST_DIR) \ -o $(CONCUERROR_LOGS_DIR)/concuerror-$1-$2.txt \ $$(CONCUERROR_OPTS) -m $1 -t $2 endef $(foreach test,$(CONCUERROR_TESTS),$(eval $(call concuerror_target,$(firstword $(subst :, ,$(test))),$(lastword $(subst :, ,$(test)))))) distclean-concuerror: $(gen_verbose) rm -rf $(CONCUERROR_LOGS_DIR) endif # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: ct apps-ct distclean-ct # Configuration. CT_OPTS ?= ifneq ($(wildcard $(TEST_DIR)),) ifndef CT_SUITES CT_SUITES := $(sort $(subst _SUITE.erl,,$(notdir $(call core_find,$(TEST_DIR)/,*_SUITE.erl)))) endif endif CT_SUITES ?= CT_LOGS_DIR ?= $(CURDIR)/logs # Core targets. tests:: ct ifndef KEEP_LOGS distclean:: distclean-ct endif help:: $(verbose) printf "%s\n" "" \ "Common_test targets:" \ " ct Run all the common_test suites for this project" \ "" \ "All your common_test suites have their associated targets." \ "A suite named http_SUITE can be ran using the ct-http target." # Plugin-specific targets. CT_RUN = ct_run \ -no_auto_compile \ -noinput \ -pa $(CURDIR)/ebin $(TEST_DIR) \ -dir $(TEST_DIR) \ -logdir $(CT_LOGS_DIR) ifeq ($(CT_SUITES),) ct: $(if $(IS_APP)$(ROOT_DIR),,apps-ct) else # We do not run tests if we are in an apps/* with no test directory. ifneq ($(IS_APP)$(wildcard $(TEST_DIR)),1) ct: test-build $(if $(IS_APP)$(ROOT_DIR),,apps-ct) $(verbose) mkdir -p $(CT_LOGS_DIR) $(gen_verbose) $(CT_RUN) -sname ct_$(PROJECT) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS) endif endif ifneq ($(ALL_APPS_DIRS),) define ct_app_target apps-ct-$1: test-build $$(MAKE) -C $1 ct IS_APP=1 endef $(foreach app,$(ALL_APPS_DIRS),$(eval $(call ct_app_target,$(app)))) apps-ct: $(addprefix apps-ct-,$(ALL_APPS_DIRS)) endif ifdef t ifeq (,$(findstring :,$t)) CT_EXTRA = -group $t else t_words = $(subst :, ,$t) CT_EXTRA = -group $(firstword $(t_words)) -case $(lastword $(t_words)) endif else ifdef c CT_EXTRA = -case $c else CT_EXTRA = endif endif define ct_suite_target ct-$1: test-build $$(verbose) mkdir -p $$(CT_LOGS_DIR) $$(gen_verbose_esc) $$(CT_RUN) -sname ct_$$(PROJECT) -suite $$(addsuffix _SUITE,$1) $$(CT_EXTRA) $$(CT_OPTS) endef $(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test)))) distclean-ct: $(gen_verbose) rm -rf $(CT_LOGS_DIR) # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: plt distclean-plt dialyze # Configuration. DIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt export DIALYZER_PLT PLT_APPS ?= DIALYZER_DIRS ?= --src -r $(wildcard src) $(ALL_APPS_DIRS) DIALYZER_OPTS ?= -Werror_handling -Wunmatched_returns # -Wunderspecs DIALYZER_PLT_OPTS ?= # Core targets. check:: dialyze distclean:: distclean-plt help:: $(verbose) printf "%s\n" "" \ "Dialyzer targets:" \ " plt Build a PLT file for this project" \ " dialyze Analyze the project using Dialyzer" # Plugin-specific targets. define filter_opts.erl Opts = init:get_plain_arguments(), {Filtered, _} = lists:foldl(fun (O, {Os, true}) -> {[O|Os], false}; (O = "-D", {Os, _}) -> {[O|Os], true}; (O = [\\$$-, \\$$D, _ | _], {Os, _}) -> {[O|Os], false}; (O = "-I", {Os, _}) -> {[O|Os], true}; (O = [\\$$-, \\$$I, _ | _], {Os, _}) -> {[O|Os], false}; (O = "-pa", {Os, _}) -> {[O|Os], true}; (_, Acc) -> Acc end, {[], false}, Opts), io:format("~s~n", [string:join(lists:reverse(Filtered), " ")]), halt(). endef # DIALYZER_PLT is a variable understood directly by Dialyzer. # # We append the path to erts at the end of the PLT. This works # because the PLT file is in the external term format and the # function binary_to_term/1 ignores any trailing data. $(DIALYZER_PLT): deps app $(eval DEPS_LOG := $(shell test -f $(ERLANG_MK_TMP)/deps.log && \ while read p; do test -d $$p/ebin && echo $$p/ebin; done <$(ERLANG_MK_TMP)/deps.log)) $(verbose) dialyzer --build_plt $(DIALYZER_PLT_OPTS) --apps \ erts kernel stdlib $(PLT_APPS) $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS_LOG) || test $$? -eq 2 $(verbose) $(ERL) -eval 'io:format("~n~s~n", [code:lib_dir(erts)]), halt().' >> $@ plt: $(DIALYZER_PLT) distclean-plt: $(gen_verbose) rm -f $(DIALYZER_PLT) ifneq ($(wildcard $(DIALYZER_PLT)),) dialyze: $(if $(filter --src,$(DIALYZER_DIRS)),,deps app) $(verbose) if ! tail -n1 $(DIALYZER_PLT) | \ grep -q "^`$(ERL) -eval 'io:format("~s", [code:lib_dir(erts)]), halt().'`$$"; then \ rm $(DIALYZER_PLT); \ $(MAKE) plt; \ fi else dialyze: $(DIALYZER_PLT) endif $(verbose) dialyzer `$(ERL) \ -eval "$(subst $(newline),,$(call escape_dquotes,$(call filter_opts.erl)))" \ -extra $(ERLC_OPTS)` $(DIALYZER_DIRS) $(DIALYZER_OPTS) $(if $(wildcard ebin/),-pa ebin/) # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-edoc edoc # Configuration. EDOC_OPTS ?= EDOC_SRC_DIRS ?= EDOC_OUTPUT ?= doc define edoc.erl SrcPaths = lists:foldl(fun(P, Acc) -> filelib:wildcard(atom_to_list(P) ++ "/{src,c_src}") ++ lists:filter(fun(D) -> filelib:is_dir(D) end, filelib:wildcard(atom_to_list(P) ++ "/{src,c_src}/**")) ++ Acc end, [], [$(call comma_list,$(patsubst %,'%',$(call core_native_path,$(EDOC_SRC_DIRS))))]), DefaultOpts = [{dir, "$(EDOC_OUTPUT)"}, {source_path, SrcPaths}, {subpackages, false}], edoc:application($(1), ".", [$(2)] ++ DefaultOpts), halt(0). endef # Core targets. ifneq ($(strip $(EDOC_SRC_DIRS)$(wildcard doc/overview.edoc)),) docs:: edoc endif distclean:: distclean-edoc # Plugin-specific targets. edoc: distclean-edoc doc-deps $(gen_verbose) $(call erlang,$(call edoc.erl,$(PROJECT),$(EDOC_OPTS))) distclean-edoc: $(gen_verbose) rm -f $(EDOC_OUTPUT)/*.css $(EDOC_OUTPUT)/*.html $(EDOC_OUTPUT)/*.png $(EDOC_OUTPUT)/edoc-info # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. # Configuration. DTL_FULL_PATH ?= DTL_PATH ?= templates/ DTL_PREFIX ?= DTL_SUFFIX ?= _dtl DTL_OPTS ?= # Verbosity. dtl_verbose_0 = @echo " DTL " $(filter %.dtl,$(?F)); dtl_verbose = $(dtl_verbose_$(V)) # Core targets. DTL_PATH := $(abspath $(DTL_PATH)) DTL_FILES := $(sort $(call core_find,$(DTL_PATH),*.dtl)) ifneq ($(DTL_FILES),) DTL_NAMES = $(addprefix $(DTL_PREFIX),$(addsuffix $(DTL_SUFFIX),$(DTL_FILES:$(DTL_PATH)/%.dtl=%))) DTL_MODULES = $(if $(DTL_FULL_PATH),$(subst /,_,$(DTL_NAMES)),$(notdir $(DTL_NAMES))) BEAM_FILES += $(addsuffix .beam,$(addprefix ebin/,$(DTL_MODULES))) ifneq ($(words $(DTL_FILES)),0) # Rebuild templates when the Makefile changes. $(ERLANG_MK_TMP)/last-makefile-change-erlydtl: $(MAKEFILE_LIST) | $(ERLANG_MK_TMP) $(verbose) if test -f $@; then \ touch $(DTL_FILES); \ fi $(verbose) touch $@ ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change-erlydtl endif define erlydtl_compile.erl [begin Module0 = case "$(strip $(DTL_FULL_PATH))" of "" -> filename:basename(F, ".dtl"); _ -> "$(call core_native_path,$(DTL_PATH))/" ++ F2 = filename:rootname(F, ".dtl"), re:replace(F2, "/", "_", [{return, list}, global]) end, Module = list_to_atom("$(DTL_PREFIX)" ++ string:to_lower(Module0) ++ "$(DTL_SUFFIX)"), case erlydtl:compile(F, Module, [$(DTL_OPTS)] ++ [{out_dir, "ebin/"}, return_errors]) of ok -> ok; {ok, _} -> ok end end || F <- string:tokens("$(1)", " ")], halt(). endef ebin/$(PROJECT).app:: $(DTL_FILES) | ebin/ $(if $(strip $?),\ $(dtl_verbose) $(call erlang,$(call erlydtl_compile.erl,$(call core_native_path,$?)),\ -pa ebin/)) endif # Copyright (c) 2016, Loïc Hoguin # Copyright (c) 2014, Dave Cottlehuber # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-escript escript escript-zip # Configuration. ESCRIPT_NAME ?= $(PROJECT) ESCRIPT_FILE ?= $(ESCRIPT_NAME) ESCRIPT_SHEBANG ?= /usr/bin/env escript ESCRIPT_COMMENT ?= This is an -*- erlang -*- file ESCRIPT_EMU_ARGS ?= -escript main $(ESCRIPT_NAME) ESCRIPT_ZIP ?= 7z a -tzip -mx=9 -mtc=off $(if $(filter-out 0,$(V)),,> /dev/null) ESCRIPT_ZIP_FILE ?= $(ERLANG_MK_TMP)/escript.zip # Core targets. distclean:: distclean-escript help:: $(verbose) printf "%s\n" "" \ "Escript targets:" \ " escript Build an executable escript archive" \ # Plugin-specific targets. ALL_ESCRIPT_DEPS_DIRS = $(LOCAL_DEPS_DIRS) $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(DEPS)),$(call query_name,$(dep)))) ESCRIPT_RUNTIME_DEPS_FILE ?= $(ERLANG_MK_TMP)/escript-deps.log escript-list-runtime-deps: ifeq ($(IS_DEP),) $(verbose) rm -f $(ESCRIPT_RUNTIME_DEPS_FILE) endif $(verbose) touch $(ESCRIPT_RUNTIME_DEPS_FILE) $(verbose) set -e; for dep in $(ALL_ESCRIPT_DEPS_DIRS) ; do \ if ! grep -qs ^$$dep$$ $(ESCRIPT_RUNTIME_DEPS_FILE); then \ echo $$dep >> $(ESCRIPT_RUNTIME_DEPS_FILE); \ if grep -qs -E "^[[:blank:]]*include[[:blank:]]+(erlang\.mk|.*/erlang\.mk|.*ERLANG_MK_FILENAME.*)$$" \ $$dep/GNUmakefile $$dep/makefile $$dep/Makefile; then \ $(MAKE) -C $$dep escript-list-runtime-deps \ IS_DEP=1 \ ESCRIPT_RUNTIME_DEPS_FILE=$(ESCRIPT_RUNTIME_DEPS_FILE); \ fi \ fi \ done ifeq ($(IS_DEP),) $(verbose) sort < $(ESCRIPT_RUNTIME_DEPS_FILE) | uniq > $(ESCRIPT_RUNTIME_DEPS_FILE).sorted $(verbose) mv $(ESCRIPT_RUNTIME_DEPS_FILE).sorted $(ESCRIPT_RUNTIME_DEPS_FILE) endif escript-prepare: deps app $(MAKE) escript-list-runtime-deps escript-zip:: escript-prepare $(verbose) mkdir -p $(dir $(abspath $(ESCRIPT_ZIP_FILE))) $(verbose) rm -f $(abspath $(ESCRIPT_ZIP_FILE)) $(gen_verbose) cd .. && $(ESCRIPT_ZIP) $(abspath $(ESCRIPT_ZIP_FILE)) $(notdir $(CURDIR))/ebin/* ifneq ($(DEPS),) $(verbose) cd $(DEPS_DIR) && $(ESCRIPT_ZIP) $(abspath $(ESCRIPT_ZIP_FILE)) \ $(subst $(DEPS_DIR)/,,$(addsuffix /*,$(wildcard \ $(addsuffix /ebin,$(shell cat $(ESCRIPT_RUNTIME_DEPS_FILE)))))) endif # @todo Only generate the zip file if there were changes. escript:: escript-zip $(gen_verbose) printf "%s\n" \ "#!$(ESCRIPT_SHEBANG)" \ "%% $(ESCRIPT_COMMENT)" \ "%%! $(ESCRIPT_EMU_ARGS)" > $(ESCRIPT_FILE) $(verbose) cat $(abspath $(ESCRIPT_ZIP_FILE)) >> $(ESCRIPT_FILE) $(verbose) chmod +x $(ESCRIPT_FILE) distclean-escript: $(gen_verbose) rm -f $(ESCRIPT_FILE) $(abspath $(ESCRIPT_ZIP_FILE)) # Copyright (c) 2015-2016, Loïc Hoguin # Copyright (c) 2014, Enrique Fernandez # This file is contributed to erlang.mk and subject to the terms of the ISC License. .PHONY: eunit apps-eunit # Eunit can be disabled by setting this to any other value. EUNIT ?= system ifeq ($(EUNIT),system) # Configuration EUNIT_OPTS ?= EUNIT_ERL_OPTS ?= EUNIT_TEST_SPEC ?= $1 # Core targets. tests:: eunit help:: $(verbose) printf "%s\n" "" \ "EUnit targets:" \ " eunit Run all the EUnit tests for this project" # Plugin-specific targets. define eunit.erl $(call cover.erl) CoverSetup(), case eunit:test($(call EUNIT_TEST_SPEC,$1), [$(EUNIT_OPTS)]) of ok -> ok; error -> halt(2) end, CoverExport("$(call core_native_path,$(COVER_DATA_DIR))/eunit.coverdata"), halt() endef EUNIT_ERL_OPTS += -pa $(TEST_DIR) $(CURDIR)/ebin ifdef t ifeq (,$(findstring :,$(t))) eunit: test-build cover-data-dir $(gen_verbose) $(call erlang,$(call eunit.erl,['$(t)']),$(EUNIT_ERL_OPTS)) else eunit: test-build cover-data-dir $(gen_verbose) $(call erlang,$(call eunit.erl,fun $(t)/0),$(EUNIT_ERL_OPTS)) endif else EUNIT_EBIN_MODS = $(notdir $(basename $(ERL_FILES) $(BEAM_FILES))) EUNIT_TEST_MODS = $(notdir $(basename $(call core_find,$(TEST_DIR)/,*.erl))) EUNIT_MODS = $(foreach mod,$(EUNIT_EBIN_MODS) $(filter-out \ $(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(EUNIT_TEST_MODS)),'$(mod)') eunit: test-build $(if $(IS_APP)$(ROOT_DIR),,apps-eunit) cover-data-dir ifneq ($(wildcard src/ $(TEST_DIR)),) $(gen_verbose) $(call erlang,$(call eunit.erl,[$(call comma_list,$(EUNIT_MODS))]),$(EUNIT_ERL_OPTS)) endif ifneq ($(ALL_APPS_DIRS),) apps-eunit: test-build $(verbose) eunit_retcode=0 ; for app in $(ALL_APPS_DIRS); do $(MAKE) -C $$app eunit IS_APP=1; \ [ $$? -ne 0 ] && eunit_retcode=1 ; done ; \ exit $$eunit_retcode endif endif endif # Copyright (c) 2020, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. define hex_user_create.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), case hex_api_user:create(Config, <<"$(strip $1)">>, <<"$(strip $2)">>, <<"$(strip $3)">>) of {ok, {201, _, #{<<"email">> := Email, <<"url">> := URL, <<"username">> := Username}}} -> io:format("User ~s (~s) created at ~s~n" "Please check your inbox for a confirmation email.~n" "You must confirm before you are allowed to publish packages.~n", [Username, Email, URL]), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(80) end endef # The $(info ) call inserts a new line after the password prompt. hex-user-create: $(ERLANG_MK_TMP)/dep_built/hex_core $(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p "Username: " username; echo $$username))) $(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p "Password: " password; stty echo; echo $$password) $(info ))) $(if $(HEX_EMAIL),,$(eval HEX_EMAIL := $(shell read -p "Email: " email; echo $$email))) $(gen_verbose) $(call erlang,$(call hex_user_create.erl,$(HEX_USERNAME),$(HEX_PASSWORD),$(HEX_EMAIL))) define hex_key_add.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => iolist_to_binary([<<"Basic ">>, base64:encode(<<"$(strip $1):$(strip $2)">>)])}, Permissions = [ case string:split(P, <<":">>) of [D] -> #{domain => D}; [D, R] -> #{domain => D, resource => R} end || P <- string:split(<<"$(strip $4)">>, <<",">>, all)], case hex_api_key:add(ConfigF, <<"$(strip $3)">>, Permissions) of {ok, {201, _, #{<<"secret">> := Secret}}} -> io:format("Key ~s created for user ~s~nSecret: ~s~n" "Please store the secret in a secure location, such as a password store.~n" "The secret will be requested for most Hex-related operations.~n", [<<"$(strip $3)">>, <<"$(strip $1)">>, Secret]), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(81) end endef hex-key-add: $(ERLANG_MK_TMP)/dep_built/hex_core $(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p "Username: " username; echo $$username))) $(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p "Password: " password; stty echo; echo $$password) $(info ))) $(gen_verbose) $(call erlang,$(call hex_key_add.erl,$(HEX_USERNAME),$(HEX_PASSWORD),\ $(if $(name),$(name),$(shell hostname)-erlang-mk),\ $(if $(perm),$(perm),api))) HEX_TARBALL_EXTRA_METADATA ?= # @todo Check that we can += files HEX_TARBALL_FILES ?= \ $(wildcard early-plugins.mk) \ $(wildcard ebin/$(PROJECT).app) \ $(wildcard ebin/$(PROJECT).appup) \ $(wildcard $(notdir $(ERLANG_MK_FILENAME))) \ $(sort $(call core_find,include/,*.hrl)) \ $(wildcard LICENSE*) \ $(wildcard Makefile) \ $(wildcard plugins.mk) \ $(sort $(call core_find,priv/,*)) \ $(wildcard README*) \ $(wildcard rebar.config) \ $(sort $(if $(LEGACY),$(filter-out src/$(PROJECT).app.src,$(call core_find,src/,*)),$(call core_find,src/,*))) HEX_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT).tar # @todo Need to check for rebar.config and/or the absence of DEPS to know # whether a project will work with Rebar. # # @todo contributors licenses links in HEX_TARBALL_EXTRA_METADATA # In order to build the requirements metadata we look into DEPS. # We do not require that the project use Hex dependencies, however # Hex.pm does require that the package name and version numbers # correspond to a real Hex package. define hex_tarball_create.erl Files0 = [$(call comma_list,$(patsubst %,"%",$(HEX_TARBALL_FILES)))], Requirements0 = #{ $(foreach d,$(DEPS), <<"$(if $(subst hex,,$(call query_fetch_method,$d)),$d,$(if $(word 3,$(dep_$d)),$(word 3,$(dep_$d)),$d))">> => #{ <<"app">> => <<"$d">>, <<"optional">> => false, <<"requirement">> => <<"$(if $(hex_req_$d),$(strip $(hex_req_$d)),$(call query_version,$d))">> },) $(if $(DEPS),dummy => dummy) }, Requirements = maps:remove(dummy, Requirements0), Metadata0 = #{ app => <<"$(strip $(PROJECT))">>, build_tools => [<<"make">>, <<"rebar3">>], description => <<"$(strip $(PROJECT_DESCRIPTION))">>, files => [unicode:characters_to_binary(F) || F <- Files0], name => <<"$(strip $(PROJECT))">>, requirements => Requirements, version => <<"$(strip $(PROJECT_VERSION))">> }, Metadata = Metadata0$(HEX_TARBALL_EXTRA_METADATA), Files = [case file:read_file(F) of {ok, Bin} -> {F, Bin}; {error, Reason} -> io:format("Error trying to open file ~0p: ~0p~n", [F, Reason]), halt(82) end || F <- Files0], case hex_tarball:create(Metadata, Files) of {ok, #{tarball := Tarball}} -> ok = file:write_file("$(strip $(HEX_TARBALL_OUTPUT_FILE))", Tarball), halt(0); {error, Reason} -> io:format("Error ~0p~n", [Reason]), halt(83) end endef hex_tar_verbose_0 = @echo " TAR $(notdir $(ERLANG_MK_TMP))/$(@F)"; hex_tar_verbose_2 = set -x; hex_tar_verbose = $(hex_tar_verbose_$(V)) $(HEX_TARBALL_OUTPUT_FILE): $(ERLANG_MK_TMP)/dep_built/hex_core app $(hex_tar_verbose) $(call erlang,$(call hex_tarball_create.erl)) hex-tarball-create: $(HEX_TARBALL_OUTPUT_FILE) define hex_release_publish_summary.erl {ok, Tarball} = erl_tar:open("$(strip $(HEX_TARBALL_OUTPUT_FILE))", [read]), ok = erl_tar:extract(Tarball, [{cwd, "$(ERLANG_MK_TMP)"}, {files, ["metadata.config"]}]), {ok, Metadata} = file:consult("$(ERLANG_MK_TMP)/metadata.config"), #{ <<"name">> := Name, <<"version">> := Version, <<"files">> := Files, <<"requirements">> := Deps } = maps:from_list(Metadata), io:format("Publishing ~s ~s~n Dependencies:~n", [Name, Version]), case Deps of [] -> io:format(" (none)~n"); _ -> [begin #{<<"app">> := DA, <<"requirement">> := DR} = maps:from_list(D), io:format(" ~s ~s~n", [DA, DR]) end || {_, D} <- Deps] end, io:format(" Included files:~n"), [io:format(" ~s~n", [F]) || F <- Files], io:format("You may also review the contents of the tarball file.~n" "Please enter your secret key to proceed.~n"), halt(0) endef define hex_release_publish.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, {ok, Tarball} = file:read_file("$(strip $(HEX_TARBALL_OUTPUT_FILE))"), case hex_api_release:publish(ConfigF, Tarball, [{replace, $2}]) of {ok, {200, _, #{}}} -> io:format("Release replaced~n"), halt(0); {ok, {201, _, #{}}} -> io:format("Release published~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(84) end endef hex-release-tarball: $(ERLANG_MK_TMP)/dep_built/hex_core $(HEX_TARBALL_OUTPUT_FILE) $(verbose) $(call erlang,$(call hex_release_publish_summary.erl)) hex-release-publish: $(ERLANG_MK_TMP)/dep_built/hex_core hex-release-tarball $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),false)) hex-release-replace: $(ERLANG_MK_TMP)/dep_built/hex_core hex-release-tarball $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),true)) define hex_release_delete.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, case hex_api_release:delete(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $(PROJECT_VERSION))">>) of {ok, {204, _, _}} -> io:format("Release $(strip $(PROJECT_VERSION)) deleted~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(85) end endef hex-release-delete: $(ERLANG_MK_TMP)/dep_built/hex_core $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_delete.erl,$(HEX_SECRET))) define hex_release_retire.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, Params = #{<<"reason">> => <<"$(strip $3)">>, <<"message">> => <<"$(strip $4)">>}, case hex_api_release:retire(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $2)">>, Params) of {ok, {204, _, _}} -> io:format("Release $(strip $2) has been retired~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(86) end endef hex-release-retire: $(ERLANG_MK_TMP)/dep_built/hex_core $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_retire.erl,$(HEX_SECRET),\ $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)),\ $(if $(HEX_REASON),$(HEX_REASON),invalid),\ $(HEX_MESSAGE))) define hex_release_unretire.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, case hex_api_release:unretire(ConfigF, <<"$(strip $(PROJECT))">>, <<"$(strip $2)">>) of {ok, {204, _, _}} -> io:format("Release $(strip $2) is not retired anymore~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(87) end endef hex-release-unretire: $(ERLANG_MK_TMP)/dep_built/hex_core $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_unretire.erl,$(HEX_SECRET),\ $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)))) HEX_DOCS_DOC_DIR ?= doc/ HEX_DOCS_TARBALL_FILES ?= $(sort $(call core_find,$(HEX_DOCS_DOC_DIR),*)) HEX_DOCS_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT)-docs.tar.gz $(HEX_DOCS_TARBALL_OUTPUT_FILE): $(ERLANG_MK_TMP)/dep_built/hex_core app docs $(hex_tar_verbose) tar czf $(HEX_DOCS_TARBALL_OUTPUT_FILE) -C $(HEX_DOCS_DOC_DIR) \ $(HEX_DOCS_TARBALL_FILES:$(HEX_DOCS_DOC_DIR)%=%) hex-docs-tarball-create: $(HEX_DOCS_TARBALL_OUTPUT_FILE) define hex_docs_publish.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, {ok, Tarball} = file:read_file("$(strip $(HEX_DOCS_TARBALL_OUTPUT_FILE))"), case hex_api:post(ConfigF, ["packages", "$(strip $(PROJECT))", "releases", "$(strip $(PROJECT_VERSION))", "docs"], {"application/octet-stream", Tarball}) of {ok, {Status, _, _}} when Status >= 200, Status < 300 -> io:format("Docs published~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(88) end endef hex-docs-publish: $(ERLANG_MK_TMP)/dep_built/hex_core hex-docs-tarball-create $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_docs_publish.erl,$(HEX_SECRET))) define hex_docs_delete.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), Config = $(hex_config.erl), ConfigF = Config#{api_key => <<"$(strip $1)">>}, case hex_api:delete(ConfigF, ["packages", "$(strip $(PROJECT))", "releases", "$(strip $2)", "docs"]) of {ok, {Status, _, _}} when Status >= 200, Status < 300 -> io:format("Docs removed~n"), halt(0); {ok, {Status, _, Errors}} -> io:format("Error ~b: ~0p~n", [Status, Errors]), halt(89) end endef hex-docs-delete: $(ERLANG_MK_TMP)/dep_built/hex_core $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_docs_delete.erl,$(HEX_SECRET),\ $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)))) # Copyright (c) 2015-2017, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifeq ($(filter proper,$(DEPS) $(TEST_DEPS)),proper) .PHONY: proper # Targets. tests:: proper define proper_check.erl $(call cover.erl) code:add_pathsa([ "$(call core_native_path,$(CURDIR)/ebin)", "$(call core_native_path,$(DEPS_DIR)/*/ebin)", "$(call core_native_path,$(TEST_DIR))"]), Module = fun(M) -> [true] =:= lists:usort([ case atom_to_list(F) of "prop_" ++ _ -> io:format("Testing ~p:~p/0~n", [M, F]), proper:quickcheck(M:F(), nocolors); _ -> true end || {F, 0} <- M:module_info(exports)]) end, try begin CoverSetup(), Res = case $(1) of all -> [true] =:= lists:usort([Module(M) || M <- [$(call comma_list,$(3))]]); module -> Module($(2)); function -> proper:quickcheck($(2), nocolors) end, CoverExport("$(COVER_DATA_DIR)/proper.coverdata"), Res end of true -> halt(0); _ -> halt(1) catch error:undef$(if $V,:Stacktrace) -> io:format("Undefined property or module?~n$(if $V,~p~n)", [$(if $V,Stacktrace)]), halt(0) end. endef ifdef t ifeq (,$(findstring :,$(t))) proper: test-build cover-data-dir $(verbose) $(call erlang,$(call proper_check.erl,module,$(t))) else proper: test-build cover-data-dir $(verbose) echo Testing $(t)/0 $(verbose) $(call erlang,$(call proper_check.erl,function,$(t)())) endif else proper: test-build cover-data-dir $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \ $(wildcard ebin/*.beam) $(call core_find,$(TEST_DIR)/,*.beam)))))) $(gen_verbose) $(call erlang,$(call proper_check.erl,all,undefined,$(MODULES))) endif endif # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. # Verbosity. proto_verbose_0 = @echo " PROTO " $(filter %.proto,$(?F)); proto_verbose = $(proto_verbose_$(V)) # Core targets. ifneq ($(wildcard src/),) ifneq ($(filter gpb protobuffs,$(BUILD_DEPS) $(DEPS)),) PROTO_FILES := $(filter %.proto,$(ALL_SRC_FILES)) ERL_FILES += $(addprefix src/,$(patsubst %.proto,%_pb.erl,$(notdir $(PROTO_FILES)))) ifeq ($(PROTO_FILES),) $(ERLANG_MK_TMP)/last-makefile-change-protobuffs: $(verbose) : else # Rebuild proto files when the Makefile changes. # We exclude $(PROJECT).d to avoid a circular dependency. $(ERLANG_MK_TMP)/last-makefile-change-protobuffs: $(filter-out $(PROJECT).d,$(MAKEFILE_LIST)) | $(ERLANG_MK_TMP) $(verbose) if test -f $@; then \ touch $(PROTO_FILES); \ fi $(verbose) touch $@ $(PROJECT).d:: $(ERLANG_MK_TMP)/last-makefile-change-protobuffs endif ifeq ($(filter gpb,$(BUILD_DEPS) $(DEPS)),) define compile_proto.erl [begin protobuffs_compile:generate_source(F, [ {output_include_dir, "./include"}, {output_src_dir, "./src"}]) end || F <- string:tokens("$1", " ")], halt(). endef else define compile_proto.erl [begin gpb_compile:file(F, [ $(foreach i,$(sort $(dir $(PROTO_FILES))),{i$(comma) "$i"}$(comma)) {include_as_lib, true}, {module_name_suffix, "_pb"}, {o_hrl, "./include"}, {o_erl, "./src"}, {use_packages, true} ]) end || F <- string:tokens("$1", " ")], halt(). endef endif ifneq ($(PROTO_FILES),) $(PROJECT).d:: $(PROTO_FILES) $(verbose) mkdir -p ebin/ include/ $(if $(strip $?),$(proto_verbose) $(call erlang,$(call compile_proto.erl,$?))) endif endif endif # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifeq ($(filter relx,$(BUILD_DEPS) $(DEPS) $(REL_DEPS)),relx) .PHONY: relx-rel relx-relup distclean-relx-rel run # Configuration. RELX_CONFIG ?= $(CURDIR)/relx.config RELX_CONFIG_SCRIPT ?= $(CURDIR)/relx.config.script RELX_OUTPUT_DIR ?= _rel RELX_REL_EXT ?= RELX_TAR ?= 1 ifdef SFX RELX_TAR = 1 endif # Core targets. ifeq ($(IS_DEP),) ifneq ($(wildcard $(RELX_CONFIG))$(wildcard $(RELX_CONFIG_SCRIPT)),) rel:: relx-rel relup:: relx-relup endif endif distclean:: distclean-relx-rel # Plugin-specific targets. define relx_get_config.erl (fun() -> Config0 = case file:consult("$(call core_native_path,$(RELX_CONFIG))") of {ok, Terms} -> Terms; {error, _} -> [] end, case filelib:is_file("$(call core_native_path,$(RELX_CONFIG_SCRIPT))") of true -> Bindings = erl_eval:add_binding('CONFIG', Config0, erl_eval:new_bindings()), {ok, Config1} = file:script("$(call core_native_path,$(RELX_CONFIG_SCRIPT))", Bindings), Config1; false -> Config0 end end)() endef define relx_release.erl Config = $(call relx_get_config.erl), {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config), Vsn = case Vsn0 of {cmd, Cmd} -> os:cmd(Cmd); semver -> ""; {semver, _} -> ""; {git, short} -> string:trim(os:cmd("git rev-parse --short HEAD"), both, "\n"); {git, long} -> string:trim(os:cmd("git rev-parse HEAD"), both, "\n"); VsnStr -> Vsn0 end, {ok, _} = relx:build_release(#{name => Name, vsn => Vsn}, Config ++ [{output_dir, "$(RELX_OUTPUT_DIR)"}]), halt(0). endef define relx_tar.erl Config = $(call relx_get_config.erl), {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config), Vsn = case Vsn0 of {cmd, Cmd} -> os:cmd(Cmd); semver -> ""; {semver, _} -> ""; {git, short} -> string:trim(os:cmd("git rev-parse --short HEAD"), both, "\n"); {git, long} -> string:trim(os:cmd("git rev-parse HEAD"), both, "\n"); VsnStr -> Vsn0 end, {ok, _} = relx:build_tar(#{name => Name, vsn => Vsn}, Config ++ [{output_dir, "$(RELX_OUTPUT_DIR)"}]), halt(0). endef define relx_relup.erl Config = $(call relx_get_config.erl), {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config), Vsn = case Vsn0 of {cmd, Cmd} -> os:cmd(Cmd); semver -> ""; {semver, _} -> ""; {git, short} -> string:trim(os:cmd("git rev-parse --short HEAD"), both, "\n"); {git, long} -> string:trim(os:cmd("git rev-parse HEAD"), both, "\n"); VsnStr -> Vsn0 end, {ok, _} = relx:build_relup(Name, Vsn, undefined, Config ++ [{output_dir, "$(RELX_OUTPUT_DIR)"}]), halt(0). endef relx-rel: rel-deps app $(call erlang,$(call relx_release.erl),-pa ebin/) $(verbose) $(MAKE) relx-post-rel $(if $(filter-out 0,$(RELX_TAR)),$(call erlang,$(call relx_tar.erl),-pa ebin/)) relx-relup: rel-deps app $(call erlang,$(call relx_release.erl),-pa ebin/) $(MAKE) relx-post-rel $(call erlang,$(call relx_relup.erl),-pa ebin/) $(if $(filter-out 0,$(RELX_TAR)),$(call erlang,$(call relx_tar.erl),-pa ebin/)) distclean-relx-rel: $(gen_verbose) rm -rf $(RELX_OUTPUT_DIR) # Default hooks. relx-post-rel:: $(verbose) : # Run target. ifeq ($(wildcard $(RELX_CONFIG))$(wildcard $(RELX_CONFIG_SCRIPT)),) run:: else define get_relx_release.erl Config = $(call relx_get_config.erl), {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config), Vsn = case Vsn0 of {cmd, Cmd} -> os:cmd(Cmd); semver -> ""; {semver, _} -> ""; {git, short} -> string:trim(os:cmd("git rev-parse --short HEAD"), both, "\n"); {git, long} -> string:trim(os:cmd("git rev-parse HEAD"), both, "\n"); VsnStr -> Vsn0 end, Extended = case lists:keyfind(extended_start_script, 1, Config) of {_, true} -> "1"; _ -> "" end, io:format("~s ~s ~s", [Name, Vsn, Extended]), halt(0). endef RELX_REL := $(shell $(call erlang,$(get_relx_release.erl))) RELX_REL_NAME := $(word 1,$(RELX_REL)) RELX_REL_VSN := $(word 2,$(RELX_REL)) RELX_REL_CMD := $(if $(word 3,$(RELX_REL)),console) ifeq ($(PLATFORM),msys2) RELX_REL_EXT := .cmd endif run:: RELX_TAR := 0 run:: all $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) $(RELX_REL_CMD) ifdef RELOAD rel:: $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) ping $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) \ eval "io:format(\"~p~n\", [c:lm()])." endif help:: $(verbose) printf "%s\n" "" \ "Relx targets:" \ " run Compile the project, build the release and run it" endif endif # Copyright (c) 2015-2016, Loïc Hoguin # Copyright (c) 2014, M Robert Martin # This file is contributed to erlang.mk and subject to the terms of the ISC License. .PHONY: shell # Configuration. SHELL_ERL ?= erl SHELL_PATHS ?= $(CURDIR)/ebin $(TEST_DIR) SHELL_OPTS ?= ALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS)) # Core targets help:: $(verbose) printf "%s\n" "" \ "Shell targets:" \ " shell Run an erlang shell with SHELL_OPTS or reasonable default" # Plugin-specific targets. $(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep)))) ifneq ($(SKIP_DEPS),) build-shell-deps: else build-shell-deps: $(ALL_SHELL_DEPS_DIRS) | $(ERLANG_MK_TMP)/dep_built $(verbose) set -e; for dep in $(ALL_SHELL_DEPS_DIRS) ; do \ if [ -z "$(strip $(FULL))" ] && [ ! -L $$dep ] && [ -f $(ERLANG_MK_TMP)/dep_built/`basename $$dep` ]; then \ :; \ else \ $(MAKE) -C $$dep IS_DEP=1; \ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $(ERLANG_MK_TMP)/dep_built/$$dep; fi; \ fi \ done endif shell:: build-shell-deps $(gen_verbose) $(SHELL_ERL) -pa $(SHELL_PATHS) $(SHELL_OPTS) # Copyright 2017, Stanislaw Klekot # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: distclean-sphinx sphinx # Configuration. SPHINX_BUILD ?= sphinx-build SPHINX_SOURCE ?= doc SPHINX_CONFDIR ?= SPHINX_FORMATS ?= html SPHINX_DOCTREES ?= $(ERLANG_MK_TMP)/sphinx.doctrees SPHINX_OPTS ?= #sphinx_html_opts = #sphinx_html_output = html #sphinx_man_opts = #sphinx_man_output = man #sphinx_latex_opts = #sphinx_latex_output = latex # Helpers. sphinx_build_0 = @echo " SPHINX" $1; $(SPHINX_BUILD) -N -q sphinx_build_1 = $(SPHINX_BUILD) -N sphinx_build_2 = set -x; $(SPHINX_BUILD) sphinx_build = $(sphinx_build_$(V)) define sphinx.build $(call sphinx_build,$1) -b $1 -d $(SPHINX_DOCTREES) $(if $(SPHINX_CONFDIR),-c $(SPHINX_CONFDIR)) $(SPHINX_OPTS) $(sphinx_$1_opts) -- $(SPHINX_SOURCE) $(call sphinx.output,$1) endef define sphinx.output $(if $(sphinx_$1_output),$(sphinx_$1_output),$1) endef # Targets. ifneq ($(wildcard $(if $(SPHINX_CONFDIR),$(SPHINX_CONFDIR),$(SPHINX_SOURCE))/conf.py),) docs:: sphinx distclean:: distclean-sphinx endif help:: $(verbose) printf "%s\n" "" \ "Sphinx targets:" \ " sphinx Generate Sphinx documentation." \ "" \ "ReST sources and 'conf.py' file are expected in directory pointed by" \ "SPHINX_SOURCE ('doc' by default). SPHINX_FORMATS lists formats to build (only" \ "'html' format is generated by default); target directory can be specified by" \ 'setting sphinx_$${format}_output, for example: sphinx_html_output = output/html' \ "Additional Sphinx options can be set in SPHINX_OPTS." # Plugin-specific targets. sphinx: $(foreach F,$(SPHINX_FORMATS),$(call sphinx.build,$F)) distclean-sphinx: $(gen_verbose) rm -rf $(filter-out $(SPHINX_SOURCE),$(foreach F,$(SPHINX_FORMATS),$(call sphinx.output,$F))) # Copyright (c) 2017, Jean-Sébastien Pédron # This file is contributed to erlang.mk and subject to the terms of the ISC License. .PHONY: show-ERL_LIBS show-ERLC_OPTS show-TEST_ERLC_OPTS show-ERL_LIBS: @echo $(ERL_LIBS) show-ERLC_OPTS: @$(foreach opt,$(ERLC_OPTS) -pa ebin -I include,echo "$(opt)";) show-TEST_ERLC_OPTS: @$(foreach opt,$(TEST_ERLC_OPTS) -pa ebin -I include,echo "$(opt)";) # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. ifeq ($(filter triq,$(DEPS) $(TEST_DEPS)),triq) .PHONY: triq # Targets. tests:: triq define triq_check.erl $(call cover.erl) code:add_pathsa([ "$(call core_native_path,$(CURDIR)/ebin)", "$(call core_native_path,$(DEPS_DIR)/*/ebin)", "$(call core_native_path,$(TEST_DIR))"]), try begin CoverSetup(), Res = case $(1) of all -> [true] =:= lists:usort([triq:check(M) || M <- [$(call comma_list,$(3))]]); module -> triq:check($(2)); function -> triq:check($(2)) end, CoverExport("$(COVER_DATA_DIR)/triq.coverdata"), Res end of true -> halt(0); _ -> halt(1) catch error:undef$(if $V,:Stacktrace) -> io:format("Undefined property or module?~n$(if $V,~p~n)", [$(if $V,Stacktrace)]), halt(0) end. endef ifdef t ifeq (,$(findstring :,$(t))) triq: test-build cover-data-dir $(verbose) $(call erlang,$(call triq_check.erl,module,$(t))) else triq: test-build cover-data-dir $(verbose) echo Testing $(t)/0 $(verbose) $(call erlang,$(call triq_check.erl,function,$(t)())) endif else triq: test-build cover-data-dir $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \ $(wildcard ebin/*.beam) $(call core_find,$(TEST_DIR)/,*.beam)))))) $(gen_verbose) $(call erlang,$(call triq_check.erl,all,undefined,$(MODULES))) endif endif # Copyright (c) 2022, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: xref # Configuration. # We do not use locals_not_used or deprecated_function_calls # because the compiler will error out by default in those # cases with Erlang.mk. Deprecated functions may make sense # in some cases but few libraries define them. We do not # use exports_not_used by default because it hinders more # than it helps library projects such as Cowboy. Finally, # undefined_functions provides little that undefined_function_calls # doesn't already provide, so it's not enabled by default. XREF_CHECKS ?= [undefined_function_calls] # Instead of predefined checks a query can be evaluated # using the Xref DSL. The $q variable is used in that case. # The scope is a list of keywords that correspond to # application directories, being essentially an easy way # to configure which applications to analyze. With: # # - app: . # - apps: $(ALL_APPS_DIRS) # - deps: $(ALL_DEPS_DIRS) # - otp: Built-in Erlang/OTP applications. # # The default is conservative (app) and will not be # appropriate for all types of queries (for example # application_call requires adding all applications # that might be called or they will not be found). XREF_SCOPE ?= app # apps deps otp # If the above is not enough, additional application # directories can be configured. XREF_EXTRA_APP_DIRS ?= # As well as additional non-application directories. XREF_EXTRA_DIRS ?= # Erlang.mk supports -ignore_xref([...]) with forms # {M, F, A} | {F, A} | M, the latter ignoring whole # modules. Ignores can also be provided project-wide. XREF_IGNORE ?= [] # All callbacks may be ignored. Erlang.mk will ignore # them automatically for exports_not_used (unless it # is explicitly disabled by the user). XREF_IGNORE_CALLBACKS ?= # Core targets. help:: $(verbose) printf '%s\n' '' \ 'Xref targets:' \ ' xref Analyze the project using Xref' \ ' xref q=QUERY Evaluate an Xref query' # Plugin-specific targets. define xref.erl {ok, Xref} = xref:start([]), Scope = [$(call comma_list,$(XREF_SCOPE))], AppDirs0 = [$(call comma_list,$(foreach d,$(XREF_EXTRA_APP_DIRS),"$d"))], AppDirs1 = case lists:member(otp, Scope) of false -> AppDirs0; true -> RootDir = code:root_dir(), AppDirs0 ++ [filename:dirname(P) || P <- code:get_path(), lists:prefix(RootDir, P)] end, AppDirs2 = case lists:member(deps, Scope) of false -> AppDirs1; true -> [$(call comma_list,$(foreach d,$(ALL_DEPS_DIRS),"$d"))] ++ AppDirs1 end, AppDirs3 = case lists:member(apps, Scope) of false -> AppDirs2; true -> [$(call comma_list,$(foreach d,$(ALL_APPS_DIRS),"$d"))] ++ AppDirs2 end, AppDirs = case lists:member(app, Scope) of false -> AppDirs3; true -> ["../$(notdir $(CURDIR))"|AppDirs3] end, [{ok, _} = xref:add_application(Xref, AppDir, [{builtins, true}]) || AppDir <- AppDirs], ExtraDirs = [$(call comma_list,$(foreach d,$(XREF_EXTRA_DIRS),"$d"))], [{ok, _} = xref:add_directory(Xref, ExtraDir, [{builtins, true}]) || ExtraDir <- ExtraDirs], ok = xref:set_library_path(Xref, code:get_path() -- (["ebin", "."] ++ AppDirs ++ ExtraDirs)), Checks = case {$1, is_list($2)} of {check, true} -> $2; {check, false} -> [$2]; {query, _} -> [$2] end, FinalRes = [begin IsInformational = case $1 of query -> true; check -> is_tuple(Check) andalso lists:member(element(1, Check), [call, use, module_call, module_use, application_call, application_use]) end, {ok, Res0} = case $1 of check -> xref:analyze(Xref, Check); query -> xref:q(Xref, Check) end, Res = case IsInformational of true -> Res0; false -> lists:filter(fun(R) -> {Mod, InMFA, MFA} = case R of {InMFA0 = {M, _, _}, MFA0} -> {M, InMFA0, MFA0}; {M, _, _} -> {M, R, R} end, Attrs = try Mod:module_info(attributes) catch error:undef -> [] end, InlineIgnores = lists:flatten([ [case V of M when is_atom(M) -> {M, '_', '_'}; {F, A} -> {Mod, F, A}; _ -> V end || V <- Values] || {ignore_xref, Values} <- Attrs]), BuiltinIgnores = [ {eunit_test, wrapper_test_exported_, 0} ], DoCallbackIgnores = case {Check, "$(strip $(XREF_IGNORE_CALLBACKS))"} of {exports_not_used, ""} -> true; {_, "0"} -> false; _ -> true end, CallbackIgnores = case DoCallbackIgnores of false -> []; true -> Behaviors = lists:flatten([ [BL || {behavior, BL} <- Attrs], [BL || {behaviour, BL} <- Attrs] ]), [{Mod, CF, CA} || B <- Behaviors, {CF, CA} <- B:behaviour_info(callbacks)] end, WideIgnores = if is_list($(XREF_IGNORE)) -> [if is_atom(I) -> {I, '_', '_'}; true -> I end || I <- $(XREF_IGNORE)]; true -> [$(XREF_IGNORE)] end, Ignores = InlineIgnores ++ BuiltinIgnores ++ CallbackIgnores ++ WideIgnores, not (lists:member(InMFA, Ignores) orelse lists:member(MFA, Ignores) orelse lists:member({Mod, '_', '_'}, Ignores)) end, Res0) end, case Res of [] -> ok; _ when IsInformational -> case Check of {call, {CM, CF, CA}} -> io:format("Functions that ~s:~s/~b calls:~n", [CM, CF, CA]); {use, {CM, CF, CA}} -> io:format("Function ~s:~s/~b is called by:~n", [CM, CF, CA]); {module_call, CMod} -> io:format("Modules that ~s calls:~n", [CMod]); {module_use, CMod} -> io:format("Module ~s is used by:~n", [CMod]); {application_call, CApp} -> io:format("Applications that ~s calls:~n", [CApp]); {application_use, CApp} -> io:format("Application ~s is used by:~n", [CApp]); _ when $1 =:= query -> io:format("Query ~s returned:~n", [Check]) end, [case R of {{InM, InF, InA}, {M, F, A}} -> io:format("- ~s:~s/~b called by ~s:~s/~b~n", [M, F, A, InM, InF, InA]); {M, F, A} -> io:format("- ~s:~s/~b~n", [M, F, A]); ModOrApp -> io:format("- ~s~n", [ModOrApp]) end || R <- Res], ok; _ -> [case {Check, R} of {undefined_function_calls, {{InM, InF, InA}, {M, F, A}}} -> io:format("Undefined function ~s:~s/~b called by ~s:~s/~b~n", [M, F, A, InM, InF, InA]); {undefined_functions, {M, F, A}} -> io:format("Undefined function ~s:~s/~b~n", [M, F, A]); {locals_not_used, {M, F, A}} -> io:format("Unused local function ~s:~s/~b~n", [M, F, A]); {exports_not_used, {M, F, A}} -> io:format("Unused exported function ~s:~s/~b~n", [M, F, A]); {deprecated_function_calls, {{InM, InF, InA}, {M, F, A}}} -> io:format("Deprecated function ~s:~s/~b called by ~s:~s/~b~n", [M, F, A, InM, InF, InA]); {deprecated_functions, {M, F, A}} -> io:format("Deprecated function ~s:~s/~b~n", [M, F, A]); _ -> io:format("~p: ~p~n", [Check, R]) end || R <- Res], error end end || Check <- Checks], stopped = xref:stop(Xref), case lists:usort(FinalRes) of [ok] -> halt(0); _ -> halt(1) end endef xref: deps app ifdef q $(verbose) $(call erlang,$(call xref.erl,query,"$q"),-pa ebin/) else $(verbose) $(call erlang,$(call xref.erl,check,$(XREF_CHECKS)),-pa ebin/) endif # Copyright (c) 2016, Loïc Hoguin # Copyright (c) 2015, Viktor Söderqvist # This file is part of erlang.mk and subject to the terms of the ISC License. COVER_REPORT_DIR ?= cover COVER_DATA_DIR ?= $(COVER_REPORT_DIR) ifdef COVER COVER_APPS ?= $(notdir $(ALL_APPS_DIRS)) COVER_DEPS ?= COVER_EXCLUDE_MODS ?= endif # Code coverage for Common Test. ifdef COVER ifdef CT_RUN ifneq ($(wildcard $(TEST_DIR)),) test-build:: $(TEST_DIR)/ct.cover.spec $(TEST_DIR)/ct.cover.spec: cover-data-dir $(gen_verbose) printf "%s\n" \ "{incl_app, '$(PROJECT)', details}." \ "{incl_dirs, '$(PROJECT)', [\"$(call core_native_path,$(CURDIR)/ebin)\" \ $(foreach a,$(COVER_APPS),$(comma) \"$(call core_native_path,$(APPS_DIR)/$a/ebin)\") \ $(foreach d,$(COVER_DEPS),$(comma) \"$(call core_native_path,$(DEPS_DIR)/$d/ebin)\")]}." \ '{export,"$(call core_native_path,$(abspath $(COVER_DATA_DIR))/ct.coverdata)"}.' \ "{excl_mods, '$(PROJECT)', [$(call comma_list,$(COVER_EXCLUDE_MODS))]}." > $@ CT_RUN += -cover $(TEST_DIR)/ct.cover.spec endif endif endif # Code coverage for other tools. ifdef COVER define cover.erl CoverSetup = fun() -> Dirs = ["$(call core_native_path,$(CURDIR)/ebin)" $(foreach a,$(COVER_APPS),$(comma) "$(call core_native_path,$(APPS_DIR)/$a/ebin)") $(foreach d,$(COVER_DEPS),$(comma) "$(call core_native_path,$(DEPS_DIR)/$d/ebin)")], Excludes = [$(call comma_list,$(foreach e,$(COVER_EXCLUDE_MODS),"$e"))], [case file:list_dir(Dir) of {error, enotdir} -> false; {error, _} -> halt(2); {ok, Files} -> BeamFiles = [filename:join(Dir, File) || File <- Files, not lists:member(filename:basename(File, ".beam"), Excludes), filename:extension(File) =:= ".beam"], case cover:compile_beam(BeamFiles) of {error, _} -> halt(1); _ -> true end end || Dir <- Dirs] end, CoverExport = fun(Filename) -> cover:export(Filename) end, endef else define cover.erl CoverSetup = fun() -> ok end, CoverExport = fun(_) -> ok end, endef endif # Core targets ifdef COVER ifneq ($(COVER_REPORT_DIR),) tests:: $(verbose) $(MAKE) --no-print-directory cover-report endif cover-data-dir: | $(COVER_DATA_DIR) $(COVER_DATA_DIR): $(verbose) mkdir -p $(COVER_DATA_DIR) else cover-data-dir: endif clean:: coverdata-clean ifneq ($(COVER_REPORT_DIR),) distclean:: cover-report-clean endif help:: $(verbose) printf "%s\n" "" \ "Cover targets:" \ " cover-report Generate a HTML coverage report from previously collected" \ " cover data." \ " all.coverdata Merge all coverdata files into all.coverdata." \ "" \ "If COVER=1 is set, coverage data is generated by the targets eunit and ct. The" \ "target tests additionally generates a HTML coverage report from the combined" \ "coverdata files from each of these testing tools. HTML reports can be disabled" \ "by setting COVER_REPORT_DIR to empty." # Plugin specific targets COVERDATA = $(filter-out $(COVER_DATA_DIR)/all.coverdata,$(wildcard $(COVER_DATA_DIR)/*.coverdata)) .PHONY: coverdata-clean coverdata-clean: $(gen_verbose) rm -f $(COVER_DATA_DIR)/*.coverdata $(TEST_DIR)/ct.cover.spec # Merge all coverdata files into one. define cover_export.erl $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) cover:export("$(COVER_DATA_DIR)/$@"), halt(0). endef all.coverdata: $(COVERDATA) cover-data-dir $(gen_verbose) $(call erlang,$(cover_export.erl)) # These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to # empty if you want the coverdata files but not the HTML report. ifneq ($(COVER_REPORT_DIR),) .PHONY: cover-report-clean cover-report cover-report-clean: $(gen_verbose) rm -rf $(COVER_REPORT_DIR) ifneq ($(COVER_REPORT_DIR),$(COVER_DATA_DIR)) $(if $(shell ls -A $(COVER_DATA_DIR)/),,$(verbose) rmdir $(COVER_DATA_DIR)) endif ifeq ($(COVERDATA),) cover-report: else # Modules which include eunit.hrl always contain one line without coverage # because eunit defines test/0 which is never called. We compensate for this. EUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \ grep -H -e '^\s*-include.*include/eunit\.hrl"' src/*.erl \ | sed "s/^src\/\(.*\)\.erl:.*/'\1'/" | uniq)) define cover_report.erl $(foreach f,$(COVERDATA),cover:import("$(f)") == ok orelse halt(1),) Ms = cover:imported_modules(), [cover:analyse_to_file(M, "$(COVER_REPORT_DIR)/" ++ atom_to_list(M) ++ ".COVER.html", [html]) || M <- Ms], Report = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms], EunitHrlMods = [$(EUNIT_HRL_MODS)], Report1 = [{M, {Y, case lists:member(M, EunitHrlMods) of true -> N - 1; false -> N end}} || {M, {Y, N}} <- Report], TotalY = lists:sum([Y || {_, {Y, _}} <- Report1]), TotalN = lists:sum([N || {_, {_, N}} <- Report1]), Perc = fun(Y, N) -> case Y + N of 0 -> 100; S -> round(100 * Y / S) end end, TotalPerc = Perc(TotalY, TotalN), {ok, F} = file:open("$(COVER_REPORT_DIR)/index.html", [write]), io:format(F, "~n" "~n" "Coverage report~n" "~n", []), io:format(F, "

Coverage

~n

Total: ~p%

~n", [TotalPerc]), io:format(F, "~n", []), [io:format(F, "" "~n", [M, M, Perc(Y, N)]) || {M, {Y, N}} <- Report1], How = "$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))", Date = "$(shell date -u "+%Y-%m-%dT%H:%M:%SZ")", io:format(F, "
ModuleCoverage
~p~p%
~n" "

Generated using ~s and erlang.mk on ~s.

~n" "", [How, Date]), halt(). endef cover-report: $(verbose) mkdir -p $(COVER_REPORT_DIR) $(gen_verbose) $(call erlang,$(cover_report.erl)) endif endif # ifneq ($(COVER_REPORT_DIR),) # Copyright (c) 2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. .PHONY: sfx ifdef RELX_REL ifdef SFX # Configuration. SFX_ARCHIVE ?= $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/$(RELX_REL_NAME)-$(RELX_REL_VSN).tar.gz SFX_OUTPUT_FILE ?= $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME).run # Core targets. rel:: sfx # Plugin-specific targets. define sfx_stub #!/bin/sh TMPDIR=`mktemp -d` ARCHIVE=`awk '/^__ARCHIVE_BELOW__$$/ {print NR + 1; exit 0;}' $$0` FILENAME=$$(basename $$0) REL=$${FILENAME%.*} tail -n+$$ARCHIVE $$0 | tar -xzf - -C $$TMPDIR $$TMPDIR/bin/$$REL console RET=$$? rm -rf $$TMPDIR exit $$RET __ARCHIVE_BELOW__ endef sfx: $(verbose) $(call core_render,sfx_stub,$(SFX_OUTPUT_FILE)) $(gen_verbose) cat $(SFX_ARCHIVE) >> $(SFX_OUTPUT_FILE) $(verbose) chmod +x $(SFX_OUTPUT_FILE) endif endif # Copyright (c) 2013-2017, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. # External plugins. DEP_PLUGINS ?= $(foreach p,$(DEP_PLUGINS),\ $(eval $(if $(findstring /,$p),\ $(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\ $(call core_dep_plugin,$p/plugins.mk,$p)))) help:: help-plugins help-plugins:: $(verbose) : # Copyright (c) 2013-2015, Loïc Hoguin # Copyright (c) 2015-2016, Jean-Sébastien Pédron # This file is part of erlang.mk and subject to the terms of the ISC License. # Fetch dependencies recursively (without building them). .PHONY: fetch-deps fetch-doc-deps fetch-rel-deps fetch-test-deps \ fetch-shell-deps .PHONY: $(ERLANG_MK_RECURSIVE_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST) fetch-deps: $(ERLANG_MK_RECURSIVE_DEPS_LIST) fetch-doc-deps: $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) fetch-rel-deps: $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) fetch-test-deps: $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) fetch-shell-deps: $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST) ifneq ($(SKIP_DEPS),) $(ERLANG_MK_RECURSIVE_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): $(verbose) :> $@ else # By default, we fetch "normal" dependencies. They are also included no # matter the type of requested dependencies. # # $(ALL_DEPS_DIRS) includes $(BUILD_DEPS). $(ERLANG_MK_RECURSIVE_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_DOC_DEPS_DIRS) $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_REL_DEPS_DIRS) $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_TEST_DEPS_DIRS) $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_SHELL_DEPS_DIRS) # Allow to use fetch-deps and $(DEP_TYPES) to fetch multiple types of # dependencies with a single target. ifneq ($(filter doc,$(DEP_TYPES)),) $(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_DOC_DEPS_DIRS) endif ifneq ($(filter rel,$(DEP_TYPES)),) $(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_REL_DEPS_DIRS) endif ifneq ($(filter test,$(DEP_TYPES)),) $(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_TEST_DEPS_DIRS) endif ifneq ($(filter shell,$(DEP_TYPES)),) $(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_SHELL_DEPS_DIRS) endif ERLANG_MK_RECURSIVE_TMP_LIST := $(abspath $(ERLANG_MK_TMP)/recursive-tmp-deps-$(shell echo $$PPID).log) $(ERLANG_MK_RECURSIVE_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \ $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): | $(ERLANG_MK_TMP) ifeq ($(IS_APP)$(IS_DEP),) $(verbose) rm -f $(ERLANG_MK_RECURSIVE_TMP_LIST) endif $(verbose) touch $(ERLANG_MK_RECURSIVE_TMP_LIST) $(verbose) set -e; for dep in $^ ; do \ if ! grep -qs ^$$dep$$ $(ERLANG_MK_RECURSIVE_TMP_LIST); then \ echo $$dep >> $(ERLANG_MK_RECURSIVE_TMP_LIST); \ if grep -qs -E "^[[:blank:]]*include[[:blank:]]+(erlang\.mk|.*/erlang\.mk|.*ERLANG_MK_FILENAME.*)$$" \ $$dep/GNUmakefile $$dep/makefile $$dep/Makefile; then \ $(MAKE) -C $$dep fetch-deps \ IS_DEP=1 \ ERLANG_MK_RECURSIVE_TMP_LIST=$(ERLANG_MK_RECURSIVE_TMP_LIST); \ fi \ fi \ done ifeq ($(IS_APP)$(IS_DEP),) $(verbose) sort < $(ERLANG_MK_RECURSIVE_TMP_LIST) | \ uniq > $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $(verbose) mv $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@ $(verbose) rm $(ERLANG_MK_RECURSIVE_TMP_LIST) endif endif # ifneq ($(SKIP_DEPS),) # List dependencies recursively. .PHONY: list-deps list-doc-deps list-rel-deps list-test-deps \ list-shell-deps list-deps: $(ERLANG_MK_RECURSIVE_DEPS_LIST) list-doc-deps: $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) list-rel-deps: $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) list-test-deps: $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) list-shell-deps: $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST) list-deps list-doc-deps list-rel-deps list-test-deps list-shell-deps: $(verbose) cat $^ # Query dependencies recursively. .PHONY: query-deps query-doc-deps query-rel-deps query-test-deps \ query-shell-deps QUERY ?= name fetch_method repo version define query_target $1: $2 clean-tmp-query.log ifeq ($(IS_APP)$(IS_DEP),) $(verbose) rm -f $4 endif $(verbose) $(foreach dep,$3,\ echo $(PROJECT): $(foreach q,$(QUERY),$(call query_$(q),$(dep))) >> $4 ;) $(if $(filter-out query-deps,$1),,\ $(verbose) set -e; for dep in $3 ; do \ if grep -qs ^$$$$dep$$$$ $(ERLANG_MK_TMP)/query.log; then \ :; \ else \ echo $$$$dep >> $(ERLANG_MK_TMP)/query.log; \ $(MAKE) -C $(DEPS_DIR)/$$$$dep $$@ QUERY="$(QUERY)" IS_DEP=1 || true; \ fi \ done) ifeq ($(IS_APP)$(IS_DEP),) $(verbose) touch $4 $(verbose) cat $4 endif endef clean-tmp-query.log: ifeq ($(IS_DEP),) $(verbose) rm -f $(ERLANG_MK_TMP)/query.log endif $(eval $(call query_target,query-deps,$(ERLANG_MK_RECURSIVE_DEPS_LIST),$(BUILD_DEPS) $(DEPS),$(ERLANG_MK_QUERY_DEPS_FILE))) $(eval $(call query_target,query-doc-deps,$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST),$(DOC_DEPS),$(ERLANG_MK_QUERY_DOC_DEPS_FILE))) $(eval $(call query_target,query-rel-deps,$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST),$(REL_DEPS),$(ERLANG_MK_QUERY_REL_DEPS_FILE))) $(eval $(call query_target,query-test-deps,$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST),$(TEST_DEPS),$(ERLANG_MK_QUERY_TEST_DEPS_FILE))) $(eval $(call query_target,query-shell-deps,$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST),$(SHELL_DEPS),$(ERLANG_MK_QUERY_SHELL_DEPS_FILE))) cowlib-2.19.0/include/000077500000000000000000000000001523166326600145175ustar00rootroot00000000000000cowlib-2.19.0/include/cow_inline.hrl000066400000000000000000000532621523166326600173640ustar00rootroot00000000000000%% Copyright (c) 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. cowlib-2.19.0/include/cow_parse.hrl000066400000000000000000000063501523166326600172140ustar00rootroot00000000000000%% Copyright (c) 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. cowlib-2.19.0/src/000077500000000000000000000000001523166326600136635ustar00rootroot00000000000000cowlib-2.19.0/src/cow_base64url.erl000066400000000000000000000045351523166326600170550ustar00rootroot00000000000000%% Copyright (c) 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. cowlib-2.19.0/src/cow_capsule.erl000066400000000000000000000052641523166326600167020ustar00rootroot00000000000000%% Copyright (c) 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_capsule). %% Parsing. -export([parse/1]). %% Building. -export([wt_drain_session/0]). -export([wt_close_session/2]). -type capsule() :: wt_drain_session | {wt_close_session, cow_http3:wt_app_error_code(), binary()}. %% Parsing. -spec parse(binary()) -> {ok, capsule(), binary()} | {ok, binary()} %% Unknown capsule gets skipped. | more | {skip, non_neg_integer()} %% Unknown capsule; remaining length to skip. | error. %% @todo Handle DATAGRAM capsules. {datagram, binary()} parse(<<2:2, 16#78ae:30, 0, Rest/bits>>) -> {ok, wt_drain_session, Rest}; parse(<<1:2, 16#2843:14, Rest0/bits>>) when byte_size(Rest0) >= 5 -> LenOrError = case Rest0 of <<0:2, Len0:6, Rest1/bits>> -> {Len0, Rest1}; <<1:2, Len0:14, Rest1/bits>> when Len0 =< 1028 -> {Len0, Rest1}; %% AppCode is 4 bytes and AppMsg is up to 1024 bytes. _ -> error end, case LenOrError of {Len1, Rest2} -> AppMsgLen = Len1 - 4, case Rest2 of <> -> {ok, {wt_close_session, AppCode, AppMsg}, Rest}; _ -> more end; error -> error end; parse(<<>>) -> more; %% Skip unknown capsules. parse(Data) -> %% @todo This can use maybe_expr in OTP-25+. case cow_http3:parse_int(Data) of more -> more; {_Type, Rest0} -> case cow_http3:parse_int(Rest0) of more -> more; {Len, Rest1} -> case Rest1 of <<_:Len/unit:8, Rest>> -> {ok, Rest}; _ -> {skip, Len - byte_size(Rest1)} end end end. %% Building. -spec wt_drain_session() -> binary(). %% @todo Where should I put capsules? wt_drain_session() -> <<2:2, 16#78ae:30, 0>>. -spec wt_close_session(cow_http3:wt_app_error_code(), iodata()) -> iodata(). wt_close_session(AppCode, <<>>) -> <<1:2, 16#2843:14, 4, AppCode:32>>; wt_close_session(AppCode, AppMsg) -> Len = 4 + iolist_size(AppMsg), [<<1:2, 16#2843:14>>, cow_http3:encode_int(Len), <>, AppMsg]. cowlib-2.19.0/src/cow_cookie.erl000066400000000000000000000415451523166326600165210ustar00rootroot00000000000000%% Copyright (c) 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_cookie/2]). -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]). -type parse_opts() :: #{max_cookies => non_neg_integer()}. -export_type([parse_opts/0]). -include("cow_inline.hrl"). -ifdef(TEST). -include_lib("stdlib/include/assert.hrl"). -endif. %% Cookie header. -spec parse_cookie(binary()) -> [{binary(), binary()}]. parse_cookie(Cookie) -> parse_cookie(Cookie, #{}). -spec parse_cookie(binary(), parse_opts()) -> [{binary(), binary()}]. parse_cookie(Cookie, Opts) -> Max = maps:get(max_cookies, Opts, 100), parse_cookie(Cookie, [], Max). parse_cookie(<<>>, Acc, _) -> lists:reverse(Acc); parse_cookie(<< $\s, Rest/binary >>, Acc, Max) -> parse_cookie(Rest, Acc, Max); parse_cookie(<< $\t, Rest/binary >>, Acc, Max) -> parse_cookie(Rest, Acc, Max); parse_cookie(<< $,, Rest/binary >>, Acc, Max) -> parse_cookie(Rest, Acc, Max); parse_cookie(<< $;, Rest/binary >>, Acc, Max) -> parse_cookie(Rest, Acc, Max); parse_cookie(_, Acc, Max) when length(Acc) =:= Max -> error(limit_reached); parse_cookie(Cookie, Acc, Max) -> parse_cookie_name(Cookie, Acc, <<>>, Max). 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, Max) -> parse_cookie_value(Rest, Acc, Name, <<>>, Max); parse_cookie_name(<< $,, _/binary >>, _, _, _) -> error(badarg); parse_cookie_name(<< $;, Rest/binary >>, Acc, Name, Max) -> parse_cookie(Rest, [{<<>>, parse_cookie_trim(Name)}|Acc], Max); 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, Max) -> parse_cookie_name(Rest, Acc, << Name/binary, C >>, Max). parse_cookie_value(<<>>, Acc, Name, Value, _) -> lists:reverse([{Name, parse_cookie_trim(Value)}|Acc]); parse_cookie_value(<< $;, Rest/binary >>, Acc, Name, Value, Max) -> parse_cookie(Rest, [{Name, parse_cookie_trim(Value)}|Acc], Max); 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, Max) -> parse_cookie_value(Rest, Acc, Name, << Value/binary, C >>, Max). 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() -> ?assertError(badarg, parse_cookie(V)) end} || V <- Tests]. parse_cookie_max_cookies_test() -> Pair = <<"a=b">>, %% 100 pairs accepted by default. OK = iolist_to_binary(lists:join(<<"; ">>, lists:duplicate(100, Pair))), Cookies = parse_cookie(OK), 100 = length(Cookies), %% 101st pair is rejected. Over = iolist_to_binary([OK, <<"; ">>, Pair]), ?assertError(limit_reached, parse_cookie(Over)), %% Custom limit: at most N pairs; exceeding errors (no truncation). [{<<"a">>, <<"b">>}] = parse_cookie(Pair, #{max_cookies => 1}), Two = <>, ?assertError(limit_reached, parse_cookie(Two, #{max_cookies => 1})), ?assertError(limit_reached, parse_cookie(Pair, #{max_cookies => 0})), ok. -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, Domain0}|Tail]) -> Domain = iolist_to_binary(Domain0), nomatch = binary:match(Domain, <<$;>>), [<<"; 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, Path0}|Tail]) -> Path = iolist_to_binary(Path0), nomatch = binary:match(Path, <<$;>>), [<<"; 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}), ?assertError({badarg, {max_age, -111}}, F(<<"Customer">>, <<"WILE_E_COYOTE">>, #{max_age => -111})), [<<"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]. setcookie_attr_failures_test_() -> F = fun(Opts) -> try setcookie(<<"Name">>, <<"Value">>, Opts) of _ -> false catch _:_ -> true end end, Tests = [ #{path => <<"/a; Secure">>}, #{domain => <<"ex.com; Path=/">>}, #{path => [<<"/a">>, <<";HttpOnly">>]} ], [{iolist_to_binary(io_lib:format("~p failure", [O])), fun() -> true = F(O) end} || O <- Tests]. -endif. cowlib-2.19.0/src/cow_date.erl000066400000000000000000000426761523166326600161730ustar00rootroot00000000000000%% Copyright (c) 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). cowlib-2.19.0/src/cow_deflate.erl000066400000000000000000000030241523166326600166420ustar00rootroot00000000000000%% Copyright (c) jdamanalo %% Copyright (c) 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_deflate). -export([inflate/3]). -spec inflate(zlib:zstream(), iodata(), non_neg_integer() | infinity) -> {ok, binary()} | {error, data_error | size_error}. inflate(Z, Data, Limit) -> try {Status, Output} = zlib:safeInflate(Z, Data), do_inflate(Z, iolist_size(Output), Limit, Status, [Output]) catch error:data_error -> {error, data_error} end. do_inflate(_, Size, Limit, _, _) when Size > Limit -> {error, size_error}; do_inflate(Z, Size0, Limit, continue, Acc) -> {Status, Output} = zlib:safeInflate(Z, []), Size = Size0 + iolist_size(Output), do_inflate(Z, Size, Limit, Status, [Output | Acc]); do_inflate(_, _, _, finished, Acc) -> {ok, iolist_to_binary(lists:reverse(Acc))}. cowlib-2.19.0/src/cow_hpack.erl000066400000000000000000001377501523166326600163420ustar00rootroot00000000000000%% Copyright (c) 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([decode/3]). -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 decoder_opts() :: #{ max_headers => non_neg_integer() }. -export_type([decoder_opts/0]). -type encoder_opts() :: #{ huffman => boolean() }. -export_type([encoder_opts/0]). -ifdef(TEST). -include_lib("stdlib/include/assert.hrl"). -include_lib("proper/include/proper.hrl"). -endif. -include("cow_hpack_common.hrl"). %% 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()}. decode(Data, State) -> decode(Data, State, #{}). -spec decode(binary(), state(), decoder_opts()) -> {cow_http:headers(), 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}, Opts) -> {MaxSize, Rest2} = dec_int5(Rest), if MaxSize =< ConfigMaxSize -> State2 = table_update_size(MaxSize, State), decode(Rest2, State2, Opts) end; decode(Data, State, Opts0) -> Opts = #{ max_headers => maps:get(max_headers, Opts0, 100) }, decode(Data, State, [], Opts). decode(<<>>, State, Acc, _) -> {lists:reverse(Acc), State}; decode(_, _, Acc, Opts) when length(Acc) >= map_get(max_headers, Opts) -> error(max_headers); %% Indexed header field representation. decode(<< 1:1, Rest/bits >>, State, Acc, Opts) -> dec_indexed(Rest, State, Acc, Opts); %% Literal header field with incremental indexing: new name. decode(<< 0:1, 1:1, 0:6, Rest/bits >>, State, Acc, Opts) -> dec_lit_index_new_name(Rest, State, Acc, Opts); %% Literal header field with incremental indexing: indexed name. decode(<< 0:1, 1:1, Rest/bits >>, State, Acc, Opts) -> dec_lit_index_indexed_name(Rest, State, Acc, Opts); %% Literal header field without indexing: new name. decode(<< 0:8, Rest/bits >>, State, Acc, Opts) -> dec_lit_no_index_new_name(Rest, State, Acc, Opts); %% Literal header field without indexing: indexed name. decode(<< 0:4, Rest/bits >>, State, Acc, Opts) -> dec_lit_no_index_indexed_name(Rest, State, Acc, Opts); %% 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, Opts) -> dec_lit_no_index_new_name(Rest, State, Acc, Opts); %% Literal header field never indexed: indexed name. %% @todo Keep track of "never indexed" headers. decode(<< 0:3, 1:1, Rest/bits >>, State, Acc, Opts) -> dec_lit_no_index_indexed_name(Rest, State, Acc, Opts). %% 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, Opts) -> {Name, Value} = table_get(127 + Int, State), decode(Rest, State, [{Name, Value}|Acc], Opts); dec_indexed(<<2#1111111:7, Rest0/bits>>, State, Acc, Opts) -> {Index, Rest} = dec_big_int(Rest0, 127, 0), {Name, Value} = table_get(Index, State), decode(Rest, State, [{Name, Value}|Acc], Opts); dec_indexed(<>, State, Acc, Opts) -> {Name, Value} = table_get(Index, State), decode(Rest, State, [{Name, Value}|Acc], Opts). %% Literal header field with incremental indexing. dec_lit_index_new_name(Rest, State, Acc, Opts) -> {Name, Rest2} = dec_str(Rest), dec_lit_index(Rest2, State, Acc, Opts, 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, Opts) -> Name = table_get_name(63 + Int, State), dec_lit_index(Rest, State, Acc, Opts, Name); dec_lit_index_indexed_name(<<2#111111:6, Rest0/bits>>, State, Acc, Opts) -> {Index, Rest} = dec_big_int(Rest0, 63, 0), Name = table_get_name(Index, State), dec_lit_index(Rest, State, Acc, Opts, Name); dec_lit_index_indexed_name(<>, State, Acc, Opts) -> Name = table_get_name(Index, State), dec_lit_index(Rest, State, Acc, Opts, Name). dec_lit_index(Rest, State, Acc, Opts, Name) -> {Value, Rest2} = dec_str(Rest), State2 = table_insert({Name, Value}, State), decode(Rest2, State2, [{Name, Value}|Acc], Opts). %% Literal header field without indexing. dec_lit_no_index_new_name(Rest, State, Acc, Opts) -> {Name, Rest2} = dec_str(Rest), dec_lit_no_index(Rest2, State, Acc, Opts, 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, Opts) -> Name = table_get_name(15 + Int, State), dec_lit_no_index(Rest, State, Acc, Opts, Name); dec_lit_no_index_indexed_name(<<2#1111:4, Rest0/bits>>, State, Acc, Opts) -> {Index, Rest} = dec_big_int(Rest0, 15, 0), Name = table_get_name(Index, State), dec_lit_no_index(Rest, State, Acc, Opts, Name); dec_lit_no_index_indexed_name(<>, State, Acc, Opts) -> Name = table_get_name(Index, State), dec_lit_no_index(Rest, State, Acc, Opts, Name). dec_lit_no_index(Rest, State, Acc, Opts, Name) -> {Value, Rest2} = dec_str(Rest), decode(Rest2, State, [{Name, Value}|Acc], Opts). %% @todo Literal header field never indexed. %% Decode a string. dec_str(<<0:1, 2#1111111:7, Rest0/bits>>) -> {Length, Rest1} = dec_big_int(Rest0, 127, 0), <> = Rest1, {maybe_copy(Str0), Rest}; dec_str(<<0:1, Length:7, Rest0/bits>>) -> <> = Rest0, {maybe_copy(Str0), 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, <<>>). %% Heap binaries (<=64 bytes) are already detached by the BEAM. %% Larger sub-binaries may retain the whole header block; copy those. maybe_copy(Bin) when byte_size(Bin) =< 64 -> Bin; maybe_copy(Bin) -> binary:copy(Bin). -ifdef(TEST). %% Test case extracted from h2spec. decode_reject_eos_test() -> ?assertError(_, decode(<<16#0085f2b24a84ff874951fffffffa7f:120>>)), ok. decode_reject_very_big_int_test() -> VeryBigInt0 = binary:copy(<<16#ff>>, 32767), VeryBigInt = <>, garbage_collect(), Prev = process_flag(max_heap_size, #{size => 10_000, kill => true}), ?assertError(_, decode(VeryBigInt)), process_flag(max_heap_size, Prev), ok. decode_lit_index_dynamic_name_test() -> %% Fill the dynamic table with two entries: %% x-name-a: v1, then x-name-b: v1. {_, State0} = decode(<< 16#4008782d6e616d652d61027631:104, 16#4008782d6e616d652d62027631:104 >>), #state{dyn_table=[ {42,{<<"x-name-b">>, <<"v1">>}}, {42,{<<"x-name-a">>, <<"v1">>}}]} = State0, %% Literal header with incremental indexing, name at index 63. {Headers, State} = decode(<< 16#7f00027632:40 >>, State0), Headers = [{<<"x-name-a">>, <<"v2">>}], #state{dyn_table=[ {42,{<<"x-name-a">>, <<"v2">>}}, {42,{<<"x-name-b">>, <<"v1">>}}, {42,{<<"x-name-a">>, <<"v1">>}}]} = State, ok. %% Raw (non-Huffman) literals larger than the heap-binary threshold %% (64 bytes) must not keep a reference to the header block binary. %% Smaller slices are copied by the BEAM automatically; this test %% uses >64-byte name/value so referenced_byte_size exposes retention. decode_raw_str_no_subbinary_retention_test() -> Name = binary:copy(<<"n">>, 80), Value = binary:copy(<<"v">>, 80), %% Literal with incremental indexing, new name, raw encoding. Enc0 = iolist_to_binary([ <<0:1, 1:1, 0:6>>, enc_str(Name, no_huffman), enc_str(Value, no_huffman) ]), %% Make Enc a sub-binary of a much larger refc binary. Name/value %% are >64 bytes so without binary:copy they keep referenced_byte_size %% equal to the large parent. (Heap binaries <=64 are always copied.) Large = <>, 50000))/binary>>, <> = Large, true = binary:referenced_byte_size(Enc) > byte_size(Enc), {[{Name, Value}], #state{dyn_table=[{_, {N, V}}]}} = decode(Enc), true = byte_size(N) =:= binary:referenced_byte_size(N), true = byte_size(V) =:= binary:referenced_byte_size(V), 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, <<>>), ?assertError(_, decode( iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4803333037c1c0bf:64>>]), State2)), ?assertError(_, 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, encoder_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. -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. cowlib-2.19.0/src/cow_hpack_common.hrl000066400000000000000000000642671523166326600177170ustar00rootroot00000000000000%% Copyright (c) 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 prefixed integer and the string formats are common %% to both HPACK and QPACK. They are included directly in %% each module in order to avoid fully-qualified calls and %% slightly improve performance. %% %% Some functions are only used in one or the other even %% though the format is the same. In that case the functions %% can be found in the relevant module. %% %% Any tests relevant to these functions should be added to %% cow_hpack since HPACK is where these originate from. %% Prefix decoding. %% %% 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. %% %% We hard limit indefinite length integers to 38bits. The maximum %% possible value is large enough for normal use cases and does %% not produce an Erlang big int. dec_int5(<<2#11111:5, Rest/bits>>) -> dec_big_int(Rest, 31, 0); dec_int5(<>) -> {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) when M < 32 -> dec_big_int(Rest, Int + (Value bsl M), M + 7). %% Prefix encoding. enc_int5(Int, Prefix) when Int < 31 -> <>; enc_int5(Int, Prefix) -> enc_big_int(Int - 31, <>). enc_int6(Int, Prefix) when Int < 63 -> <>; enc_int6(Int, Prefix) -> enc_big_int(Int - 63, <>). enc_int7(Int, Prefix) when Int < 127 -> <>; enc_int7(Int, Prefix) -> enc_big_int(Int - 127, <>). enc_big_int(Int, Acc) when Int < 128 -> <>; enc_big_int(Int, Acc) -> enc_big_int(Int bsr 7, <>). %% String decoding. %% %% 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"). %% String encoding. 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 -> <>; 2 -> <>; 3 -> <>; 4 -> <>; 5 -> <>; 6 -> <>; 7 -> <>; 0 -> Acc end; enc_huffman(<<0, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<1, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<2, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<3, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<4, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<5, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<6, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<7, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<8, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<9, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<10, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<11, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<12, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<13, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<14, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<15, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<16, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<17, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<18, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<19, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<20, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<21, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<22, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<23, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<24, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<25, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<26, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<27, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<28, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<29, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<30, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<31, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<32, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<33, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<34, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<35, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<36, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<37, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<38, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<39, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<40, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<41, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<42, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<43, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<44, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<45, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<46, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<47, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<48, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<49, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<50, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<51, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<52, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<53, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<54, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<55, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<56, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<57, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<58, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<59, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<60, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<61, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<62, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<63, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<64, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<65, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<66, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<67, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<68, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<69, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<70, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<71, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<72, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<73, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<74, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<75, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<76, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<77, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<78, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<79, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<80, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<81, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<82, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<83, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<84, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<85, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<86, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<87, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<88, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<89, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<90, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<91, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<92, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<93, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<94, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<95, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<96, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<97, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<98, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<99, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<100, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<101, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<102, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<103, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<104, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<105, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<106, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<107, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<108, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<109, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<110, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<111, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<112, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<113, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<114, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<115, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<116, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<117, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<118, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<119, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<120, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<121, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<122, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<123, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<124, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<125, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<126, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<127, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<128, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<129, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<130, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<131, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<132, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<133, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<134, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<135, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<136, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<137, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<138, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<139, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<140, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<141, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<142, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<143, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<144, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<145, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<146, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<147, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<148, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<149, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<150, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<151, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<152, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<153, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<154, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<155, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<156, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<157, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<158, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<159, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<160, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<161, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<162, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<163, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<164, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<165, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<166, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<167, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<168, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<169, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<170, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<171, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<172, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<173, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<174, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<175, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<176, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<177, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<178, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<179, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<180, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<181, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<182, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<183, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<184, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<185, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<186, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<187, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<188, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<189, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<190, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<191, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<192, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<193, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<194, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<195, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<196, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<197, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<198, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<199, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<200, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<201, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<202, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<203, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<204, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<205, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<206, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<207, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<208, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<209, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<210, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<211, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<212, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<213, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<214, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<215, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<216, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<217, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<218, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<219, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<220, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<221, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<222, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<223, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<224, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<225, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<226, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<227, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<228, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<229, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<230, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<231, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<232, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<233, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<234, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<235, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<236, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<237, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<238, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<239, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<240, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<241, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<242, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<243, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<244, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<245, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<246, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<247, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<248, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<249, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<250, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<251, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<252, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<253, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<254, R/bits>>, A) -> enc_huffman(R, <>); enc_huffman(<<255, R/bits>>, A) -> enc_huffman(R, <>). cowlib-2.19.0/src/cow_hpack_dec_huffman_lookup.hrl000066400000000000000000007107341523166326600222540ustar00rootroot00000000000000%% Copyright (c) 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. cowlib-2.19.0/src/cow_http.erl000066400000000000000000000435221523166326600162240ustar00rootroot00000000000000%% Copyright (c) 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 contains functions and types common %% to all or most HTTP versions. -module(cow_http). %% The HTTP/1 functions have been moved to cow_http1. %% In order to remain backward compatible we redirect %% calls to cow_http1. The type version() was moved %% and no fallback is provided. %% %% @todo Remove the aliases in Cowlib 3.0. -export([parse_request_line/1]). -export([parse_status_line/1]). -export([status_to_integer/1]). -export([parse_headers/1]). -export([parse_version/1]). -export([request/4]). -export([response/3]). -export([headers/1]). -export([version/1]). %% Functions used by HTTP/2+. -export([format_semantic_error/1]). -export([merge_pseudo_headers/2]). -export([process_headers/5]). -export([remove_http1_headers/1]). -export([parse_fullpath/1]). %% Validation functions. -export([ensure_scheme/1]). -export([ensure_token/1]). %% Types used by all versions of HTTP. -type status() :: 100..999. -export_type([status/0]). -type headers() :: [{binary(), iodata()}]. -export_type([headers/0]). %% Types used by HTTP/2+. -type pseudo_headers() :: #{} %% Trailers | #{ %% Responses. status := cow_http:status() } | #{ %% Normal CONNECT requests. method := binary(), authority := binary() } | #{ %% Extended CONNECT requests. method := binary(), scheme := binary(), authority := binary(), path := binary(), protocol := binary() } | #{ %% Other requests. method := binary(), scheme := binary(), authority => binary(), path := binary() }. -export_type([pseudo_headers/0]). -type fin() :: fin | nofin. -export_type([fin/0]). -include("cow_parse.hrl"). -ifdef(TEST). -include_lib("stdlib/include/assert.hrl"). -endif. %% HTTP/1 function aliases. -spec parse_request_line(binary()) -> {binary(), binary(), cow_http1:version(), binary()}. parse_request_line(Data) -> cow_http1:parse_request_line(Data). -spec parse_status_line(binary()) -> {cow_http1:version(), status(), binary(), binary()}. parse_status_line(Data) -> cow_http1:parse_status_line(Data). -spec status_to_integer(status() | binary()) -> status(). status_to_integer(Status) -> cow_http1:status_to_integer(Status). -spec parse_headers(binary()) -> {[{binary(), binary()}], binary()}. parse_headers(Data) -> cow_http1:parse_headers(Data). -spec parse_version(binary()) -> cow_http1:version(). parse_version(Data) -> cow_http1:parse_version(Data). -spec request(binary(), iodata(), cow_http1:version(), headers()) -> iodata(). request(Method, Path, Version, Headers) -> cow_http1:request(Method, Path, Version, Headers). -spec response(status() | binary(), cow_http1:version(), headers()) -> iodata(). response(Status, Version, Headers) -> cow_http1:response(Status, Version, Headers). -spec headers(headers()) -> iodata(). headers(Headers) -> cow_http1:headers(Headers). -spec version(cow_http1:version()) -> binary(). version(Version) -> cow_http1:version(Version). %% Functions used by HTTP/2+. %% Semantic errors are common to all HTTP versions. -spec format_semantic_error(atom()) -> atom(). format_semantic_error(connect_invalid_content_length_2xx) -> 'Content-length header received in a 2xx response to a CONNECT request. (RFC7230 3.3.2).'; format_semantic_error(invalid_content_length_header) -> 'The content-length header is invalid. (RFC7230 3.3.2)'; format_semantic_error(invalid_content_length_header_1xx) -> 'Content-length header received in a 1xx response. (RFC7230 3.3.2)'; format_semantic_error(invalid_content_length_header_204) -> 'Content-length header received in a 204 response. (RFC7230 3.3.2)'; format_semantic_error(multiple_content_length_headers) -> 'Multiple content-length headers were received. (RFC7230 3.3.2)'. %% Merge pseudo headers at the start of headers. -spec merge_pseudo_headers(pseudo_headers(), headers()) -> 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)). %% Process HTTP/2+ headers. This is done after decoding them. -spec process_headers(headers(), request | push_promise | response | trailers, binary() | undefined, fin(), #{enable_connect_protocol => boolean(), any() => any()}) -> {headers, headers(), pseudo_headers(), non_neg_integer() | undefined} | {push_promise, headers(), pseudo_headers()} | {trailers, headers()} | {error, atom()}. process_headers(Headers0, Type, ReqMethod, IsFin, LocalSettings) when Type =:= request; Type =:= push_promise -> IsExtendedConnectEnabled = maps:get(enable_connect_protocol, LocalSettings, false), case request_pseudo_headers(Headers0, #{}) of %% Extended CONNECT method (HTTP/2: RFC8441, HTTP/3: RFC9220). {ok, PseudoHeaders=#{method := <<"CONNECT">>, scheme := _, authority := _, path := _, protocol := _}, Headers} when IsExtendedConnectEnabled -> regular_headers(Headers, Type, ReqMethod, IsFin, PseudoHeaders); {ok, #{method := <<"CONNECT">>, scheme := _, authority := _, path := _}, _} when IsExtendedConnectEnabled -> {error, extended_connect_missing_protocol}; {ok, #{protocol := _}, _} -> {error, invalid_protocol_pseudo_header}; %% Normal CONNECT (no scheme/path). {ok, PseudoHeaders = #{method := <<"CONNECT">>, authority := _}, Headers} when map_size(PseudoHeaders) =:= 2 -> regular_headers(Headers, Type, ReqMethod, IsFin, PseudoHeaders); {ok, #{method := <<"CONNECT">>, authority := _}, _} -> {error, connect_invalid_pseudo_header}; {ok, #{method := <<"CONNECT">>}, _} -> {error, connect_missing_authority}; %% Other requests. {ok, PseudoHeaders = #{method := _, scheme := _, path := _}, Headers} -> regular_headers(Headers, Type, ReqMethod, IsFin, PseudoHeaders); {ok, _, _} -> {error, missing_pseudo_header}; Error = {error, _} -> Error end; process_headers(Headers0, Type = response, ReqMethod, IsFin, _LocalSettings) -> case response_pseudo_headers(Headers0, #{}) of {ok, PseudoHeaders=#{status := _}, Headers} -> regular_headers(Headers, Type, ReqMethod, IsFin, PseudoHeaders); {ok, _, _} -> {error, missing_pseudo_header}; Error = {error, _} -> Error end; process_headers(Headers, Type = trailers, ReqMethod, IsFin, _LocalSettings) -> case trailers_have_pseudo_headers(Headers) of false -> regular_headers(Headers, Type, ReqMethod, IsFin, #{}); true -> {error, trailer_invalid_pseudo_header} end. request_pseudo_headers([{<<":method">>, _}|_], #{method := _}) -> {error, multiple_method_pseudo_headers}; request_pseudo_headers([{<<":method">>, Method}|Tail], PseudoHeaders) -> try validate_token(Method) of ok -> request_pseudo_headers(Tail, PseudoHeaders#{method => Method}) catch _:_ -> {error, invalid_pseudo_header} end; request_pseudo_headers([{<<":scheme">>, _}|_], #{scheme := _}) -> {error, multiple_scheme_pseudo_headers}; request_pseudo_headers([{<<":scheme">>, Scheme}|Tail], PseudoHeaders) -> try validate_scheme(Scheme) of ok -> request_pseudo_headers(Tail, PseudoHeaders#{scheme => Scheme}) catch _:_ -> {error, invalid_pseudo_header} end; request_pseudo_headers([{<<":authority">>, _}|_], #{authority := _}) -> {error, multiple_authority_pseudo_headers}; request_pseudo_headers([{<<":authority">>, Authority}|Tail], PseudoHeaders) -> case validate_header(Authority) of ok -> request_pseudo_headers(Tail, PseudoHeaders#{authority => Authority}); error -> {error, invalid_pseudo_header} end; request_pseudo_headers([{<<":path">>, _}|_], #{path := _}) -> {error, multiple_path_pseudo_headers}; request_pseudo_headers([{<<":path">>, Path}|Tail], PseudoHeaders) -> case validate_path(Path) of ok -> request_pseudo_headers(Tail, PseudoHeaders#{path => Path}); error -> {error, invalid_pseudo_header} end; request_pseudo_headers([{<<":protocol">>, _}|_], #{protocol := _}) -> {error, multiple_protocol_pseudo_headers}; request_pseudo_headers([{<<":protocol">>, Protocol}|Tail], PseudoHeaders) -> try validate_token(Protocol) of ok -> request_pseudo_headers(Tail, PseudoHeaders#{protocol => Protocol}) catch _:_ -> {error, invalid_pseudo_header} end; request_pseudo_headers([{<<":", _/bits>>, _}|_], _) -> {error, invalid_pseudo_header}; request_pseudo_headers(Headers, PseudoHeaders) -> {ok, PseudoHeaders, Headers}. response_pseudo_headers([{<<":status">>, _}|_], #{status := _}) -> {error, multiple_status_pseudo_headers}; 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, invalid_status_pseudo_header} end; response_pseudo_headers([{<<":", _/bits>>, _}|_], _) -> {error, invalid_pseudo_header}; response_pseudo_headers(Headers, PseudoHeaders) -> {ok, PseudoHeaders, Headers}. trailers_have_pseudo_headers([]) -> false; trailers_have_pseudo_headers([{<<":", _/bits>>, _}|_]) -> true; trailers_have_pseudo_headers([_|Tail]) -> trailers_have_pseudo_headers(Tail). %% Rejecting invalid regular headers might be a bit too strong for clients. regular_headers(Headers, Type, ReqMethod, IsFin, PseudoHeaders) -> case regular_headers(Headers, Type) of ok when Type =:= request -> request_expected_size(Headers, IsFin, PseudoHeaders); ok when Type =:= push_promise -> return_push_promise(Headers, PseudoHeaders); ok when Type =:= response -> response_expected_size(Headers, ReqMethod, IsFin, PseudoHeaders); ok when Type =:= trailers -> return_trailers(Headers); Error = {error, _} -> Error end. regular_headers([{<<>>, _}|_], _) -> {error, empty_header_name}; regular_headers([{<<":", _/bits>>, _}|_], _) -> {error, pseudo_header_after_regular}; regular_headers([{<<"connection">>, _}|_], _) -> {error, invalid_connection_header}; regular_headers([{<<"keep-alive">>, _}|_], _) -> {error, invalid_keep_alive_header}; regular_headers([{<<"proxy-authenticate">>, _}|_], _) -> {error, invalid_proxy_authenticate_header}; regular_headers([{<<"proxy-authorization">>, _}|_], _) -> {error, invalid_proxy_authorization_header}; regular_headers([{<<"transfer-encoding">>, _}|_], _) -> {error, invalid_transfer_encoding_header}; regular_headers([{<<"upgrade">>, _}|_], _) -> {error, invalid_upgrade_header}; regular_headers([{<<"te">>, Value}|_], request) when Value =/= <<"trailers">> -> {error, invalid_te_value}; regular_headers([{<<"te">>, _}|_], Type) when Type =/= request -> {error, invalid_te_header}; regular_headers([{Name, Value}|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>>, <<$\r>>, <<$\n>>, <<$\0>> ], case binary:match(Name, Pattern) of nomatch -> case validate_header(Value) of ok -> regular_headers(Tail, Type); error -> {error, invalid_header_value} end; _ -> {error, invalid_header_name} end; regular_headers([], _) -> ok. %% Reject CR, LF and NUL early. validate_header(Value) -> case binary:match(Value, [<<$\r>>, <<$\n>>, <<$\0>>]) of nomatch -> ok; _ -> error end. %% Reject CR, LF, NUL and # early. validate_path(Value) -> case binary:match(Value, [<<$\r>>, <<$\n>>, <<$\0>>, <<$\#>>]) of nomatch -> ok; _ -> error end. request_expected_size(Headers, IsFin, PseudoHeaders) -> case [CL || {<<"content-length">>, CL} <- Headers] of [] when IsFin =:= fin -> return_headers(Headers, PseudoHeaders, 0); [] -> return_headers(Headers, PseudoHeaders, undefined); [<<"0">>] -> return_headers(Headers, PseudoHeaders, 0); [_] when IsFin =:= fin -> {error, non_zero_length_with_fin_flag}; [BinLen] -> parse_expected_size(Headers, PseudoHeaders, BinLen); _ -> {error, multiple_content_length_headers} end. response_expected_size(Headers, ReqMethod, IsFin, PseudoHeaders = #{status := Status}) -> case [CL || {<<"content-length">>, CL} <- Headers] of [] when IsFin =:= fin -> return_headers(Headers, PseudoHeaders, 0); [] -> return_headers(Headers, PseudoHeaders, undefined); [_] when Status >= 100, Status =< 199 -> {error, invalid_content_length_header_1xx}; [_] when Status =:= 204 -> {error, invalid_content_length_header_204}; [_] when Status >= 200, Status =< 299, ReqMethod =:= <<"CONNECT">> -> {error, connect_invalid_content_length_2xx}; %% Responses to HEAD requests, and 304 responses may contain %% a content-length header that must be ignored. (RFC7230 3.3.2) [_] when ReqMethod =:= <<"HEAD">> -> return_headers(Headers, PseudoHeaders, 0); [_] when Status =:= 304 -> return_headers(Headers, PseudoHeaders, 0); [<<"0">>] when IsFin =:= fin -> return_headers(Headers, PseudoHeaders, 0); [_] when IsFin =:= fin -> {error, non_zero_length_with_fin_flag}; [BinLen] -> parse_expected_size(Headers, PseudoHeaders, BinLen); _ -> {error, multiple_content_length_headers} end. parse_expected_size(Headers, PseudoHeaders, BinLen) -> try cow_http_hd:parse_content_length(BinLen) of Len -> return_headers(Headers, PseudoHeaders, Len) catch _:_ -> {error, invalid_content_length_header} end. return_headers(Headers, PseudoHeaders, Len) -> {headers, Headers, PseudoHeaders, Len}. return_push_promise(Headers, PseudoHeaders) -> {push_promise, Headers, PseudoHeaders}. return_trailers(Headers) -> {trailers, Headers}. %% Remove HTTP/1-specific headers. -spec remove_http1_headers(headers()) -> headers(). remove_http1_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). %% Extract path and query string from a binary. -spec parse_fullpath(binary()) -> {binary(), binary()}. parse_fullpath(Fullpath) -> case binary:split(Fullpath, <<$?>>) of [Path] -> {Path, <<>>}; [Path, Query] -> {Path, Query} end. -ifdef(TEST). parse_fullpath_test() -> {<<"*">>, <<>>} = parse_fullpath(<<"*">>), {<<"/">>, <<>>} = parse_fullpath(<<"/">>), {<<"/path/to/resource">>, <<>>} = parse_fullpath(<<"/path/to/resource">>), {<<"/">>, <<>>} = parse_fullpath(<<"/?">>), {<<"/">>, <<"q=cowboy">>} = parse_fullpath(<<"/?q=cowboy">>), {<<"/path/to/resource">>, <<"q=cowboy">>} = parse_fullpath(<<"/path/to/resource?q=cowboy">>), ok. -endif. %% Validation functions. -spec ensure_scheme(binary()) -> binary(). ensure_scheme(Scheme) -> ok = validate_scheme(Scheme), Scheme. validate_scheme(<>) when ?IS_ALPHA(C) -> validate_scheme1(R). validate_scheme1(<>) when ?IS_ALPHA(C) or ?IS_DIGIT(C) or (C =:= $+) or (C =:= $-) or (C =:= $.) -> validate_scheme1(R); validate_scheme1(<<>>) -> ok. -ifdef(TEST). ensure_scheme_test_() -> Tests = [ <<"a">>, <<"a0">>, <<"a+">>, <<"a-">>, <<"a.">>, <<"GET">>, <<"foo">>, <<"foo-bar">>, <<"foo+bar">>, <<"foo.bar">>, %% Long. << <<$a>> || _ <- lists:seq(1, 1024) >> ], [{V, fun() -> V = ensure_scheme(V) end} || V <- Tests]. ensure_scheme_error_test_() -> Tests = [ %% Empty. <<>>, %% Invalid first. <<"9http">>, <<"-http">>, <<"+http">>, <<".http">>, <<" http">>, <<"_http">>, %% Invalid chars. <<"a!">>, <<"a#">>, <<"a'">>, <<"a*">>, <<"a^">>, <<"a_">>, <<"a`">>, <<"a|">>, <<"a~">>, <<"a ">>, <<"a,">>, <<"a:">>, <<"a;">>, <<"a\"">>, <<"a(">>, <<"a)">>, <<"a<">>, <<"a>">>, <<"a[">>, <<"a]">>, <<"a{">>, <<"a}">>, <<"a\\">>, <<"a?">>, <<"a@">>, <<"a\r">>, <<"a\n">>, <<"a\0">>, <<$a,16#1f>>, <<$a,16#7f>>, <<$a,16#80>>, <<195,169>>, %% é %% Invalid type. undefined ], [{iolist_to_binary(io_lib:format("~0p", [V])), fun() -> ?assertError(_, ensure_scheme(V)) end} || V <- Tests]. -endif. -spec ensure_token(binary()) -> binary(). ensure_token(Token) -> ok = validate_token(Token), Token. validate_token(Token) when Token =/= <<>> -> validate_token1(Token). validate_token1(<>) when ?IS_TOKEN(C) -> validate_token1(R); validate_token1(<<>>) -> ok. -ifdef(TEST). ensure_token_test_() -> Tests = [ <<$a>>, <<$0>>, <<"!">>, <<"#">>, <<"$">>, <<"%">>, <<"&">>, <<"'">>, <<"*">>, <<"+">>, <<"-">>, <<".">>, <<"^">>, <<"_">>, <<"`">>, <<"|">>, <<"~">>, <<"GET">>, <<"foo">>, <<"foo-bar">>, <<"!#$%&'*+-.^_`|~09azAZ">>, %% Long. << <<$a>> || _ <- lists:seq(1, 1024) >> ], [{V, fun() -> V = ensure_token(V) end} || V <- Tests]. ensure_token_error_test_() -> Tests = [ %% Empty. <<>>, %% Invalid chars. <<" ">>, <<",">>, <<":">>, <<";">>, <<"\"">>, <<"(">>, <<")">>, <<"<">>, <<">">>, <<"[">>, <<"]">>, <<"{">>, <<"}">>, <<"\\">>, <<"?">>, <<"@">>, <<$\r>>, <<$\n>>, <<0>>, <<16#1f>>, <<16#7f>>, <<16#80>>, <<195,169>>, %% é %% Invalid in middle. <<"a b">>, <<"a,b">>, <<"a:b">>, %% Invalid type. undefined ], [{iolist_to_binary(io_lib:format("~0p", [V])), fun() -> ?assertError(_, ensure_token(V)) end} || V <- Tests]. -endif. cowlib-2.19.0/src/cow_http1.erl000066400000000000000000000307541523166326600163100ustar00rootroot00000000000000%% Copyright (c) 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_http1). -export([parse_request_line/1]). -export([parse_status_line/1]). -export([status_to_integer/1]). -export([parse_headers/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]). -include("cow_inline.hrl"). -ifdef(TEST). -include_lib("stdlib/include/assert.hrl"). -endif. %% @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() -> ?assertError(_, 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(), cow_http: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(cow_http:status() | binary()) -> cow_http: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() -> ?assertError(_, 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() -> ?assertError(_, 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 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">>), ?assertError(_, 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(), cow_http:headers()) -> iodata(). request(Method, Path, Version, Headers) -> [Method, <<" ">>, Path, <<" ">>, version(Version), <<"\r\n">>, [[N, <<": ">>, V, <<"\r\n">>] || {N, V} <- Headers], <<"\r\n">>]. -spec response(cow_http:status() | binary(), version(), cow_http:headers()) -> iodata(). response(Status, Version, Headers) -> [version(Version), <<" ">>, status(Status), <<"\r\n">>, headers(Headers), <<"\r\n">>]. -spec headers(cow_http: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'), ?assertError(_, version('HTTP/1.2')), ok. -endif. %% @doc Return the status code and string as binary. -spec status(cow_http: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. cowlib-2.19.0/src/cow_http2.erl000066400000000000000000000530521523166326600163050ustar00rootroot00000000000000%% Copyright (c) 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 head_fin() :: head_fin | head_nofin. -export_type([head_fin/0]). %% @todo The PRIORITY mechanism in HTTP/2 is de facto deprecated. -type exclusive() :: exclusive | shared. -type weight() :: 1..256. -type settings() :: #{ enable_connect_protocol => boolean(), enable_push => boolean(), header_table_size => 16384..16#7fffffff, initial_window_size => 0..16#7fffffff, max_concurrent_streams => 0..16#ffffffff, max_frame_size => 16384..16777215, max_header_list_size => 16384..16#ffffffff }. -export_type([settings/0]). -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(), cow_http:fin(), binary()} | {headers, streamid(), cow_http:fin(), head_fin(), binary()} | {headers, streamid(), cow_http: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(<< 0:24, 9:8, _:5, 0:1, _/bits >>) -> {connection_error, enhance_your_calm, 'CONTINUATION frames MUST have a non-zero length unless the END_HEADERS flag is set. (RFC9113 10.5)'}; 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. cowlib-2.19.0/src/cow_http2_machine.erl000066400000000000000000001777731523166326600200120ustar00rootroot00000000000000%% Copyright (c) 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([terminate/1]). -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_remote_concurrency_limit_reached/1]). -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_headers => non_neg_integer(), 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_http: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_http: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_http: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_http: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. Settings with %% a minimal default must be acknowledged before they can %% be expected to be applied. Settings with a protocol %% default of 'infinity' get a safer default enforced %% immediately. 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 % enable_connect_protocol => false } :: map(), next_settings = #{} :: 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]). %% 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), %% We use a default of 100 even though the protocol %% default is infinity. S1 = setting_from_opt(S0, Opts, max_concurrent_streams, max_concurrent_streams, infinity, 100), 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) -> setting_from_opt(Settings, Opts, OptName, SettingName, Default, Default). setting_from_opt(Settings, Opts, OptName, SettingName, Default, DefaultIfMissing) -> case Opts of #{OptName := Default} -> Settings; #{OptName := Value} -> Settings#{SettingName => Value}; #{} when Default =:= DefaultIfMissing -> Settings; #{} -> Settings#{SettingName => DefaultIfMissing} end. -spec terminate(State::http2_machine()) -> ok. terminate(#http2_machine{preface_timer=PTRef, settings_timer=STRef}) -> cancel_timeout(PTRef), cancel_timeout(STRef). cancel_timeout(TRef) -> ok = case TRef of undefined -> ok; _ -> %% Do a synchronous cancel and remove the message if any %% to avoid receiving stray messages. _ = erlang:cancel_timer(TRef, [{async, false}, {info, false}]), receive {timeout, TRef, _} -> ok after 0 -> ok end end. -ifdef(TEST). terminate_removes_stray_timeout_messages_test() -> PTRef = erlang:start_timer(5000, self(), {?MODULE, undefined, preface_timeout}), STRef = erlang:start_timer(5000, self(), {?MODULE, undefined, settings_timeout}), PTMsg = {timeout, PTRef, {?MODULE, undefined, preface_timeout}}, STMsg = {timeout, STRef, {?MODULE, undefined, settings_timeout}}, self() ! PTMsg, self() ! STMsg, terminate(#http2_machine{preface_timer=PTRef, settings_timer=STRef}), [] = collect_timeout_messages(PTRef, STRef), ok. collect_timeout_messages(PTRef, STRef) -> receive {timeout, PTRef, {?MODULE, undefined, preface_timeout}} -> [preface_timeout|collect_timeout_messages(PTRef, STRef)]; {timeout, STRef, {?MODULE, undefined, settings_timeout}} -> [settings_timeout|collect_timeout_messages(PTRef, STRef)] after 0 -> [] end. -endif. -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_http:fin(), binary()}, State} | {ok, {headers, cow_http2:streamid(), cow_http:fin(), cow_http:headers(), cow_http: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(), cow_http:pseudo_headers()}, State} | {ok, {goaway, cow_http2:streamid(), cow_http2:error(), binary()}, State} | {send, [{cow_http2:streamid(), cow_http: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(). %-define(HTTP2_MACHINE_DEBUG, 1). -ifdef(HTTP2_MACHINE_DEBUG). -define(LOG_FRAME(Frame, State), begin Frame2 = case Frame of {data,_,_,_} -> setelement(4, Frame, {'BINARY-DATA', byte_size(element(4, Frame))}); {continuation,_,_,_} -> setelement(4, Frame, {'BINARY-DATA', byte_size(element(4, Frame))}); _ -> Frame end, io:format(user, "~p rcv: ~p~n", [State#http2_machine.mode, Frame2]) end ). -else. -define(LOG_FRAME(Frame, State), _ = Frame). -endif. frame(Frame, State=#http2_machine{state=settings, preface_timer=TRef}) -> ?LOG_FRAME(Frame, State), 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, _, _}}) -> ?LOG_FRAME(Frame, State), maybe_discard_result(continuation_frame(Frame, State)); frame(Frame = settings_ack, State=#http2_machine{state=normal}) -> ?LOG_FRAME(Frame, State), settings_ack_frame(State); frame(Frame, State=#http2_machine{state=normal}) -> ?LOG_FRAME(Frame, State), 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_http: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; %% The PRIORITY mechanism is seen as flawed and deprecated. %% We will not implement it. 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{opts=Opts, decode_state=DecodeState0}, Type, Stream) -> try cow_hpack:decode(HeaderData, DecodeState0, Opts) of {Headers, DecodeState} when Type =:= request -> headers_enforce_concurrency_limit(Frame, State#http2_machine{decode_state=DecodeState}, Type, Stream, Headers); {Headers, DecodeState} -> headers_process(Frame, State#http2_machine{decode_state=DecodeState}, Type, Stream, Headers) catch error:max_headers -> {error, {connection_error, enhance_your_calm, 'The number of headers is larger than configuration allows. (RFC9110 5.4)'}, State}; _:_ -> {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, next_settings=NextSettings, streams=Streams}, Type, Stream, Headers) -> %% We use the most up to date max_concurrent_streams %% even if a settings ack was not yet received. LocalMax = maps:get(max_concurrent_streams, LocalSettings, undefined), NextMax = maps:get(max_concurrent_streams, NextSettings, undefined), MaxConcurrentStreams = case {LocalMax, NextMax} of {undefined, undefined} -> infinity; {_, undefined} -> LocalMax; _ -> NextMax end, %% 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_process(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_process(Frame=#headers{id=StreamID, fin=IsFin}, State=#http2_machine{local_settings=LocalSettings}, Type, Stream, Headers0) -> ReqMethod = case Stream of #stream{method=ReqMethod0} -> ReqMethod0; undefined -> undefined end, case cow_http:process_headers(Headers0, Type, ReqMethod, IsFin, LocalSettings) of {headers, Headers, PseudoHeaders, Len} -> headers_frame(Frame, State, Type, Stream, Headers, PseudoHeaders, Len); {push_promise, Headers, PseudoHeaders} -> push_promise_frame(Frame, State, Stream, Headers, PseudoHeaders); {trailers, Headers} -> trailers_frame(Frame, State, Stream, Headers); {error, Reason} when Type =:= request -> headers_malformed(Frame, State, format_error(Reason)); {error, Reason} -> stream_reset(StreamID, State, protocol_error, format_error(Reason)) end. headers_malformed(#headers{id=StreamID}, State, HumanReadable) -> {error, {stream_error, StreamID, protocol_error, HumanReadable}, State}. format_error(connect_invalid_pseudo_header) -> 'CONNECT requests only use the :method and :authority pseudo-headers. (RFC7540 8.3)'; format_error(connect_missing_authority) -> 'CONNECT requests must include the :authority pseudo-header. (RFC7540 8.3)'; format_error(empty_header_name) -> 'Empty header names are not valid regular headers. (CVE-2019-9516)'; format_error(extended_connect_missing_protocol) -> 'The :protocol pseudo-header MUST be sent with an extended CONNECT. (RFC8441 4)'; format_error(invalid_connection_header) -> 'The connection header is not allowed. (RFC7540 8.1.2.2)'; format_error(invalid_keep_alive_header) -> 'The keep-alive header is not allowed. (RFC7540 8.1.2.2)'; format_error(invalid_protocol_pseudo_header) -> 'The :protocol pseudo-header is only defined for the extended CONNECT. (RFC8441 4)'; format_error(invalid_proxy_authenticate_header) -> 'The proxy-authenticate header is not allowed. (RFC7540 8.1.2.2)'; format_error(invalid_proxy_authorization_header) -> 'The proxy-authorization header is not allowed. (RFC7540 8.1.2.2)'; format_error(invalid_pseudo_header) -> 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)'; format_error(invalid_status_pseudo_header) -> 'The :status pseudo-header value is invalid. (RFC7540 8.1.2.4)'; format_error(invalid_te_header) -> 'The te header is only allowed in request headers. (RFC7540 8.1.2.2)'; format_error(invalid_te_value) -> 'The te header with a value other than "trailers" is not allowed. (RFC7540 8.1.2.2)'; format_error(invalid_transfer_encoding_header) -> 'The transfer-encoding header is not allowed. (RFC7540 8.1.2.2)'; format_error(invalid_upgrade_header) -> 'The upgrade header is not allowed. (RFC7540 8.1.2.2)'; format_error(missing_pseudo_header) -> 'A required pseudo-header was not found. (RFC7540 8.1.2.3, RFC7540 8.1.2.4)'; format_error(multiple_authority_pseudo_headers) -> 'Multiple :authority pseudo-headers were found. (RFC7540 8.1.2.3)'; format_error(multiple_method_pseudo_headers) -> 'Multiple :method pseudo-headers were found. (RFC7540 8.1.2.3)'; format_error(multiple_path_pseudo_headers) -> 'Multiple :path pseudo-headers were found. (RFC7540 8.1.2.3)'; format_error(multiple_protocol_pseudo_headers) -> 'Multiple :protocol pseudo-headers were found. (RFC7540 8.1.2.3)'; format_error(multiple_scheme_pseudo_headers) -> 'Multiple :scheme pseudo-headers were found. (RFC7540 8.1.2.3)'; format_error(multiple_status_pseudo_headers) -> 'Multiple :status pseudo-headers were found. (RFC7540 8.1.2.3)'; format_error(non_zero_length_with_fin_flag) -> 'HEADERS frame with the END_STREAM flag contains a non-zero content-length. (RFC7540 8.1.2.6)'; format_error(pseudo_header_after_regular) -> 'Pseudo-headers were found after regular headers. (RFC7540 8.1.2.1)'; format_error(trailer_invalid_pseudo_header) -> 'Trailer header blocks must not contain pseudo-headers. (RFC7540 8.1.2.1)'; format_error(invalid_header_name) -> 'Header names must be lowercase. (RFC7540 8.1.2)'; format_error(invalid_header_value) -> 'Header values must only contain valid characters. (RFC7540 8.1.2)'; format_error(Reason) -> cow_http:format_semantic_error(Reason). headers_frame(#headers{id=StreamID, fin=IsFin}, State0=#http2_machine{ local_settings=#{initial_window_size := RemoteWindow}, remote_settings=#{initial_window_size := LocalWindow}}, Type, Stream0, Headers, PseudoHeaders, 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. %% %% The PRIORITY mechanism is seen as flawed and deprecated. %% We will not implement it. 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}, Headers, PseudoHeaders=#{method := Method}) -> 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#http2_machine{preface_timer=undefined}}; 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#http2_machine{settings_timer=undefined}}; 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_http:fin(), cow_http:pseudo_headers(), cow_http:headers()) -> {ok, cow_http: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 = cow_http:merge_pseudo_headers(PseudoHeaders, cow_http:remove_http1_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, cow_http: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 = cow_http:merge_pseudo_headers(PseudoHeaders, cow_http:remove_http1_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})}. -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_http:fin(), DataOrFileOrTrailers) -> {ok, State} | {send, [{cow_http2:streamid(), cow_http: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. %% The PRIORITY mechanism is seen as flawed and deprecated. %% We will not implement it. So 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_http: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_http: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. %% Check if we are allowed to initiate a new stream according to the remote %% setting MAX_CONCURRENT_STREAMS. -spec is_remote_concurrency_limit_reached(http2_machine()) -> boolean(). is_remote_concurrency_limit_reached(State=#http2_machine{ remote_settings=RemoteSettings, streams=Streams}) -> MaxConcurrentStreams = maps:get(max_concurrent_streams, RemoteSettings, infinity), %% We care about local streams, but first check the total number of %% streams because it's cheaper. MaxConcurrentStreams =/= infinity andalso map_size(Streams) >= MaxConcurrentStreams andalso count_local_streams(State) >= MaxConcurrentStreams. count_local_streams(#http2_machine{mode=Mode, streams=Streams}) -> maps:fold(fun(StreamId, _Stream, Sum) when ?IS_LOCAL(Mode, StreamId) -> Sum + 1; (_, _, Sum) -> Sum end, 0, Streams). %% 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}. cowlib-2.19.0/src/cow_http3.erl000066400000000000000000000551621523166326600163120ustar00rootroot00000000000000%% Copyright (c) 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_http3). %% Parsing. -export([parse/1]). -export([parse_unidi_stream_header/1]). -export([parse_datagram/1]). -export([code_to_error/1]). -export([parse_int/1]). %% Building. -export([data/1]). -export([headers/1]). -export([settings/1]). -export([webtransport_stream_header/2]). -export([datagram/2]). -export([error_to_code/1]). -export([encode_int/1]). -type stream_id() :: non_neg_integer(). -export_type([stream_id/0]). -type push_id() :: non_neg_integer(). -export_type([push_id/0]). -type h3_non_neg_integer() :: 0..16#3fffffffffffffff. -type settings() :: #{ qpack_max_table_capacity => h3_non_neg_integer(), max_field_section_size => h3_non_neg_integer(), qpack_blocked_streams => h3_non_neg_integer(), enable_connect_protocol => boolean(), %% Extensions. h3_datagram => boolean(), wt_max_sessions => h3_non_neg_integer(), wt_initial_max_streams_uni => h3_non_neg_integer(), wt_initial_max_streams_bidi => h3_non_neg_integer(), wt_initial_max_data => h3_non_neg_integer() }. -export_type([settings/0]). -type wt_app_error_code() :: 0..16#ffffffff. -export_type([wt_app_error_code/0]). -type error() :: h3_no_error | h3_general_protocol_error | h3_internal_error | h3_stream_creation_error | h3_closed_critical_stream | h3_frame_unexpected | h3_frame_error | h3_excessive_load | h3_id_error | h3_settings_error | h3_missing_settings | h3_request_rejected | h3_request_cancelled | h3_request_incomplete | h3_message_error | h3_connect_error | h3_version_fallback %% Extensions. | h3_datagram_error | wt_buffered_stream_rejected | wt_session_gone | {wt_application_error, wt_app_error_code()}. -export_type([error/0]). -type frame() :: {data, binary()} | {headers, binary()} | {cancel_push, push_id()} | {settings, settings()} | {push_promise, push_id(), binary()} | {goaway, stream_id() | push_id()} | {max_push_id, push_id()}. -export_type([frame/0]). %% Parsing. -spec parse(binary()) -> {ok, frame(), binary()} | {webtransport_stream_header, stream_id(), binary()} | {more, {data, binary()} | ignore, non_neg_integer()} | {ignore, binary()} | {connection_error, h3_frame_error | h3_frame_unexpected | h3_settings_error, atom()} | more. %% %% DATA frames. %% parse(<<0, 0:2, Len:6, Data:Len/binary, Rest/bits>>) -> {ok, {data, Data}, Rest}; parse(<<0, 1:2, Len:14, Data:Len/binary, Rest/bits>>) -> {ok, {data, Data}, Rest}; parse(<<0, 2:2, Len:30, Data:Len/binary, Rest/bits>>) -> {ok, {data, Data}, Rest}; parse(<<0, 3:2, Len:62, Data:Len/binary, Rest/bits>>) -> {ok, {data, Data}, Rest}; %% DATA frames may be split over multiple QUIC packets %% but we want to process them immediately rather than %% risk buffering a very large payload. parse(<<0, 0:2, Len:6, Data/bits>>) when byte_size(Data) < Len -> {more, {data, Data}, Len - byte_size(Data)}; parse(<<0, 1:2, Len:14, Data/bits>>) when byte_size(Data) < Len -> {more, {data, Data}, Len - byte_size(Data)}; parse(<<0, 2:2, Len:30, Data/bits>>) when byte_size(Data) < Len -> {more, {data, Data}, Len - byte_size(Data)}; parse(<<0, 3:2, Len:62, Data/bits>>) when byte_size(Data) < Len -> {more, {data, Data}, Len - byte_size(Data)}; %% %% HEADERS frames. %% parse(<<1, 0:2, 0:6, _/bits>>) -> {connection_error, h3_frame_error, 'HEADERS frames payload CANNOT be 0 bytes wide. (RFC9114 7.1, RFC9114 7.2.2)'}; parse(<<1, 1:2, 0:14, _/bits>>) -> {connection_error, h3_frame_error, 'HEADERS frames payload CANNOT be 0 bytes wide. (RFC9114 7.1, RFC9114 7.2.2)'}; parse(<<1, 2:2, 0:30, _/bits>>) -> {connection_error, h3_frame_error, 'HEADERS frames payload CANNOT be 0 bytes wide. (RFC9114 7.1, RFC9114 7.2.2)'}; parse(<<1, 3:2, 0:62, _/bits>>) -> {connection_error, h3_frame_error, 'HEADERS frames payload CANNOT be 0 bytes wide. (RFC9114 7.1, RFC9114 7.2.2)'}; parse(<<1, 0:2, Len:6, EncodedFieldSection:Len/binary, Rest/bits>>) -> {ok, {headers, EncodedFieldSection}, Rest}; parse(<<1, 1:2, Len:14, EncodedFieldSection:Len/binary, Rest/bits>>) -> {ok, {headers, EncodedFieldSection}, Rest}; parse(<<1, 2:2, Len:30, EncodedFieldSection:Len/binary, Rest/bits>>) -> {ok, {headers, EncodedFieldSection}, Rest}; parse(<<1, 3:2, Len:62, EncodedFieldSection:Len/binary, Rest/bits>>) -> {ok, {headers, EncodedFieldSection}, Rest}; %% %% CANCEL_PUSH frames. %% parse(<<3, 0:2, 1:6, 0:2, PushID:6, Rest/bits>>) -> {ok, {cancel_push, PushID}, Rest}; parse(<<3, 0:2, 2:6, 1:2, PushID:14, Rest/bits>>) -> {ok, {cancel_push, PushID}, Rest}; parse(<<3, 0:2, 4:6, 2:2, PushID:30, Rest/bits>>) -> {ok, {cancel_push, PushID}, Rest}; parse(<<3, 0:2, 8:6, 3:2, PushID:62, Rest/bits>>) -> {ok, {cancel_push, PushID}, Rest}; parse(<<3, _/bits>>) -> {connection_error, h3_frame_error, 'CANCEL_PUSH frames payload MUST be 1, 2, 4 or 8 bytes wide. (RFC9114 7.1, RFC9114 7.2.3)'}; %% %% SETTINGS frames. %% parse(<<4, 0:2, Len:6, Rest/bits>>) when byte_size(Rest) >= Len -> parse_settings_id(Rest, Len, #{}); parse(<<4, 1:2, Len:14, Rest/bits>>) when byte_size(Rest) >= Len -> parse_settings_id(Rest, Len, #{}); parse(<<4, 2:2, Len:30, Rest/bits>>) when byte_size(Rest) >= Len -> parse_settings_id(Rest, Len, #{}); parse(<<4, 3:2, Len:62, Rest/bits>>) when byte_size(Rest) >= Len -> parse_settings_id(Rest, Len, #{}); %% %% PUSH_PROMISE frames. %% parse(<<5, 0:2, Len:6, Rest/bits>>) when byte_size(Rest) >= Len -> parse_push_promise(Rest, Len); parse(<<5, 1:2, Len:14, Rest/bits>>) when byte_size(Rest) >= Len -> parse_push_promise(Rest, Len); parse(<<5, 2:2, Len:30, Rest/bits>>) when byte_size(Rest) >= Len -> parse_push_promise(Rest, Len); parse(<<5, 3:2, Len:62, Rest/bits>>) when byte_size(Rest) >= Len -> parse_push_promise(Rest, Len); %% %% GOAWAY frames. %% parse(<<7, 0:2, 1:6, 0:2, StreamOrPushID:6, Rest/bits>>) -> {ok, {goaway, StreamOrPushID}, Rest}; parse(<<7, 0:2, 2:6, 1:2, StreamOrPushID:14, Rest/bits>>) -> {ok, {goaway, StreamOrPushID}, Rest}; parse(<<7, 0:2, 4:6, 2:2, StreamOrPushID:30, Rest/bits>>) -> {ok, {goaway, StreamOrPushID}, Rest}; parse(<<7, 0:2, 8:6, 3:2, StreamOrPushID:62, Rest/bits>>) -> {ok, {goaway, StreamOrPushID}, Rest}; parse(<<7, 0:2, N:6, _/bits>>) when N =:= 1; N =:= 2; N =:= 4; N =:= 8 -> more; parse(<<7, _/bits>>) -> {connection_error, h3_frame_error, 'GOAWAY frames payload MUST be 1, 2, 4 or 8 bytes wide. (RFC9114 7.1, RFC9114 7.2.6)'}; %% %% MAX_PUSH_ID frames. %% parse(<<13, 0:2, 1:6, 0:2, PushID:6, Rest/bits>>) -> {ok, {max_push_id, PushID}, Rest}; parse(<<13, 0:2, 2:6, 1:2, PushID:14, Rest/bits>>) -> {ok, {max_push_id, PushID}, Rest}; parse(<<13, 0:2, 4:6, 2:2, PushID:30, Rest/bits>>) -> {ok, {max_push_id, PushID}, Rest}; parse(<<13, 0:2, 8:6, 3:2, PushID:62, Rest/bits>>) -> {ok, {max_push_id, PushID}, Rest}; parse(<<13, 0:2, N:6, _/bits>>) when N =:= 1; N =:= 2; N =:= 4; N =:= 8 -> more; parse(<<13, _/bits>>) -> {connection_error, h3_frame_error, 'MAX_PUSH_ID frames payload MUST be 1, 2, 4 or 8 bytes wide. (RFC9114 7.1, RFC9114 7.2.6)'}; %% %% WebTransport stream header. %% parse(<<1:2, 16#41:14, 0:2, SessionID:6, Rest/bits>>) -> {webtransport_stream_header, SessionID, Rest}; parse(<<1:2, 16#41:14, 1:2, SessionID:14, Rest/bits>>) -> {webtransport_stream_header, SessionID, Rest}; parse(<<1:2, 16#41:14, 2:2, SessionID:30, Rest/bits>>) -> {webtransport_stream_header, SessionID, Rest}; parse(<<1:2, 16#41:14, 3:2, SessionID:62, Rest/bits>>) -> {webtransport_stream_header, SessionID, Rest}; parse(<<16#41, _/bits>>) -> more; %% %% HTTP/2 frame types must be rejected. %% parse(<<2, _/bits>>) -> {connection_error, h3_frame_unexpected, 'HTTP/2 PRIORITY frame not defined for HTTP/3 must be rejected. (RFC9114 7.2.8)'}; parse(<<6, _/bits>>) -> {connection_error, h3_frame_unexpected, 'HTTP/2 PING frame not defined for HTTP/3 must be rejected. (RFC9114 7.2.8)'}; parse(<<8, _/bits>>) -> {connection_error, h3_frame_unexpected, 'HTTP/2 WINDOW_UPDATE frame not defined for HTTP/3 must be rejected. (RFC9114 7.2.8)'}; parse(<<9, _/bits>>) -> {connection_error, h3_frame_unexpected, 'HTTP/2 CONTINUATION frame not defined for HTTP/3 must be rejected. (RFC9114 7.2.8)'}; %% %% Unknown frames must be ignored. parse(<<0:2, Type:6, 0:2, Len:6, Rest/bits>>) when Type =:= 10; Type =:= 11; Type =:= 12; Type > 13 -> parse_ignore(Rest, Len); parse(<<0:2, Type:6, 1:2, Len:14, Rest/bits>>) when Type =:= 10; Type =:= 11; Type =:= 12; Type > 13 -> parse_ignore(Rest, Len); parse(<<0:2, Type:6, 2:2, Len:30, Rest/bits>>) when Type =:= 10; Type =:= 11; Type =:= 12; Type > 13 -> parse_ignore(Rest, Len); parse(<<0:2, Type:6, 3:2, Len:62, Rest/bits>>) when Type =:= 10; Type =:= 11; Type =:= 12; Type > 13 -> parse_ignore(Rest, Len); parse(<<1:2, _:14, 0:2, Len:6, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<1:2, _:14, 1:2, Len:14, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<1:2, _:14, 2:2, Len:30, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<1:2, _:14, 3:2, Len:62, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<2:2, _:30, 0:2, Len:6, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<2:2, _:30, 1:2, Len:14, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<2:2, _:30, 2:2, Len:30, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<2:2, _:30, 3:2, Len:62, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<3:2, _:62, 0:2, Len:6, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<3:2, _:62, 1:2, Len:14, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<3:2, _:62, 2:2, Len:30, Rest/bits>>) -> parse_ignore(Rest, Len); parse(<<3:2, _:62, 3:2, Len:62, Rest/bits>>) -> parse_ignore(Rest, Len); %% %% Incomplete frames for those we fully process only. %% parse(_) -> more. parse_settings_id(Rest, 0, Settings) -> {ok, {settings, Settings}, Rest}; parse_settings_id(<<0:2, Identifier:6, Rest/bits>>, Len, Settings) when Len >= 1 -> parse_settings_val(Rest, Len - 1, Settings, Identifier); parse_settings_id(<<1:2, Identifier:14, Rest/bits>>, Len, Settings) when Len >= 2 -> parse_settings_val(Rest, Len - 2, Settings, Identifier); parse_settings_id(<<2:2, Identifier:30, Rest/bits>>, Len, Settings) when Len >= 4 -> parse_settings_val(Rest, Len - 4, Settings, Identifier); parse_settings_id(<<3:2, Identifier:62, Rest/bits>>, Len, Settings) when Len >= 8 -> parse_settings_val(Rest, Len - 8, Settings, Identifier); parse_settings_id(_, _, _) -> {connection_error, h3_frame_error, 'SETTINGS payload size exceeds the length given. (RFC9114 7.1, RFC9114 7.2.4)'}. parse_settings_val(<<0:2, Value:6, Rest/bits>>, Len, Settings, Identifier) when Len >= 1 -> parse_settings_id_val(Rest, Len - 1, Settings, Identifier, Value); parse_settings_val(<<1:2, Value:14, Rest/bits>>, Len, Settings, Identifier) when Len >= 2 -> parse_settings_id_val(Rest, Len - 2, Settings, Identifier, Value); parse_settings_val(<<2:2, Value:30, Rest/bits>>, Len, Settings, Identifier) when Len >= 4 -> parse_settings_id_val(Rest, Len - 4, Settings, Identifier, Value); parse_settings_val(<<3:2, Value:62, Rest/bits>>, Len, Settings, Identifier) when Len >= 8 -> parse_settings_id_val(Rest, Len - 8, Settings, Identifier, Value); parse_settings_val(_, _, _, _) -> {connection_error, h3_frame_error, 'SETTINGS payload size exceeds the length given. (RFC9114 7.1, RFC9114 7.2.4)'}. parse_settings_id_val(Rest, Len, Settings, Identifier, Value) -> case Identifier of %% SETTINGS_QPACK_MAX_TABLE_CAPACITY (RFC9204). 1 -> parse_settings_key_val(Rest, Len, Settings, qpack_max_table_capacity, Value); %% SETTINGS_MAX_FIELD_SECTION_SIZE (RFC9114). 6 -> parse_settings_key_val(Rest, Len, Settings, max_field_section_size, Value); %% SETTINGS_QPACK_BLOCKED_STREAMS (RFC9204). 7 -> parse_settings_key_val(Rest, Len, Settings, qpack_blocked_streams, Value); %% SETTINGS_ENABLE_CONNECT_PROTOCOL (RFC9220). 8 when Value =:= 0 -> parse_settings_key_val(Rest, Len, Settings, enable_connect_protocol, false); 8 when Value =:= 1 -> parse_settings_key_val(Rest, Len, Settings, enable_connect_protocol, true); 8 -> {connection_error, h3_settings_error, 'The SETTINGS_ENABLE_CONNECT_PROTOCOL value MUST be 0 or 1. (RFC9220 3, RFC8441 3)'}; %% SETTINGS_H3_DATAGRAM (RFC9297). 16#33 when Value =:= 0 -> parse_settings_key_val(Rest, Len, Settings, h3_datagram, false); 16#33 when Value =:= 1 -> parse_settings_key_val(Rest, Len, Settings, h3_datagram, true); 16#33 -> {connection_error, h3_settings_error, 'The SETTINGS_H3_DATAGRAM value MUST be 0 or 1. (RFC9297 2.1.1)'}; %% SETTINGS_WT_MAX_SESSIONS (draft-ietf-webtrans-http3). 16#c671706a -> parse_settings_key_val(Rest, Len, Settings, wt_max_sessions, Value); %% SETTINGS_WT_INITIAL_MAX_STREAMS_UNI (draft-ietf-webtrans-http3). 16#2b64 -> parse_settings_key_val(Rest, Len, Settings, wt_initial_max_streams_uni, Value); %% SETTINGS_WT_INITIAL_MAX_STREAMS_BIDI (draft-ietf-webtrans-http3). 16#2b65 -> parse_settings_key_val(Rest, Len, Settings, wt_initial_max_streams_bidi, Value); %% SETTINGS_WT_INITIAL_MAX_DATA (draft-ietf-webtrans-http3). 16#2b61 -> parse_settings_key_val(Rest, Len, Settings, wt_initial_max_data, Value); _ when Identifier < 6 -> {connection_error, h3_settings_error, 'HTTP/2 setting not defined for HTTP/3 must be rejected. (RFC9114 7.2.4.1)'}; %% Unknown settings must be ignored. _ -> parse_settings_id(Rest, Len, Settings) end. parse_settings_key_val(Rest, Len, Settings, Key, Value) -> case Settings of #{Key := _} -> {connection_error, h3_settings_error, 'A duplicate setting identifier was found. (RFC9114 7.2.4)'}; _ -> parse_settings_id(Rest, Len, Settings#{Key => Value}) end. parse_push_promise(<<0:2, PushID:6, Data/bits>>, Len) -> <> = Data, {ok, {push_promise, PushID, EncodedFieldSection}, Rest}; parse_push_promise(<<1:2, PushID:14, Data/bits>>, Len) -> <> = Data, {ok, {push_promise, PushID, EncodedFieldSection}, Rest}; parse_push_promise(<<2:2, PushID:30, Data/bits>>, Len) -> <> = Data, {ok, {push_promise, PushID, EncodedFieldSection}, Rest}; parse_push_promise(<<3:2, PushID:62, Data/bits>>, Len) -> <> = Data, {ok, {push_promise, PushID, EncodedFieldSection}, Rest}. %% Large ignored frames could lead to DoS. Users of %% this module must limit the size of such frames. parse_ignore(Data, Len) -> case Data of <<_:Len/binary, Rest/bits>> -> {ignore, Rest}; _ -> {more, ignore, Len - byte_size(Data)} end. -spec parse_unidi_stream_header(binary()) -> {ok, control | push | encoder | decoder | {webtransport, stream_id()}, binary()} | {undefined, binary()} | more. parse_unidi_stream_header(<<0, Rest/bits>>) -> {ok, control, Rest}; parse_unidi_stream_header(<<1, Rest/bits>>) -> {ok, push, Rest}; parse_unidi_stream_header(<<2, Rest/bits>>) -> {ok, encoder, Rest}; parse_unidi_stream_header(<<3, Rest/bits>>) -> {ok, decoder, Rest}; %% WebTransport unidi streams. parse_unidi_stream_header(<<1:2, 16#54:14, 0:2, SessionID:6, Rest/bits>>) -> {ok, {webtransport, SessionID}, Rest}; parse_unidi_stream_header(<<1:2, 16#54:14, 1:2, SessionID:14, Rest/bits>>) -> {ok, {webtransport, SessionID}, Rest}; parse_unidi_stream_header(<<1:2, 16#54:14, 2:2, SessionID:30, Rest/bits>>) -> {ok, {webtransport, SessionID}, Rest}; parse_unidi_stream_header(<<1:2, 16#54:14, 3:2, SessionID:62, Rest/bits>>) -> {ok, {webtransport, SessionID}, Rest}; parse_unidi_stream_header(<<1:2, 16#54:14, _/bits>>) -> more; %% Unknown unidi streams. parse_unidi_stream_header(<<0:2, _:6, Rest/bits>>) -> {undefined, Rest}; parse_unidi_stream_header(<<1:2, _:14, Rest/bits>>) -> {undefined, Rest}; parse_unidi_stream_header(<<2:2, _:30, Rest/bits>>) -> {undefined, Rest}; parse_unidi_stream_header(<<3:2, _:62, Rest/bits>>) -> {undefined, Rest}. -spec parse_datagram(binary()) -> {stream_id(), binary()}. parse_datagram(Data) -> {QuarterID, Rest} = parse_int(Data), SessionID = QuarterID * 4, {SessionID, Rest}. -spec code_to_error(non_neg_integer()) -> error(). code_to_error(16#0100) -> h3_no_error; code_to_error(16#0101) -> h3_general_protocol_error; code_to_error(16#0102) -> h3_internal_error; code_to_error(16#0103) -> h3_stream_creation_error; code_to_error(16#0104) -> h3_closed_critical_stream; code_to_error(16#0105) -> h3_frame_unexpected; code_to_error(16#0106) -> h3_frame_error; code_to_error(16#0107) -> h3_excessive_load; code_to_error(16#0108) -> h3_id_error; code_to_error(16#0109) -> h3_settings_error; code_to_error(16#010a) -> h3_missing_settings; code_to_error(16#010b) -> h3_request_rejected; code_to_error(16#010c) -> h3_request_cancelled; code_to_error(16#010d) -> h3_request_incomplete; code_to_error(16#010e) -> h3_message_error; code_to_error(16#010f) -> h3_connect_error; code_to_error(16#0110) -> h3_version_fallback; %% Extensions. code_to_error(16#33) -> h3_datagram_error; code_to_error(16#3994bd84) -> wt_buffered_stream_rejected; code_to_error(16#170d7b68) -> wt_session_gone; code_to_error(Code) when Code >= 16#52e4a40fa8db, Code =< 16#52e5ac983162 -> case (Code - 16#21) rem 16#1f of 0 -> h3_no_error; _ -> %% @todo We need tests for this. Shifted = Code - 16#52e4a40fa8db, {wt_application_error, Shifted - Shifted div 16#1f} end; %% Unknown/reserved error codes must be treated %% as equivalent to H3_NO_ERROR. code_to_error(_) -> h3_no_error. -spec parse_int(binary()) -> {non_neg_integer(), binary()} | more. parse_int(<<0:2, Int:6, Rest/bits>>) -> {Int, Rest}; parse_int(<<1:2, Int:14, Rest/bits>>) -> {Int, Rest}; parse_int(<<2:2, Int:30, Rest/bits>>) -> {Int, Rest}; parse_int(<<3:2, Int:62, Rest/bits>>) -> {Int, Rest}; parse_int(_) -> more. %% Building. -spec data(iodata()) -> iolist(). data(Data) -> Len = encode_int(iolist_size(Data)), [<<0:8>>, Len, Data]. -spec headers(iodata()) -> iolist(). headers(HeaderBlock) -> Len = encode_int(iolist_size(HeaderBlock)), [<<1:8>>, Len, HeaderBlock]. -spec settings(settings()) -> iolist(). settings(Settings) when Settings =:= #{} -> <<4:8, 0:8>>; settings(Settings) -> Payload = settings_payload(Settings), Len = encode_int(iolist_size(Payload)), [<<4:8>>, Len, Payload]. settings_payload(Settings) -> Payload = [case Key of %% SETTINGS_QPACK_MAX_TABLE_CAPACITY (RFC9204). qpack_max_table_capacity when Value =:= 0 -> <<>>; qpack_max_table_capacity -> [encode_int(1), encode_int(Value)]; %% SETTINGS_MAX_FIELD_SECTION_SIZE (RFC9114). max_header_list_size when Value =:= infinity -> <<>>; max_header_list_size -> [encode_int(6), encode_int(Value)]; %% SETTINGS_QPACK_BLOCKED_STREAMS (RFC9204). qpack_blocked_streams when Value =:= 0 -> <<>>; qpack_blocked_streams -> [encode_int(1), encode_int(Value)]; %% SETTINGS_ENABLE_CONNECT_PROTOCOL (RFC9220). enable_connect_protocol when Value -> [encode_int(8), encode_int(1)]; enable_connect_protocol -> [encode_int(8), encode_int(0)]; %% SETTINGS_H3_DATAGRAM (RFC9297). h3_datagram when Value -> [encode_int(16#33), encode_int(1)]; h3_datagram -> [encode_int(16#33), encode_int(0)]; %% SETTINGS_ENABLE_WEBTRANSPORT (draft-ietf-webtrans-http3-02, for compatibility). enable_webtransport when Value -> [encode_int(16#2b603742), encode_int(1)]; enable_webtransport -> [encode_int(16#2b603742), encode_int(0)]; %% SETTINGS_WT_MAX_SESSIONS (draft-ietf-webtrans-http3). wt_max_sessions when Value =:= 0 -> <<>>; wt_max_sessions -> [encode_int(16#c671706a), encode_int(Value)]; %% SETTINGS_WT_INITIAL_MAX_STREAMS_UNI (draft-ietf-webtrans-http3). wt_initial_max_streams_uni when Value =:= 0 -> <<>>; wt_initial_max_streams_uni -> [encode_int(16#2b64), encode_int(Value)]; %% SETTINGS_WT_INITIAL_MAX_STREAMS_BIDI (draft-ietf-webtrans-http3). wt_initial_max_streams_bidi when Value =:= 0 -> <<>>; wt_initial_max_streams_bidi -> [encode_int(16#2b65), encode_int(Value)]; %% SETTINGS_WT_INITIAL_MAX_DATA (draft-ietf-webtrans-http3). wt_initial_max_data when Value =:= 0 -> <<>>; wt_initial_max_data -> [encode_int(16#2b61), encode_int(Value)] end || {Key, Value} <- maps:to_list(Settings)], %% Include one reserved identifier in addition. ReservedType = 16#1f * (rand:uniform(148764065110560900) - 1) + 16#21, [encode_int(ReservedType), encode_int(rand:uniform(15384) - 1)|Payload]. -spec webtransport_stream_header(stream_id(), unidi | bidi) -> iolist(). webtransport_stream_header(SessionID, StreamType) -> Signal = case StreamType of unidi -> 16#54; bidi -> 16#41 end, [encode_int(Signal), encode_int(SessionID)]. -spec datagram(stream_id(), iodata()) -> iolist(). datagram(SessionID, Data) -> QuarterID = SessionID div 4, [encode_int(QuarterID), Data]. -spec error_to_code(error()) -> non_neg_integer(). error_to_code(h3_no_error) -> %% Implementations should select a reserved error code %% with some probability when they would have sent H3_NO_ERROR. (RFC9114 8.1) case rand:uniform(2) of 1 -> 16#0100; 2 -> 16#1f * (rand:uniform(148764065110560900) - 1) + 16#21 end; error_to_code(h3_general_protocol_error) -> 16#0101; error_to_code(h3_internal_error) -> 16#0102; error_to_code(h3_stream_creation_error) -> 16#0103; error_to_code(h3_closed_critical_stream) -> 16#0104; error_to_code(h3_frame_unexpected) -> 16#0105; error_to_code(h3_frame_error) -> 16#0106; error_to_code(h3_excessive_load) -> 16#0107; error_to_code(h3_id_error) -> 16#0108; error_to_code(h3_settings_error) -> 16#0109; error_to_code(h3_missing_settings) -> 16#010a; error_to_code(h3_request_rejected) -> 16#010b; error_to_code(h3_request_cancelled) -> 16#010c; error_to_code(h3_request_incomplete) -> 16#010d; error_to_code(h3_message_error) -> 16#010e; error_to_code(h3_connect_error) -> 16#010f; error_to_code(h3_version_fallback) -> 16#0110; %% Extensions. error_to_code(h3_datagram_error) -> 16#33; error_to_code(wt_buffered_stream_rejected) -> 16#3994bd84; error_to_code(wt_session_gone) -> 16#170d7b68; error_to_code({wt_application_error, AppErrorCode}) -> 16#52e4a40fa8db + AppErrorCode + AppErrorCode div 16#1e. -spec encode_int(h3_non_neg_integer()) -> binary(). encode_int(I) when I < 64 -> <<0:2, I:6>>; encode_int(I) when I < 16384 -> <<1:2, I:14>>; encode_int(I) when I < 1073741824 -> <<2:2, I:30>>; encode_int(I) when I < 4611686018427387904 -> <<3:2, I:62>>. cowlib-2.19.0/src/cow_http3_machine.erl000066400000000000000000000721301523166326600177700ustar00rootroot00000000000000%% Copyright (c) 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_http3_machine). -export([init/2]). -export([init_unidi_local_streams/4]). -export([init_unidi_stream/3]). -export([set_unidi_remote_stream_type/3]). -export([init_bidi_stream/2]). -export([init_bidi_stream/3]). -export([become_webtransport_session/2]). -export([become_webtransport_stream/3]). -export([close_webtransport_session/2]). -export([close_bidi_stream_for_sending/2]). -export([close_stream/2]). -export([unidi_data/4]). -export([frame/4]). -export([ignored_frame/2]). -export([prepare_headers/5]). -export([prepare_trailers/3]). -export([reset_stream/2]). -export([get_bidi_stream_local_state/2]). -export([get_bidi_stream_remote_state/2]). -type opts() :: #{ enable_connect_protocol => boolean(), max_decode_blocked_streams => 0..16#3fffffffffffffff, max_decode_table_size => 0..16#3fffffffffffffff, max_encode_blocked_streams => 0..16#3fffffffffffffff, max_encode_table_size => 0..16#3fffffffffffffff }. -export_type([opts/0]). -type unidi_stream_dir() :: unidi_local | unidi_remote. -type unidi_stream_type() :: control | push | encoder | decoder. %% All stream types must have `id` as the first element %% of the record as the more general functions require it there. -record(unidi_stream, { id :: cow_http3:stream_id(), %% Unidi stream direction (local = we initiated). dir :: unidi_stream_dir(), %% Unidi stream type. type :: undefined | unidi_stream_type() }). -record(bidi_stream, { id :: cow_http3:stream_id(), %% Request method. method = undefined :: undefined | binary(), %% Whether we finished sending data. local = idle :: idle | cow_http:fin(), %% Whether we finished receiving data. remote = idle :: idle | cow_http:fin(), %% 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() }). -record(wt_session, { id :: cow_http3:stream_id() }). -record(wt_stream, { id :: cow_http3:stream_id(), %% All WT streams belong to a single WT session. session_id :: cow_http3:stream_id(), %% Unidi stream direction (local = we initiated) or bidi. dir :: unidi_stream_dir() | bidi }). -type stream() :: #unidi_stream{} | #bidi_stream{} | #wt_session{} | #wt_stream{}. -record(http3_machine, { %% Whether the HTTP/3 endpoint is a client or a server. mode :: client | server, %% Current state of the supported unidi streams: %% * the control stream must send SETTINGS as its first frame %% * none of these streams can be closed once they are open peer_control_state = no_stream :: no_stream | no_settings | ready, peer_decode_state = no_stream :: no_stream | ready, peer_encode_state = no_stream :: no_stream | ready, %% Maximum Push ID. max_push_id = -1 :: -1 | cow_http3:push_id(), %% Settings are separate for each endpoint. They are sent once %% at the beginning of the control stream. local_settings = #{ % enable_connect_protocol => false % max_decode_blocked_streams => 0, % max_decode_table_size => 0, % max_encode_blocked_streams => 0, % max_encode_table_size => 4096 } :: map(), %% Currently active HTTP/3 streams. Streams may be initiated either %% by the client or by the server through PUSH_PROMISE frames. streams = #{} :: #{cow_http3:stream_id() => stream()}, %% QPACK decoding and encoding state. decode_state :: cow_qpack:state(), encode_state :: cow_qpack:state() }). -opaque http3_machine() :: #http3_machine{}. -export_type([http3_machine/0]). -type instructions() :: undefined | {decoder_instructions | encoder_instructions, iodata()}. -spec init(client | server, opts()) -> {ok, iolist(), http3_machine()}. init(Mode, Opts) -> Settings = init_settings(Opts), {ok, cow_http3:settings(Settings), #http3_machine{ mode=Mode, local_settings=Settings, decode_state=init_decode_state(Opts), encode_state=init_encode_state(Opts) }}. init_settings(Opts) -> S0 = setting_from_opt(#{}, Opts, max_decode_table_size, qpack_max_table_capacity, 0), S1 = setting_from_opt(S0, Opts, max_decode_blocked_streams, qpack_blocked_streams, 0), %% @todo max_field_section_size S2 = setting_from_opt(S1, Opts, enable_connect_protocol, enable_connect_protocol, false), S3 = setting_from_opt(S2, Opts, h3_datagram, h3_datagram, false), %% For compatibility with draft-02. S4 = setting_from_opt(S3, Opts, enable_webtransport, enable_webtransport, false), setting_from_opt(S4, Opts, wt_max_sessions, wt_max_sessions, 0). setting_from_opt(Settings, Opts, OptName, SettingName, Default) -> case maps:get(OptName, Opts, Default) of Default -> Settings; Value -> Settings#{SettingName => Value} end. %% Note that only the decoder sends them as SETTINGS. init_decode_state(Opts) -> MaxTableCapacity = maps:get(max_decode_table_size, Opts, 0), MaxBlockedStreams = maps:get(max_decode_blocked_streams, Opts, 0), cow_qpack:init(decoder, MaxTableCapacity, MaxBlockedStreams). %% We want to use the dynamic table by default to improve %% compression ratio, but we do not allow blocked streams %% by default because they could lead to the latency being %% worse than otherwise. init_encode_state(Opts) -> MaxTableCapacity = maps:get(max_encode_table_size, Opts, 4096), MaxBlockedStreams = maps:get(max_encode_blocked_streams, Opts, 0), cow_qpack:init(encoder, MaxTableCapacity, MaxBlockedStreams). -spec init_unidi_local_streams(cow_http3:stream_id(), cow_http3:stream_id(), cow_http3:stream_id(), State) -> State when State::http3_machine(). init_unidi_local_streams(ControlID, EncoderID, DecoderID, State=#http3_machine{streams=Streams}) -> State#http3_machine{ streams=Streams#{ ControlID => #unidi_stream{id=ControlID, dir=unidi_local, type=control}, EncoderID => #unidi_stream{id=EncoderID, dir=unidi_local, type=encoder}, DecoderID => #unidi_stream{id=DecoderID, dir=unidi_local, type=decoder} }}. -spec init_unidi_stream(cow_http3:stream_id(), unidi_stream_dir(), State) -> State when State::http3_machine(). init_unidi_stream(StreamID, StreamDir, State=#http3_machine{streams=Streams}) -> State#http3_machine{streams=Streams#{StreamID => #unidi_stream{ id=StreamID, dir=StreamDir, type=undefined}}}. -spec set_unidi_remote_stream_type(cow_http3:stream_id(), unidi_stream_type(), State) -> {ok, State} | {error, {connection_error, h3_stream_creation_error, atom()}, State} when State::http3_machine(). set_unidi_remote_stream_type(StreamID, Type=control, State=#http3_machine{peer_control_state=no_stream}) -> Stream = stream_get(StreamID, State), {ok, stream_store(Stream#unidi_stream{type=Type}, State#http3_machine{peer_control_state=no_settings})}; set_unidi_remote_stream_type(_, control, State) -> {error, {connection_error, h3_stream_creation_error, 'A peer cannot open two control streams. (RFC9114 6.2.1)'}, State}; set_unidi_remote_stream_type(StreamID, Type=decoder, State=#http3_machine{peer_decode_state=no_stream}) -> Stream = stream_get(StreamID, State), {ok, stream_store(Stream#unidi_stream{type=Type}, State#http3_machine{peer_decode_state=ready})}; set_unidi_remote_stream_type(StreamID, Type=encoder, State=#http3_machine{peer_encode_state=no_stream}) -> Stream = stream_get(StreamID, State), {ok, stream_store(Stream#unidi_stream{type=Type}, State#http3_machine{peer_encode_state=ready})}; set_unidi_remote_stream_type(_, decoder, State) -> {error, {connection_error, h3_stream_creation_error, 'A peer cannot open two decoder streams. (RFC9204 4.2)'}, State}; set_unidi_remote_stream_type(_, encoder, State) -> {error, {connection_error, h3_stream_creation_error, 'A peer cannot open two encoder streams. (RFC9204 4.2)'}, State}. %% All bidi streams are request/response. %% We only need to know the method when in client mode. -spec init_bidi_stream(cow_http3:stream_id(), State) -> State when State::http3_machine(). init_bidi_stream(StreamID, State=#http3_machine{streams=Streams}) -> State#http3_machine{streams=Streams#{ StreamID => #bidi_stream{id=StreamID} }}. -spec init_bidi_stream(cow_http3:stream_id(), binary(), State) -> State when State::http3_machine(). init_bidi_stream(StreamID, Method, State=#http3_machine{streams=Streams}) -> State#http3_machine{streams=Streams#{ StreamID => #bidi_stream{id=StreamID, method=Method} }}. -spec become_webtransport_session(cow_http3:stream_id(), State) -> State when State::http3_machine(). become_webtransport_session(StreamID, State=#http3_machine{streams=Streams}) -> #{StreamID := #bidi_stream{}} = Streams, stream_store(#wt_session{id=StreamID}, State). -spec become_webtransport_stream(cow_http3:stream_id(), cow_http3:stream_id(), State) -> {ok, State} when State::http3_machine(). become_webtransport_stream(StreamID, SessionID, State0) -> %% First we check whether SessionID really exists and is a WT session. case stream_get(SessionID, State0) of #wt_session{} -> %% The stream becomes a WT stream tied to SessionID. Dir = case stream_get(StreamID, State0) of #unidi_stream{dir=Dir0} -> Dir0; %% @todo The bidi stream must be in idle state. #bidi_stream{} -> bidi end, State = stream_store(#wt_stream{ id=StreamID, session_id=SessionID, dir=Dir}, State0), {ok, State} %% @todo Error conditions. end. -spec close_webtransport_session(cow_http3:stream_id(), State) -> State when State::http3_machine(). close_webtransport_session(SessionID, State=#http3_machine{streams=Streams0}) -> #{SessionID := #wt_session{}} = Streams0, %% Remove all streams belonging to the session. Streams = maps:filtermap(fun (_, #wt_session{id=StreamID}) when StreamID =:= SessionID -> false; (_, #wt_stream{session_id=StreamID}) when StreamID =:= SessionID -> false; (_, _) -> true end, Streams0), State#http3_machine{streams=Streams}. -spec close_bidi_stream_for_sending(cow_http3:stream_id(), State) -> State when State::http3_machine(). close_bidi_stream_for_sending(StreamID, State=#http3_machine{streams=Streams}) -> #{StreamID := Stream} = Streams, stream_store(Stream#bidi_stream{local=fin}, State). -spec close_stream(cow_http3:stream_id(), State) -> {ok, State} | {error, {connection_error, h3_closed_critical_stream, atom()}, State} when State::http3_machine(). close_stream(StreamID, State=#http3_machine{streams=Streams0}) -> case maps:take(StreamID, Streams0) of {#unidi_stream{type=control}, Streams} -> {error, {connection_error, h3_closed_critical_stream, 'A control stream was closed. (RFC9114 6.2.1)'}, State#http3_machine{streams=Streams}}; {#unidi_stream{type=decoder}, Streams} -> {error, {connection_error, h3_closed_critical_stream, 'A decoder stream was closed. (RFC9204 4.2)'}, State#http3_machine{streams=Streams}}; {#unidi_stream{type=encoder}, Streams} -> {error, {connection_error, h3_closed_critical_stream, 'An encoder stream was closed. (RFC9204 4.2)'}, State#http3_machine{streams=Streams}}; {_, Streams} -> {ok, State#http3_machine{streams=Streams}} end. -spec unidi_data(binary(), cow_http:fin(), cow_http3:stream_id(), State) -> {ok, instructions(), State} | {error, {connection_error, cow_qpack:error(), atom()}, State} when State::http3_machine(). %% All currently supported unidi streams are critical. unidi_data(_, fin, _, State) -> {error, {connection_error, h3_closed_critical_stream, 'The FIN flag was set on an encoder or decoder stream. (RFC9204 4.2)'}, State}; unidi_data(Data, nofin, StreamID, State=#http3_machine{ decode_state=DecState0, encode_state=EncState0}) -> case stream_get(StreamID, State) of #unidi_stream{type=decoder} -> case cow_qpack:execute_decoder_instructions(Data, EncState0) of {ok, EncState} -> {ok, undefined, State#http3_machine{encode_state=EncState}}; Error = {connection_error, _, _} -> {error, Error, State} end; #unidi_stream{type=encoder} -> case cow_qpack:execute_encoder_instructions(Data, DecState0) of {ok, <<>>, DecState} -> {ok, undefined, State#http3_machine{decode_state=DecState}}; {ok, DecData, DecState} -> {ok, {decoder_instructions, DecData}, State#http3_machine{decode_state=DecState}}; Error = {connection_error, _, _} -> {error, Error, State} end end. -spec frame(cow_http3:frame(), cow_http:fin(), cow_http3:stream_id(), State) -> {ok, State} | {ok, {data, binary()}, State} | {ok, {headers, cow_http:headers(), cow_http:pseudo_headers(), non_neg_integer() | undefined}, instructions(), State} | {ok, {trailers, cow_http:headers()}, instructions(), State} | {ok, {goaway, cow_http3:stream_id() | cow_http3:push_id()}, State} | {error, {stream_error, h3_message_error, atom()}, instructions(), State} | {error, {connection_error, cow_http3:error() | cow_qpack:error(), atom()}, State} when State::http3_machine(). frame(Frame, IsFin, StreamID, State) -> case element(1, Frame) of data -> data_frame(Frame, IsFin, StreamID, State); headers -> headers_frame(Frame, IsFin, StreamID, State); cancel_push -> cancel_push_frame(Frame, IsFin, StreamID, State); settings -> settings_frame(Frame, IsFin, StreamID, State); push_promise -> push_promise_frame(Frame, IsFin, StreamID, State); goaway -> goaway_frame(Frame, IsFin, StreamID, State); max_push_id -> max_push_id_frame(Frame, IsFin, StreamID, State) end. %% DATA frame. data_frame(Frame={data, Data}, IsFin, StreamID, State) -> DataLen = byte_size(Data), case stream_get(StreamID, State) of Stream = #bidi_stream{remote=nofin} -> data_frame(Frame, IsFin, Stream, State, DataLen); #bidi_stream{remote=idle} -> {error, {connection_error, h3_frame_unexpected, 'DATA frame received before a HEADERS frame. (RFC9114 4.1)'}, State}; #bidi_stream{remote=fin} -> {error, {connection_error, h3_frame_unexpected, 'DATA frame received after trailer HEADERS frame. (RFC9114 4.1)'}, State}; #unidi_stream{type=control} -> control_frame(Frame, State) end. data_frame(Frame, IsFin, Stream0=#bidi_stream{remote_read_size=StreamRead}, State0, DataLen) -> Stream = Stream0#bidi_stream{remote=IsFin, remote_read_size=StreamRead + DataLen}, State = stream_store(Stream, State0), case is_body_size_valid(Stream) of true -> {ok, Frame, State}%; %% @todo Implement and update error type/message. % 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(#bidi_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(#bidi_stream{remote=nofin, remote_expected_size=Expected, remote_read_size=Read}) when Read > Expected -> false; is_body_size_valid(#bidi_stream{remote=nofin}) -> true; is_body_size_valid(#bidi_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. headers_frame(Frame, IsFin, StreamID, State=#http3_machine{mode=Mode}) -> case stream_get(StreamID, State) of %% Headers. Stream=#bidi_stream{remote=idle} -> headers_decode(Frame, IsFin, Stream, State, case Mode of server -> request; client -> response end); %% Trailers. Stream=#bidi_stream{remote=nofin} -> headers_decode(Frame, IsFin, Stream, State, trailers); %% Additional frame received after trailers. #bidi_stream{remote=fin} -> {error, {connection_error, h3_frame_unexpected, 'HEADERS frame received after trailer HEADERS frame. (RFC9114 4.1)'}, State}; #unidi_stream{type=control} -> control_frame(Frame, State) end. headers_decode({headers, EncodedFieldSection}, IsFin, Stream=#bidi_stream{id=StreamID}, State=#http3_machine{decode_state=DecodeState0}, Type) -> try cow_qpack:decode_field_section(EncodedFieldSection, StreamID, DecodeState0) of {ok, Headers, DecData, DecodeState} -> headers_process(Stream, State#http3_machine{decode_state=DecodeState}, IsFin, Type, DecData, Headers); Error = {connection_error, _, _} -> {error, Error, State} catch _:_ -> {error, {connection_error, qpack_decompression_failed, 'Exception while trying to decode QPACK-encoded header block. (RFC9204 2.2)'}, State} end. headers_process(Stream=#bidi_stream{method=ReqMethod}, State=#http3_machine{local_settings=LocalSettings}, IsFin, Type, DecData, Headers0) -> case cow_http:process_headers(Headers0, Type, ReqMethod, IsFin, LocalSettings) of %% @todo If this is a WebTransport request we also need to check a few %% other things such as h3_datagram, max_sessions and QUIC's max_datagram_size options. %% @todo So cow_http3_machine needs to know about at least some QUIC options. {headers, Headers, PseudoHeaders, Len} -> headers_frame(Stream, State, IsFin, Type, DecData, Headers, PseudoHeaders, Len); % {push_promise, Headers, PseudoHeaders} -> %% @todo Implement push promises. {trailers, Headers} -> trailers_frame(Stream, State, DecData, Headers); {error, Reason} -> {error, {stream_error, h3_message_error, format_error(Reason)}, %% We decoded the headers so must send the instructions if any. case DecData of <<>> -> undefined; _ -> {decoder_instructions, DecData} end, State} end. headers_frame(Stream0, State0, IsFin, Type, DecData, Headers, PseudoHeaders, Len) -> Stream = case Type of request -> TE = case lists:keyfind(<<"te">>, 1, Headers) of {_, TE0} -> TE0; false -> undefined end, Stream0#bidi_stream{method=maps:get(method, PseudoHeaders), remote=IsFin, remote_expected_size=Len, te=TE}; response -> case PseudoHeaders of #{status := Status} when Status >= 100, Status =< 199 -> Stream0; _ -> Stream0#bidi_stream{remote=IsFin, remote_expected_size=Len} end end, State = stream_store(Stream, State0), {ok, {headers, Headers, PseudoHeaders, Len}, case DecData of <<>> -> undefined; _ -> {decoder_instructions, DecData} end, State}. trailers_frame(Stream0, State0, DecData, Headers) -> Stream = Stream0#bidi_stream{remote=fin}, State = stream_store(Stream, State0), %% @todo Error out if we didn't get the full body. case is_body_size_valid(Stream) of true -> {ok, {trailers, Headers}, case DecData of <<>> -> undefined; _ -> {decoder_instructions, DecData} end, State}%; %% @todo Implement and update error type/message. % 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. format_error(connect_invalid_pseudo_header) -> 'CONNECT requests only use the :method and :authority pseudo-headers. (RFC9114 4.4)'; format_error(connect_missing_authority) -> 'CONNECT requests must include the :authority pseudo-header. (RFC9114 4.4)'; format_error(empty_header_name) -> 'Empty header names are not valid regular headers. (CVE-2019-9516)'; format_error(extended_connect_missing_protocol) -> 'Extended CONNECT requests must include the :protocol pseudo-header. (RFC9220, RFC8441 4)'; format_error(invalid_connection_header) -> 'The connection header is not allowed. (RFC9114 4.2)'; format_error(invalid_keep_alive_header) -> 'The keep-alive header is not allowed. (RFC9114 4.2)'; format_error(invalid_protocol_pseudo_header) -> 'The :protocol pseudo-header is only defined for the extended CONNECT. (RFC9220, RFC8441 4)'; format_error(invalid_proxy_authenticate_header) -> 'The proxy-authenticate header is not allowed. (RFC9114 4.2)'; format_error(invalid_proxy_authorization_header) -> 'The proxy-authorization header is not allowed. (RFC9114 4.2)'; format_error(invalid_pseudo_header) -> 'An unknown or invalid pseudo-header was found. (RFC9114 4.3)'; format_error(invalid_status_pseudo_header) -> 'The :status pseudo-header value is invalid. (RFC9114 4.3, RFC9114 4.3.2)'; format_error(invalid_te_header) -> 'The te header is only allowed in request headers. (RFC9114 4.2)'; format_error(invalid_te_value) -> 'The te header with a value other than "trailers" is not allowed. (RFC9114 4.2)'; format_error(invalid_transfer_encoding_header) -> 'The transfer-encoding header is not allowed. (RFC9114 4.1)'; format_error(invalid_upgrade_header) -> 'The upgrade header is not allowed. (RFC9114 4.2)'; format_error(missing_pseudo_header) -> 'A required pseudo-header was not found. (RFC9114 4.3.1, RFC9114 4.3.2)'; format_error(multiple_authority_pseudo_headers) -> 'Multiple :authority pseudo-headers were found. (RFC9114 4.3.1)'; format_error(multiple_method_pseudo_headers) -> 'Multiple :method pseudo-headers were found. (RFC9114 4.3.1)'; format_error(multiple_path_pseudo_headers) -> 'Multiple :path pseudo-headers were found. (RFC9114 4.3.1)'; format_error(multiple_protocol_pseudo_headers) -> 'Multiple :protocol pseudo-headers were found. (RFC9114 4.3.1)'; format_error(multiple_scheme_pseudo_headers) -> 'Multiple :scheme pseudo-headers were found. (RFC9114 4.3.1)'; format_error(multiple_status_pseudo_headers) -> 'Multiple :status pseudo-headers were found. (RFC9114 4.3.2)'; format_error(non_zero_length_with_fin_flag) -> 'HEADERS frame with the FIN flag contains a non-zero content-length. (RFC9114 4.1.2)'; format_error(pseudo_header_after_regular) -> 'Pseudo-headers were found after regular headers. (RFC9114 4.3)'; format_error(trailer_invalid_pseudo_header) -> 'Trailer header blocks must not contain pseudo-headers. (RFC9114 4.3)'; format_error(invalid_header_name) -> 'Header names must be lowercase token. (RFC9114 4.1.2, RFC9114 4.2)'; format_error(invalid_header_value) -> 'Header values must only contain valid characters. (RFC9114 4.1.2, RFC9114 4.2)'; format_error(Reason) -> cow_http:format_semantic_error(Reason). cancel_push_frame(Frame, _IsFin, StreamID, State) -> case stream_get(StreamID, State) of #unidi_stream{type=control} -> control_frame(Frame, State) end. settings_frame(Frame, _IsFin, StreamID, State) -> case stream_get(StreamID, State) of #unidi_stream{type=control} -> control_frame(Frame, State); #bidi_stream{} -> {error, {connection_error, h3_frame_unexpected, 'The SETTINGS frame is not allowed on a bidi stream. (RFC9114 7.2.4)'}, State} end. push_promise_frame(Frame, _IsFin, StreamID, State) -> case stream_get(StreamID, State) of #unidi_stream{type=control} -> control_frame(Frame, State) end. goaway_frame(Frame, _IsFin, StreamID, State) -> case stream_get(StreamID, State) of #unidi_stream{type=control} -> control_frame(Frame, State); #bidi_stream{} -> {error, {connection_error, h3_frame_unexpected, 'The GOAWAY frame is not allowed on a bidi stream. (RFC9114 7.2.6)'}, State} end. max_push_id_frame(Frame, _IsFin, StreamID, State) -> case stream_get(StreamID, State) of #unidi_stream{type=control} -> control_frame(Frame, State); #bidi_stream{} -> {error, {connection_error, h3_frame_unexpected, 'The MAX_PUSH_ID frame is not allowed on a bidi stream. (RFC9114 7.2.7)'}, State} end. control_frame({settings, Settings}, State=#http3_machine{ peer_control_state=no_settings, encode_state=EncState0}) -> %% @todo max_field_section_size %% Send the QPACK values to the encoder. MaxTableCapacity = maps:get(qpack_max_table_capacity, Settings, 0), MaxBlockedStreams = maps:get(qpack_blocked_streams, Settings, 0), EncState = cow_qpack:encoder_set_settings(MaxTableCapacity, MaxBlockedStreams, EncState0), {ok, State#http3_machine{peer_control_state=ready, encode_state=EncState}}; control_frame({settings, _}, State) -> {error, {connection_error, h3_frame_unexpected, 'The SETTINGS frame cannot be sent more than once. (RFC9114 7.2.4)'}, State}; control_frame(_Frame, State=#http3_machine{peer_control_state=no_settings}) -> {error, {connection_error, h3_missing_settings, 'The first frame on the control stream must be a SETTINGS frame. (RFC9114 6.2.1)'}, State}; control_frame(Frame = {goaway, _}, State) -> {ok, Frame, State}; %% @todo Implement server push. control_frame({max_push_id, PushID}, State=#http3_machine{max_push_id=MaxPushID}) -> if PushID >= MaxPushID -> {ok, State#http3_machine{max_push_id=PushID}}; true -> {error, {connection_error, h3_id_error, 'MAX_PUSH_ID must not be lower than previously received. (RFC9114 7.2.7)'}, State} end; control_frame(ignored_frame, State) -> {ok, State}; control_frame(_Frame, State) -> {error, {connection_error, h3_frame_unexpected, 'DATA and HEADERS frames are not allowed on the control stream. (RFC9114 7.2.1, RFC9114 7.2.2)'}, State}. %% Ignored frames. -spec ignored_frame(cow_http3:stream_id(), State) -> {ok, State} | {error, {connection_error, cow_http3:error(), atom()}, State} when State::http3_machine(). ignored_frame(StreamID, State) -> case stream_get(StreamID, State) of #unidi_stream{type=control} -> control_frame(ignored_frame, State); _ -> {ok, State} end. %% 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_http3:stream_id(), State, idle | cow_http:fin(), cow_http:pseudo_headers(), cow_http:headers()) -> {ok, cow_http:fin(), iodata(), instructions(), State} when State::http3_machine(). prepare_headers(StreamID, State=#http3_machine{encode_state=EncodeState0}, IsFin0, PseudoHeaders, Headers0) -> Stream = #bidi_stream{method=Method, local=idle} = stream_get(StreamID, State), IsFin = case {IsFin0, Method} of {idle, _} -> nofin; {_, <<"HEAD">>} -> fin; _ -> IsFin0 end, %% With QUIC we don't have a data queue so the local state %% can be updated immediately. LocalIsFin = case IsFin0 of idle -> idle; _ -> IsFin end, Headers = cow_http:merge_pseudo_headers(PseudoHeaders, cow_http:remove_http1_headers(Headers0)), {ok, HeaderBlock, EncData, EncodeState} = cow_qpack:encode_field_section(Headers, StreamID, EncodeState0), {ok, IsFin, HeaderBlock, case EncData of [] -> undefined; _ -> {encoder_instructions, EncData} end, stream_store(Stream#bidi_stream{local=LocalIsFin}, State#http3_machine{encode_state=EncodeState})}. -spec prepare_trailers(cow_http3:stream_id(), State, cow_http:headers()) -> {trailers, iodata(), instructions(), State} | {no_trailers, State} when State::http3_machine(). prepare_trailers(StreamID, State=#http3_machine{encode_state=EncodeState0}, Trailers) -> Stream = #bidi_stream{local=nofin, te=TE0} = stream_get(StreamID, State), 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 -> {ok, HeaderBlock, EncData, EncodeState} = cow_qpack:encode_field_section(Trailers, StreamID, EncodeState0), {trailers, HeaderBlock, case EncData of [] -> undefined; _ -> {encoder_instructions, EncData} end, stream_store(Stream#bidi_stream{local=fin}, State#http3_machine{encode_state=EncodeState})}; no_trailers -> {no_trailers, stream_store(Stream#bidi_stream{local=fin}, State)} end. %% Public interface to reset streams. -spec reset_stream(cow_http3:stream_id(), State) -> {ok, State} | {error, not_found} when State::http3_machine(). reset_stream(StreamID, State=#http3_machine{streams=Streams0}) -> case maps:take(StreamID, Streams0) of {_, Streams} -> {ok, State#http3_machine{streams=Streams}}; error -> {error, not_found} end. %% Retrieve the local state for a bidi stream. -spec get_bidi_stream_local_state(cow_http3:stream_id(), http3_machine()) -> {ok, idle | cow_http:fin()} | {error, not_found}. get_bidi_stream_local_state(StreamID, State) -> case stream_get(StreamID, State) of #bidi_stream{local=IsFin} -> {ok, IsFin}; %% Stream may never have been opened, or could have %% already been closed. undefined -> {error, not_found} end. %% Retrieve the remote state for a bidi stream. -spec get_bidi_stream_remote_state(cow_http3:stream_id(), http3_machine()) -> {ok, idle | cow_http:fin()} | {error, not_found}. get_bidi_stream_remote_state(StreamID, State) -> case stream_get(StreamID, State) of #bidi_stream{remote=IsFin} -> {ok, IsFin}; %% Stream may never have been opened, or could have %% already been closed. undefined -> {error, not_found} end. %% Stream-related functions. stream_get(StreamID, #http3_machine{streams=Streams}) -> maps:get(StreamID, Streams, undefined). stream_store(Stream, State=#http3_machine{streams=Streams}) -> StreamID = element(2, Stream), State#http3_machine{streams=Streams#{StreamID => Stream}}. cowlib-2.19.0/src/cow_http_hd.erl000066400000000000000000004113501523166326600166750ustar00rootroot00000000000000%% Copyright (c) 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]). -export([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_wt_available_protocols/1]). -export([wt_available_protocols/1]). -export([parse_wt_protocol/1]). -export([wt_protocol/1]). -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"). %% The limit of 17 digits ensure the produced integer %% is not a big int. -define(MAX_DIGITS, 17). -ifdef(TEST). -include_lib("stdlib/include/assert.hrl"). -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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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) when byte_size(Age) =< ?MAX_DIGITS -> 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() -> ?assertError(_, 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. -spec allow([binary()]) -> binary(). allow([]) -> <<>>; allow(Methods) -> << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>, Allow. -ifdef(TEST). allow_test_() -> Tests = [ {[], <<>>}, {[<<"GET">>, <<"HEAD">>, <<"PUT">>], <<"GET, HEAD, PUT">>} ], [{R, fun() -> R = allow(V) end} || {V, R} <- Tests]. -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(<< $=, R0/bits >>, Acc, T) when (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">>) -> {Delta, R} = digits(R0), cache_directive_list_sep(R, [{T, Delta}|Acc]); 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_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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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) when byte_size(ContentLength) =< ?MAX_DIGITS -> 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() -> ?assertError(_, 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 */", R/bits >>) -> {Complete, <<>>} = digits(R), {bytes, '*', Complete}; parse_content_range(<<"bytes ", R0/bits >>) -> {First, <<"-",R1/bits>>} = digits(R0), {Last, <<"/",R/bits>>} = digits(R1), case R of <<"*">> -> {bytes, First, Last, '*'}; _ -> {Complete, <<>>} = digits(R), {bytes, First, Last, Complete} end; parse_content_range(<< C, R/bits >>) when ?IS_TOKEN(C) -> ?LOWER(other_content_range_unit, R, <<>>). 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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) when byte_size(Port) =< 5 -> {<< 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) when byte_size(Port) =< 5 -> {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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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) when byte_size(MaxForwards) =< ?MAX_DIGITS -> 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() -> ?assertError(_, 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) when byte_size(Port) =< 5 -> lists:reverse([{Scheme, Host, binary_to_integer(Port)}|Acc]); origin_port(<< $\s, R/bits >>, Acc, Scheme, Host, Port) when byte_size(Port) =< 5 -> 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() -> ?assertError(_, 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(<< $-, R0/bits >>, Acc) -> {Suffix, R} = digits(R0), bytes_range_set_sep(R, [-Suffix|Acc]); bytes_range_set(R0, Acc) -> {First, <<"-",R1/bits>>} = digits(R0), case R1 of <> when ?IS_DIGIT(C) -> {Last, R} = digits(R1), bytes_range_set_sep(R, [{First, Last}|Acc]); _ -> bytes_range_set_sep(R1, [{First, infinity}|Acc]) end. 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() -> ?assertError(_, 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) -> true = byte_size(RetryAfter) =< ?MAX_DIGITS, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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(_, _, V) when V > 255 -> error(function_clause); 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 = [ <<>>, <<"256">> ], [{V, fun() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, 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() -> ?assertError(_, parse_vary(V)) end} || V <- Tests]. -endif. %% WT-Available-Protocols header. -spec parse_wt_available_protocols(binary()) -> [binary()]. parse_wt_available_protocols(Protocols) -> List = cow_http_struct_hd:parse_list(Protocols), [case Item of {item, {string, Value}, _} -> Value end || Item <- List]. -spec wt_available_protocols([binary()]) -> iolist(). wt_available_protocols(Protocols) -> cow_http_struct_hd:list([ {item, {string, Value}, []} || Value <- Protocols]). %% @todo Tests. %% WT-Protocol header. -spec parse_wt_protocol(binary()) -> binary(). parse_wt_protocol(WTProtocol) -> {item, {string, Value}, _} = cow_http_struct_hd:parse_item(WTProtocol), Value. -spec wt_protocol(iodata()) -> iolist(). wt_protocol(WTProtocol) -> cow_http_struct_hd:item({item, {string, WTProtocol}, []}). %% @todo Tests. %% 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() -> ?assertError(_, 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() -> ?assertError(_, 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]). %% Parse an integer. digits(Bin) -> digits(Bin, Bin, 0). digits(_, _, N) when N > ?MAX_DIGITS -> error(function_clause); digits(<>, Bin0, N) when ?IS_DIGIT(C) -> digits(R, Bin0, N + 1); digits(R, Bin0, N) -> Digits = binary_part(Bin0, 0, N), {binary_to_integer(Digits), R}. -ifdef(TEST). digits_test_() -> Tests = [ {<<"123">>, {123, <<>>}}, {<<"123, 456">>, {123, <<", 456">>}}, {<<"99999999999999999">>, {99999999999999999, <<>>}} ], [{V, fun() -> R = digits(V) end} || {V, R} <- Tests]. digits_error_test_() -> Tests = [ <<>>, <<"hello">>, <<"999999999999999999">> ], [{V, fun() -> ?assertError(_, digits(V)) end} || V <- Tests]. -endif. cowlib-2.19.0/src/cow_http_struct_hd.erl000066400000000000000000000413561523166326600203060ustar00rootroot00000000000000%% Copyright (c) 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) ). -ifdef(TEST). -include_lib("stdlib/include/assert.hrl"). -endif. %% 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 -> ?assertError(_, 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 -> try parse_item(Raw) of Expected -> ok catch error:_ -> ok end; <<"item">> when MustFail -> ?assertError(_, parse_item(Raw)); <<"list">> when MustFail; CanFail -> ?assertError(_, parse_list(Raw)); <<"dictionary">> -> Expected = parse_dictionary(Raw); <<"item">> -> Expected = parse_item(Raw); <<"list">> -> Expected = 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, <<>>), $"]; bare_item({token, Token}) -> ok = validate_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">>. validate_token(<>) when ?IS_ALPHA(C) or (C =:= $*) -> validate_token1(R). validate_token1(<>) when ?IS_TOKEN(C) or (C =:= $:) or (C =:= $/) -> validate_token1(R); validate_token1(<<>>) -> ok. 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]). struct_hd_item_test() -> <<"token">> = iolist_to_binary(item({item, {token, <<"token">>}, []})), ?assertError(_, item({item, {token, <<"tok\0en">>}, []})), ok. -endif. cowlib-2.19.0/src/cow_http_te.erl000066400000000000000000000322411523166326600167100ustar00rootroot00000000000000%% Copyright (c) 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). -include_lib("stdlib/include/assert.hrl"). 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, no_trailers, 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, no_trailers, Rest} end. -spec identity(Data) -> Data when Data::iodata(). identity(Data) -> Data. -ifdef(TEST). stream_identity_test() -> {done, <<>>, no_trailers, <<>>} = stream_identity(identity(<<>>), {0, 0}), {done, <<"\r\n">>, no_trailers, <<>>} = stream_identity(identity(<<"\r\n">>), {0, 2}), {done, << 0:80000 >>, no_trailers, <<>>} = 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 >>, no_trailers, <<>>} = 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, 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, D) when D < 16 -> chunked_len(R, S, A, Len * 16, D + 1); chunked_len(<< $1, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 1, D + 1); chunked_len(<< $2, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 2, D + 1); chunked_len(<< $3, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 3, D + 1); chunked_len(<< $4, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 4, D + 1); chunked_len(<< $5, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 5, D + 1); chunked_len(<< $6, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 6, D + 1); chunked_len(<< $7, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 7, D + 1); chunked_len(<< $8, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 8, D + 1); chunked_len(<< $9, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 9, D + 1); chunked_len(<< $A, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 10, D + 1); chunked_len(<< $B, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 11, D + 1); chunked_len(<< $C, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 12, D + 1); chunked_len(<< $D, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 13, D + 1); chunked_len(<< $E, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 14, D + 1); chunked_len(<< $F, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 15, D + 1); chunked_len(<< $a, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 10, D + 1); chunked_len(<< $b, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 11, D + 1); chunked_len(<< $c, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 12, D + 1); chunked_len(<< $d, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 13, D + 1); chunked_len(<< $e, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 14, D + 1); chunked_len(<< $f, R/bits >>, S, A, Len, D) when D < 16 -> chunked_len(R, S, A, Len * 16 + 15, D + 1); %% 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, 0); skip_chunk_ext(R = <<>>, S, A, Len, _) -> chunked_len(R, S, A, Len, 0); %% 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}), {more, <<>>, 18446744073709551617, _} = stream_chunked(<<"FFFFFFFFFFFFFFFF\r\n">>, {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}}, {<<"10000000000000000\r\n">>, {0, 0}} ], [{lists:flatten(io_lib:format("value ~p state ~p", [V, S])), fun() -> ?assertError(_, 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. cowlib-2.19.0/src/cow_iolists.erl000066400000000000000000000063531523166326600167340ustar00rootroot00000000000000%% Copyright (c) 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. cowlib-2.19.0/src/cow_link.erl000066400000000000000000000362261523166326600162050ustar00rootroot00000000000000%% Copyright (c) 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. cowlib-2.19.0/src/cow_mimetypes.erl000066400000000000000000002066621523166326600172670ustar00rootroot00000000000000%% Copyright (c) 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(<<"avif">>) -> {<<"image">>, <<"avif">>, []}; 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(<<"ggs">>) -> {<<"application">>, <<"vnd.geogebra.slides">>, []}; 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(<<"json">>) -> {<<"application">>, <<"json">>, []}; all_ext(<<"jsonml">>) -> {<<"application">>, <<"jsonml+json">>, []}; all_ext(<<"js">>) -> {<<"text">>, <<"javascript">>, []}; all_ext(<<"jxl">>) -> {<<"image">>, <<"jxl">>, []}; 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(<<"m2ts">>) -> {<<"video">>, <<"mp2t">>, []}; all_ext(<<"m2t">>) -> {<<"video">>, <<"mp2t">>, []}; 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(<<"mjs">>) -> {<<"text">>, <<"javascript">>, []}; 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">>) -> {<<"video">>, <<"mp2t">>, []}; 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(<<"opus">>) -> {<<"audio">>, <<"ogg">>, []}; 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(<<"ts">>) -> {<<"video">>, <<"mp2t">>, []}; 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(<<"wasm">>) -> {<<"application">>, <<"wasm">>, []}; 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(<<"mjs">>) -> {<<"text">>, <<"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">>, []}. cowlib-2.19.0/src/cow_mimetypes.erl.src000066400000000000000000000051371523166326600200470ustar00rootroot00000000000000%% Copyright (c) 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(<<"mjs">>) -> {<<"text">>, <<"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">>, []}. cowlib-2.19.0/src/cow_multipart.erl000066400000000000000000000650471523166326600172740ustar00rootroot00000000000000%% Copyright (c) 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); $\0 -> error(invalid_header_name); _ -> ?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(<< $\0, _/bits >>, _, _, _) -> error(invalid_header_value); 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. parse_headers_reject_nul_test_() -> Boundary = <<"deadbeef">>, Tests = [ <<"--deadbeef\r\n", 0, "x-bad: value\r\n\r\nbody\r\n--deadbeef--">>, <<"--deadbeef\r\nx-", 0, "bad: value\r\n\r\nbody\r\n--deadbeef--">>, <<"--deadbeef\r\nx-bad", 0, ": value\r\n\r\nbody\r\n--deadbeef--">>, <<"--deadbeef\r\nx-bad: ", 0, "value\r\n\r\nbody\r\n--deadbeef--">>, <<"--deadbeef\r\nx-bad: val", 0, "ue\r\n\r\nbody\r\n--deadbeef--">>, <<"--deadbeef\r\nx-bad: value", 0, "\r\n\r\nbody\r\n--deadbeef--">>, <<"--deadbeef\r\n" "content-disposition: form-data; name=\"a\"; " "filename=\"fi", 0, "le.txt\"\r\n" "\r\nbody\r\n--deadbeef--">> ], [{iolist_to_binary(V), fun() -> ok = try parse_headers(iolist_to_binary(V), Boundary) of _ -> error(unexpected_success) catch error:invalid_header_name -> ok; error:invalid_header_value -> ok end end} || V <- Tests]. 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 >>). cowlib-2.19.0/src/cow_qpack.erl000066400000000000000000001754511523166326600163530ustar00rootroot00000000000000%% Copyright (c) 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_qpack). -dialyzer(no_improper_lists). -export([init/1]). -export([init/3]). -export([decode_field_section/3]). -export([execute_encoder_instructions/2]). -export([decoder_cancel_stream/1]). %% @todo Use it. -export([encode_field_section/3]). -export([encode_field_section/4]). -export([execute_decoder_instructions/2]). -export([encoder_set_settings/3]). -record(state, { %% Configuration. %% %% For the encoder these values will be set to %% the lowest value between configuration and SETTINGS. %% Whether the configured values can be used. The %% decoder can always use the configured values. %% The encoder must wait for the SETTINGS frame. settings_received :: boolean(), %% Maximum size of the table. max_table_capacity = 0 :: non_neg_integer(), %% Maximum number of potentially blocked streams. max_blocked_streams = 0 :: non_neg_integer(), %% Dynamic table. %% The current max table capacity after the encoder %% sent an instruction to change the capacity. capacity = 0 :: non_neg_integer(), %% The size of each entry is len(Name) + len(Value) + 32. size = 0 :: non_neg_integer(), %% The number of entries ever inserted in the dynamic table. %% This value is used on the decoder's size to know whether %% it can decode a field section; and on both sides to find %% entries in the dynamic table. insert_count = 0 :: non_neg_integer(), %% The dynamic table. The first value is the size of the entry %% and the second value the entry (Name, Value tuple). The %% order of the entries is from newest to oldest. %% %% If 4 entries were inserted, the index of each entry would %% be [3, 2, 1, 0]. If 4 entries were inserted and 1 of them %% was later dropped, the index of each entry remaining would %% be [3, 2, 1] and the insert_count value would be 3, allowing %% us to know what index the newest entry is using. dyn_table = [] :: [{pos_integer(), {binary(), binary()}}], %% Decoder-specific state. %% We keep track of streams that are currently blocked %% in a map for easy counting and removal. A stream may %% be blocked at the beginning of the decoding process. %% A stream may be unblocked after encoder instructions %% have been processed. blocked_streams = #{} :: #{cow_http3:stream_id() => true}, %% Encoder-specific state. %% We keep track of the known received count of the %% decoder (the insert_count it has that we know of) %% so that we know when we can evict an entry we %% inserted. We cannot evict an entry before it has %% been acknowledged. The known received count can %% also be used to avoid blocking. known_received_count = 0 :: non_neg_integer(), %% We keep track of the streams that have used the %% dynamic table in order to increase the known %% received count when the decoder acks a stream. %% We only keep the insert_count value for a stream's %% field section. %% %% Because a stream can send multiple field sections %% (informational response, final response, trailers), %% we use a list to keep track of the different sections. %% A FIFO structure would be more adequate but we do %% not expect a lot of field sections per stream. references = #{} :: #{cow_http3:stream_id() => [non_neg_integer()]}, %% Smallest absolute index the encoder will reference. %% Indexes below may exist in the dynamic table but are %% in the process of being phased out and will eventually %% be evicted. Only duplicating these indexes is allowed. draining_index = 0 :: non_neg_integer(), %% Size of the dynamic table that is available for %% eviction during encoding. Both this value and the %% draining_index are computed at the start of encoding. %% Note that for the encoder this cannot reach negatives, %% but might for the decoder. draining_size = 0 :: integer() }). -opaque state() :: #state{}. -export_type([state/0]). -type error() :: qpack_decompression_failed | qpack_encoder_stream_error | qpack_decoder_stream_error. -export_type([error/0]). -type encoder_opts() :: #{ huffman => boolean() }. -export_type([encoder_opts/0]). %-ifdef(TEST). %-include_lib("proper/include/proper.hrl"). %-endif. -include("cow_hpack_common.hrl"). %% State initialization. -spec init(decoder | encoder) -> state(). init(Role) -> init(Role, 4096, 0). -spec init(decoder | encoder, non_neg_integer(), non_neg_integer()) -> state(). init(Role, MaxTableCapacity, MaxBlockedStreams) -> #state{ settings_received=Role =:= decoder, max_table_capacity=MaxTableCapacity, max_blocked_streams=MaxBlockedStreams }. %% Decoding. -spec decode_field_section(binary(), cow_http3:stream_id(), State) -> {ok, cow_http:headers(), binary(), State} | {blocked, State} | {connection_error, error(), atom()} when State::state(). decode_field_section(Data, StreamID, State=#state{max_blocked_streams=MaxBlockedStreams, insert_count=InsertCount, blocked_streams=BlockedStreams}) -> {EncInsertCount, Rest} = dec_big_int(Data, 0, 0), ReqInsertCount = decode_req_insert_count(EncInsertCount, State), if ReqInsertCount =< InsertCount -> decode_field_section(Rest, StreamID, State, ReqInsertCount); %% The stream is blocked and we do not allow that; %% or there are already too many blocked streams. map_size(BlockedStreams) > MaxBlockedStreams -> {connection_error, qpack_decompression_failed, 'More blocked streams than configuration allows. (RFC9204 2.1.2)'}; %% The stream is blocked and we allow that. %% The caller must keep the data and retry after %% calling the execute_encoder_instructions function. true -> {blocked, State#state{blocked_streams=BlockedStreams#{StreamID => true}}} end. decode_field_section(<>, StreamID, State0=#state{blocked_streams=BlockedStreams}, ReqInsertCount) -> State1 = State0#state{ %% The stream may have been blocked. Unblock it. blocked_streams=maps:remove(StreamID, BlockedStreams), %% Reset the draining_size. We don't use it, but don't %% want the value to unnecessarily become a big int. draining_size=0 }, {DeltaBase, Rest} = dec_int7(Rest0), Base = case S of 0 -> ReqInsertCount + DeltaBase; 1 -> ReqInsertCount - DeltaBase - 1 end, case decode(Rest, State1, Base, []) of {ok, Headers, State} when ReqInsertCount =:= 0 -> {ok, Headers, <<>>, State}; {ok, Headers, State} -> {ok, Headers, enc_int7(StreamID, 2#1), State} end. decode_req_insert_count(0, _) -> 0; decode_req_insert_count(EncInsertCount, #state{ max_table_capacity=MaxTableCapacity, insert_count=InsertCount}) -> MaxEntries = MaxTableCapacity div 32, FullRange = 2 * MaxEntries, if EncInsertCount > FullRange -> {connection_error, qpack_decompression_failed, 'EncInsertCount larger than maximum possible value. (RFC9204 4.5.1.1)'}; true -> MaxValue = InsertCount + MaxEntries, MaxWrapped = (MaxValue div FullRange) * FullRange, ReqInsertCount = MaxWrapped + EncInsertCount - 1, if ReqInsertCount > MaxValue -> if ReqInsertCount =< FullRange -> {connection_error, qpack_decompression_failed, 'ReqInsertCount value larger than current maximum value. (RFC9204 4.5.1.1)'}; true -> ReqInsertCount - FullRange end; ReqInsertCount =:= 0 -> {connection_error, qpack_decompression_failed, 'ReqInsertCount value of 0 must be encoded as 0. (RFC9204 4.5.1.1)'}; true -> ReqInsertCount end end. decode(<<>>, State, _, Acc) -> {ok, lists:reverse(Acc), State}; %% Indexed field line. decode(<<2#1:1,T:1,Rest0/bits>>, State, Base, Acc) -> {Index, Rest} = dec_int6(Rest0), Entry = case T of 0 -> table_get_dyn_pre_base(Index, Base, State); 1 -> table_get_static(Index) end, decode(Rest, State, Base, [Entry|Acc]); %% Indexed field line with post-base index. decode(<<2#0001:4,Rest0/bits>>, State, Base, Acc) -> {Index, Rest} = dec_int4(Rest0), Entry = table_get_dyn_post_base(Index, Base, State), decode(Rest, State, Base, [Entry|Acc]); %% Literal field line with name reference. decode(<<2#01:2,_N:1,T:1,Rest0/bits>>, State, Base, Acc) -> %% @todo N=1 the encoded field line MUST be encoded as literal, need to return metadata about this? {NameIndex, <>} = dec_int4(Rest0), Name = case T of 0 -> table_get_name_dyn_rel(NameIndex, State); 1 -> table_get_name_static(NameIndex) end, {ValueLen, Rest2} = dec_int7(Rest1), {Value, Rest} = maybe_dec_huffman(Rest2, ValueLen, H), decode(Rest, State, Base, [{Name, Value}|Acc]); %% Literal field line with post-base name reference. decode(<<2#0000:4,_N:1,Rest0/bits>>, State, Base, Acc) -> %% @todo N=1 the encoded field line MUST be encoded as literal, need to return metadata about this? {NameIndex, <>} = dec_int3(Rest0), Name = table_get_name_dyn_post_base(NameIndex, Base, State), {ValueLen, Rest2} = dec_int7(Rest1), {Value, Rest} = maybe_dec_huffman(Rest2, ValueLen, H), decode(Rest, State, Base, [{Name, Value}|Acc]); %% Literal field line with literal name. decode(<<2#001:3,_N:1,NameH:1,Rest0/bits>>, State, Base, Acc) -> %% @todo N=1 the encoded field line MUST be encoded as literal, need to return metadata about this? {NameLen, Rest1} = dec_int3(Rest0), <> = Rest1, {Name, <<>>} = maybe_dec_huffman(NameStr, NameLen, NameH), {ValueLen, Rest3} = dec_int7(Rest2), {Value, Rest} = maybe_dec_huffman(Rest3, ValueLen, ValueH), decode(Rest, State, Base, [{Name, Value}|Acc]). -spec execute_encoder_instructions(binary(), State) -> {ok, binary(), State} | {connection_error, qpack_encoder_stream_error, atom()} when State::state(). execute_encoder_instructions(Data, State) -> execute_encoder_instructions(Data, State, 0). execute_encoder_instructions(<<>>, State, 0) -> {ok, <<>>, State}; execute_encoder_instructions(<<>>, State, Increment) -> {ok, enc_int6(Increment, 2#00), State}; %% Set dynamic table capacity. execute_encoder_instructions(<<2#001:3,Rest0/bits>>, State=#state{ max_table_capacity=MaxTableCapacity, capacity=Capacity0, dyn_table=DynamicTable0}, Increment) -> {Capacity, Rest} = dec_int5(Rest0), if %% Capacity larger than configured, or dynamic table %% disabled when max_table_capacity=0. Capacity > MaxTableCapacity -> {connection_error, qpack_encoder_stream_error, 'New table capacity higher than SETTINGS_QPACK_MAX_TABLE_CAPACITY. (RFC9204 3.2.3, RFC9204 4.3.1)'}; %% Table capacity was reduced. We must evict entries. Capacity < Capacity0 -> {DynamicTable, Size} = table_evict(DynamicTable0, Capacity, 0, []), execute_encoder_instructions(Rest, State#state{capacity=Capacity, size=Size, dyn_table=DynamicTable}, Increment); %% Table capacity equal or higher than previous. true -> execute_encoder_instructions(Rest, State#state{capacity=Capacity}, Increment) end; %% Insert with name reference. execute_encoder_instructions(<<2#1:1,T:1,Rest0/bits>>, State, Increment) -> {NameIndex, <>} = dec_int6(Rest0), Name = case T of 0 -> table_get_name_dyn_rel(NameIndex, State); 1 -> table_get_name_static(NameIndex) end, {ValueLen, Rest2} = dec_int7(Rest1), {Value, Rest} = maybe_dec_huffman(Rest2, ValueLen, H), execute_insert_instruction(Rest, State, Increment, {Name, Value}); %% Insert with literal name. execute_encoder_instructions(<<2#01:2,NameH:1,Rest0/bits>>, State, Increment) -> {NameLen, Rest1} = dec_int5(Rest0), {Name, <>} = maybe_dec_huffman(Rest1, NameLen, NameH), {ValueLen, Rest3} = dec_int7(Rest2), {Value, Rest} = maybe_dec_huffman(Rest3, ValueLen, ValueH), execute_insert_instruction(Rest, State, Increment, {Name, Value}); %% Duplicate. execute_encoder_instructions(<<2#000:3,Rest0/bits>>, State, Increment) -> {Index, Rest} = dec_int5(Rest0), Entry = table_get_dyn_rel(Index, State), execute_insert_instruction(Rest, State, Increment, Entry). execute_insert_instruction(Rest, State0, Increment, Entry) -> case table_insert(Entry, State0) of {ok, State} -> execute_encoder_instructions(Rest, State, Increment + 1); Error = {connection_error, _, _} -> Error end. %% @todo Export / spec. decoder_cancel_stream(StreamID) -> enc_int6(StreamID, 2#01). dec_int3(<<2#111:3,Rest/bits>>) -> dec_big_int(Rest, 7, 0); dec_int3(<>) -> {Int, Rest}. dec_int4(<<2#1111:4,Rest/bits>>) -> dec_big_int(Rest, 15, 0); dec_int4(<>) -> {Int, Rest}. dec_int6(<<2#111111:6,Rest/bits>>) -> dec_big_int(Rest, 63, 0); dec_int6(<>) -> {Int, Rest}. dec_int7(<<2#1111111:7,Rest/bits>>) -> dec_big_int(Rest, 127, 0); dec_int7(<>) -> {Int, Rest}. maybe_dec_huffman(Data, ValueLen, 0) -> <> = Data, {Value, Rest}; maybe_dec_huffman(Data, ValueLen, 1) -> dec_huffman(Data, ValueLen, 0, <<>>). -ifdef(TEST). appendix_b_decoder_test() -> %% Stream: 0 {ok, [ {<<":path">>, <<"/index.html">>} ], <<>>, DecState0} = decode_field_section(<< 16#0000:16, 16#510b:16, 16#2f69:16, 16#6e64:16, 16#6578:16, 16#2e68:16, 16#746d:16, 16#6c >>, 0, init(decoder, 4096, 0)), #state{ capacity=0, size=0, insert_count=0, dyn_table=[] } = DecState0, %% Stream: Encoder {ok, EncData1, DecState1} = execute_encoder_instructions(<< 16#3fbd01:24, 16#c00f:16, 16#7777:16, 16#772e:16, 16#6578:16, 16#616d:16, 16#706c:16, 16#652e:16, 16#636f:16, 16#6d, 16#c10c:16, 16#2f73:16, 16#616d:16, 16#706c:16, 16#652f:16, 16#7061:16, 16#7468:16 >>, DecState0), <<2#00:2,2:6>> = EncData1, #state{ capacity=220, size=106, insert_count=2, %% The dynamic table is in reverse order. dyn_table=[ {49, {<<":path">>, <<"/sample/path">>}}, {57, {<<":authority">>, <<"www.example.com">>}} ] } = DecState1, %% Stream: 4 {ok, [ {<<":authority">>, <<"www.example.com">>}, {<<":path">>, <<"/sample/path">>} ], <<16#84>>, DecState2} = decode_field_section(<< 16#0381:16, 16#10, 16#11 >>, 4, DecState1), DecState1 = DecState2, %% Stream: Encoder {ok, EncData3, DecState3} = execute_encoder_instructions(<< 16#4a63:16, 16#7573:16, 16#746f:16, 16#6d2d:16, 16#6b65:16, 16#790c:16, 16#6375:16, 16#7374:16, 16#6f6d:16, 16#2d76:16, 16#616c:16, 16#7565:16 >>, DecState2), <<2#00:2,1:6>> = EncData3, #state{ capacity=220, size=160, insert_count=3, dyn_table=[ {54, {<<"custom-key">>, <<"custom-value">>}}, {49, {<<":path">>, <<"/sample/path">>}}, {57, {<<":authority">>, <<"www.example.com">>}} ] } = DecState3, %% Stream: Encoder {ok, EncData4, DecState4} = execute_encoder_instructions(<< 16#02 >>, DecState3), <<2#00:2,1:6>> = EncData4, #state{ capacity=220, size=217, insert_count=4, dyn_table=[ {57, {<<":authority">>, <<"www.example.com">>}}, {54, {<<"custom-key">>, <<"custom-value">>}}, {49, {<<":path">>, <<"/sample/path">>}}, {57, {<<":authority">>, <<"www.example.com">>}} ] } = DecState4, %% Stream: 8 %% %% Note that this one is not really received by the decoder %% so we will ignore the decoder state before we continue. {ok, [ {<<":authority">>, <<"www.example.com">>}, {<<":path">>, <<"/">>}, {<<"custom-key">>, <<"custom-value">>} ], <<16#88>>, IgnoredDecState} = decode_field_section(<< 16#0500:16, 16#80, 16#c1, 16#81 >>, 8, DecState4), %% Note that the state did not change anyway. DecState4 = IgnoredDecState, %% Stream: Decoder - Stream Cancellation (Stream=8) <<16#48>> = decoder_cancel_stream(8), {ok, EncData5, DecState5} = execute_encoder_instructions(<< 16#810d:16, 16#6375:16, 16#7374:16, 16#6f6d:16, 16#2d76:16, 16#616c:16, 16#7565:16, 16#32 >>, DecState4), <<2#00:2,1:6>> = EncData5, #state{ capacity=220, size=215, insert_count=5, dyn_table=[ {55, {<<"custom-key">>, <<"custom-value2">>}}, {57, {<<":authority">>, <<"www.example.com">>}}, {54, {<<"custom-key">>, <<"custom-value">>}}, {49, {<<":path">>, <<"/sample/path">>}} ] } = DecState5, ok. -endif. %% Encoding. -spec encode_field_section(cow_http:headers(), cow_http3:stream_id(), State) -> {ok, iolist(), iolist(), State} when State::state(). %% @todo Would be good to know encoder stream flow control to avoid writing there. Opts? encode_field_section(Headers, StreamID, State0) -> encode_field_section(Headers, StreamID, State0, #{}). -spec encode_field_section(cow_http:headers(), cow_http3:stream_id(), State, encoder_opts()) -> {ok, iolist(), iolist(), State} when State::state(). encode_field_section(Headers, StreamID, State0=#state{ max_table_capacity=MaxTableCapacity, insert_count=InsertCount, references=Refs0}, Opts) -> State1 = encode_update_drain_info(State0), Base = InsertCount + 1, {ReqInsertCount, EncData, Data, State} = encode( Headers, StreamID, State1, huffman_opt(Opts), 0, Base, [], []), case ReqInsertCount of 0 -> {ok, [<<0:16>>|Data], EncData, State}; _ -> MaxEntries = MaxTableCapacity div 32, EncInsertCount = (ReqInsertCount rem (2 * MaxEntries)) + 1, {S, DeltaBase} = if %% We inserted new entries. ReqInsertCount > Base -> {2#1, ReqInsertCount - Base}; %% We only used existing entries. ReqInsertCount =< Base -> {2#0, ReqInsertCount - Base} end, %% Save the reference to avoid draining entries too quickly. Refs = case Refs0 of #{StreamID := ICs} -> Refs0#{StreamID => [ReqInsertCount|ICs]}; _ -> Refs0#{StreamID => [ReqInsertCount]} end, {ok, [enc_big_int(EncInsertCount, <<>>), enc_int7(DeltaBase, S)|Data], EncData, State#state{references=Refs}} end. %% We check how many entries we can evict. The result %% will take the form of a draining_index (the oldest %% entry the encoder can reference) as well as a %% draining_size (how much data can be gained by evicting). %% %% We first look at streams that have not been acknowledged %% and find the smallest insert_count value from them. We %% cannot evict any value that is newer than or equal to %% that value. %% %% Then we also need to make sure we don't evict too much %% from the table. %% %% Finally we go over the dynamic table to count how much %% we can actually drain and what the draining index really is. encode_update_drain_info(State=#state{max_table_capacity=MaxCapacity, insert_count=InsertCount, dyn_table=DynTable, references=Refs}) -> PendingInsertCount = if %% When we don't use the dynamic table, or we didn't insert %% anything yet, there are no references. We can drain %% everything but are still constrained by the max draining size. Refs =:= #{} -> InsertCount; true -> maps:fold(fun(_, ICs, V) -> IC = hd(lists:reverse(ICs)), case V of undefined -> IC; _ -> min(IC, V) end end, undefined, Refs) end, %% We use a simple formula for calculating the maximum %% draining size, found in nginx: we allow evicting %% between 1/8th of the current table capacity and %% 512 bytes, whichever is smaller. When the maximum %% table capacity is small this formula may get us %% a value that's too small to drain anything, so %% we use 64 bytes as a minimum. MaxDrainingSize0 = min(512, MaxCapacity div 8), MaxDrainingSize = if MaxDrainingSize0 < 64 -> 64; true -> MaxDrainingSize0 end, {DrainingIndex, DrainingSize} = encode_update_drain_loop(lists:reverse(DynTable), InsertCount - length(DynTable), PendingInsertCount, 0, MaxDrainingSize), State#state{ draining_index=DrainingIndex, draining_size=DrainingSize }. %% We go over the dynamic table in reverse order. We stop %% when we either reach the PendingInsertCount value or get %% above MaxDrainingSize. It's not possible to go over the %% entire dynamic table because we have references. encode_update_drain_loop(_, Index, PendingIndex, Size, _) when Index =:= PendingIndex -> {Index, Size}; encode_update_drain_loop([{EntrySize, _}|_], Index, _, Size, MaxSize) when Size + EntrySize > MaxSize -> {Index, Size}; encode_update_drain_loop([{EntrySize, _}|Tail], Index, PendingIndex, Size, MaxSize) -> encode_update_drain_loop(Tail, Index + 1, PendingIndex, Size + EntrySize, MaxSize). encode([], _StreamID, State, _HuffmanOpt, ReqInsertCount, _Base, EncAcc, Acc) -> {ReqInsertCount, lists:reverse(EncAcc), lists:reverse(Acc), State}; encode([{Name, Value0}|Tail], StreamID, State, HuffmanOpt, ReqInsertCount, Base, EncAcc, Acc) -> %% We conditionally call iolist_to_binary/1 because a small %% but noticeable speed improvement happens when we do this. %% (Or at least it did for cow_hpack.) Value = if is_binary(Value0) -> Value0; true -> iolist_to_binary(Value0) end, Entry = {Name, Value}, encode_static([Entry|Tail], StreamID, State, HuffmanOpt, ReqInsertCount, Base, EncAcc, Acc). encode_static([Entry|Tail], StreamID, State, HuffmanOpt, ReqInsertCount, Base, EncAcc, Acc) -> case table_find_static(Entry) of not_found -> encode_dyn([Entry|Tail], StreamID, State, HuffmanOpt, ReqInsertCount, Base, EncAcc, Acc); StaticIndex -> encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base, EncAcc, %% Indexed Field Line. T=1 (static). [enc_int6(StaticIndex, 2#11)|Acc]) end. encode_dyn([Entry|Tail], StreamID, State0=#state{draining_index=DrainingIndex}, HuffmanOpt, ReqInsertCount0, Base, EncAcc, Acc) -> case table_find_dyn(Entry, State0) of not_found -> encode_static_name([Entry|Tail], StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc, Acc); %% When the index is below the drain index and there is enough %% space in the table for duplicating the value, we do that %% and use the duplicated index. If we can't then we must not %% use the dynamic index for the field. DynIndex when DynIndex < DrainingIndex -> case encode_can_insert(Entry, State0) of {true, EncInstr, State1} -> {ok, State} = table_insert(Entry, State1), #state{insert_count=ReqInsertCount} = State, %% We must reference the relative index of the entry we duplicated %% before we duplicated it. The newest entry starts at 0. If we %% have 3 entries in the table, the oldest one will have a relative %% index of 2. Because we already inserted the duplicate, our %% ReqInsertCount has 1 added, so for our previously 3 entries %% table, we end up with a ReqInsertCount of 4. This means we %% have to remove 2 from the difference to find the relative index. DynIndexRel = ReqInsertCount - DynIndex - 2, encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base, %% Duplicate. [[EncInstr|enc_int5(DynIndexRel, 2#000)]|EncAcc], %% Indexed Field Line. T=0 (dynamic). [enc_int6(Base - ReqInsertCount, 2#10)|Acc]); false -> encode_static_name([Entry|Tail], StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc, Acc) end; DynIndex -> ReqInsertCount = max(ReqInsertCount0, DynIndex), encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount, Base, EncAcc, %% Indexed Field Line. T=0 (dynamic). [enc_int6(Base - DynIndex - 1, 2#10)|Acc]) end. encode_static_name([Entry = {Name, Value}|Tail], StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc, Acc) -> case table_find_name_static(Name) of not_found -> encode_dyn_name([Entry|Tail], StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc, Acc); StaticNameIndex -> case encode_can_insert(Entry, State0) of {true, EncInstr, State1} -> {ok, State} = table_insert(Entry, State1), #state{insert_count=ReqInsertCount} = State, PostBaseIndex = length(EncAcc), encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base, %% Insert with Name Reference. T=1 (static). [[EncInstr, enc_int6(StaticNameIndex, 2#11)|enc_str(Value, HuffmanOpt)] |EncAcc], %% Indexed Field Line with Post-Base Index. [enc_int4(PostBaseIndex, 2#0001)|Acc]); false -> encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc, %% Literal Field Line with Name Reference. N=0. T=1 (static). [[enc_int4(StaticNameIndex, 2#0101)|enc_str(Value, HuffmanOpt)]|Acc]) end end. encode_dyn_name([Entry = {Name, Value}|Tail], StreamID, State0=#state{draining_index=DrainingIndex}, HuffmanOpt, ReqInsertCount0, Base, EncAcc, Acc) -> case table_find_name_dyn(Name, State0) of %% We can reference the dynamic name. DynIndex when is_integer(DynIndex), DynIndex >= DrainingIndex -> case encode_can_insert(Entry, State0) of {true, EncInstr, State1} -> {ok, State} = table_insert(Entry, State1), #state{insert_count=ReqInsertCount} = State, %% See comment in encode_dyn for why we remove 2. DynIndexRel = ReqInsertCount - DynIndex - 2, PostBaseIndex = length(EncAcc), encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base, %% Insert with Name Reference. T=0 (dynamic). [[EncInstr, enc_int6(DynIndexRel, 2#10)|enc_str(Value, HuffmanOpt)] |EncAcc], %% Indexed Field Line with Post-Base Index. [enc_int4(PostBaseIndex, 2#0001)|Acc]); false -> encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc, %% Literal Field Line with Name Reference. N=0. T=0 (dynamic). [[enc_int4(DynIndex, 2#0100)|enc_str(Value, HuffmanOpt)]|Acc]) end; %% When there are no name to reference, or the name %% is found below the drain index, we do not attempt %% to refer to it. _ -> case encode_can_insert(Entry, State0) of {true, EncInstr, State1} -> {ok, State} = table_insert(Entry, State1), #state{insert_count=ReqInsertCount} = State, PostBaseIndex = length(EncAcc), encode(Tail, StreamID, State, HuffmanOpt, ReqInsertCount, Base, %% Insert with Literal Name. [[EncInstr, enc_str6(Name, HuffmanOpt, 2#01)|enc_str(Value, HuffmanOpt)] |EncAcc], %% Indexed Field Line with Post-Base Index. [enc_int4(PostBaseIndex, 2#0001)|Acc]); false -> encode(Tail, StreamID, State0, HuffmanOpt, ReqInsertCount0, Base, EncAcc, %% Literal Field Line with Literal Name. N=0. [[enc_str4(Name, HuffmanOpt, 2#0010)|enc_str(Value, HuffmanOpt)]|Acc]) end end. %% @todo We should make sure we have a large enough flow control window. %% %% We can never insert before receiving the SETTINGS frame. encode_can_insert(_, #state{settings_received=false}) -> false; encode_can_insert({Name, Value}, State=#state{ max_table_capacity=MaxCapacity, capacity=Capacity, size=Size, draining_size=DrainingSize}) -> EntrySize = byte_size(Name) + byte_size(Value) + 32, if %% We have enough space in the current capacity, %% without having to drain entries. EntrySize + Size =< Capacity -> {true, <<>>, State}; %% We have enough space if we increase the capacity. %% We prefer to first increase the capacity to the %% maximum before we start draining entries. EntrySize + Size =< MaxCapacity -> {true, enc_int5(MaxCapacity, 2#001), State#state{capacity=MaxCapacity}}; %% We are already at max capacity and have enough %% space if we drain entries. EntrySize + Size =< Capacity + DrainingSize, Capacity =:= MaxCapacity -> {true, <<>>, State}; %% We are not at max capacity. We have enough space %% if we both increase the capacity and drain entries. EntrySize + Size =< MaxCapacity + DrainingSize -> {true, enc_int5(MaxCapacity, 2#001), State#state{capacity=MaxCapacity}}; true -> false end. -spec execute_decoder_instructions(binary(), State) -> {ok, State} | {connection_error, qpack_decoder_stream_error, atom()} when State::state(). execute_decoder_instructions(<<>>, State) -> {ok, State}; %% Section acknowledgement. %% We remove one reference and if needed increase the known received count. execute_decoder_instructions(<<2#1:1,Rest0/bits>>, State=#state{ known_received_count=KnownReceivedCount0, references=Refs}) -> {StreamID, Rest} = dec_int7(Rest0), case Refs of #{StreamID := [InsertCount]} -> KnownReceivedCount = max(KnownReceivedCount0, InsertCount), execute_decoder_instructions(Rest, State#state{ known_received_count=KnownReceivedCount, references=maps:remove(StreamID, Refs)}); #{StreamID := InsertCounts} -> [InsertCount|InsertCountsTail] = lists:reverse(InsertCounts), KnownReceivedCount = max(KnownReceivedCount0, InsertCount), execute_decoder_instructions(Rest, State#state{ known_received_count=KnownReceivedCount, references=Refs#{StreamID => lists:reverse(InsertCountsTail)}}); _ -> {connection_error, qpack_decoder_stream_error, 'Acknowledgement received for stream with no pending sections. (RFC9204 4.4.1)'} end; %% Stream cancellation. %% We drop all references for the given stream. execute_decoder_instructions(<<2#01:2,Rest0/bits>>, State=#state{references=Refs}) -> {StreamID, Rest} = dec_int6(Rest0), case Refs of #{StreamID := _} -> execute_decoder_instructions(Rest, State#state{ references=maps:remove(StreamID, Refs)}); %% It is not an error for the reference to not exist. %% The dynamic table may not have been used for this %% stream. _ -> execute_decoder_instructions(Rest, State) end; %% Insert count increment. %% We increase the known received count. execute_decoder_instructions(<<2#00:2,Rest0/bits>>, State=#state{ known_received_count=KnownReceivedCount}) -> {Increment, Rest} = dec_int6(Rest0), execute_decoder_instructions(Rest, State#state{ known_received_count=KnownReceivedCount + Increment}). %% Inform the encoder of the relevant SETTINGS from the decoder. %% The encoder will choose the smallest value between what it %% has configured and what it received through SETTINGS. Should %% there be no value in the SETTINGS then 0 must be given. -spec encoder_set_settings(non_neg_integer(), non_neg_integer(), state()) -> state(). encoder_set_settings(MaxTableCapacity, MaxBlockedStreams, State=#state{ max_table_capacity=MaxTableCapacityConfigured, max_blocked_streams=MaxBlockedStreamsConfigured}) -> State#state{ settings_received=true, max_table_capacity=min(MaxTableCapacity, MaxTableCapacityConfigured), max_blocked_streams=min(MaxBlockedStreams, MaxBlockedStreamsConfigured) }. huffman_opt(#{huffman := false}) -> no_huffman; huffman_opt(_) -> huffman. enc_int3(Int, Prefix) when Int < 7 -> <>; enc_int3(Int, Prefix) -> enc_big_int(Int - 7, <>). enc_int4(Int, Prefix) when Int < 15 -> <>; enc_int4(Int, Prefix) -> enc_big_int(Int - 15, <>). enc_str4(Str, huffman, Prefix) -> Str2 = enc_huffman(Str, <<>>), [enc_int3(byte_size(Str2), Prefix * 2 + 2#1)|Str2]; enc_str4(Str, no_huffman, Prefix) -> [enc_int3(byte_size(Str), Prefix * 2 + 2#0)|Str]. enc_str6(Str, huffman, Prefix) -> Str2 = enc_huffman(Str, <<>>), [enc_int5(byte_size(Str2), Prefix * 2 + 2#1)|Str2]; enc_str6(Str, no_huffman, Prefix) -> [enc_int5(byte_size(Str), Prefix * 2 + 2#0)|Str]. -ifdef(TEST). %% This function is a good starting point to let the calling %% process insert entries in the dynamic table outside of %% encoding a field section. To be usable more broadly %% it would need to handle the case where a static name %% is found, but also consider how it should be used: %% do we have capacity in the table? We don't have %% capacity before receiving the SETTINGS frame. Until %% then it will be restricted to testing. encoder_insert_entry(Entry={Name, Value}, State0, Opts) -> {ok, State} = table_insert(Entry, State0), HuffmanOpt = huffman_opt(Opts), case table_find_name_static(Name) of not_found -> case table_find_name_dyn(Name, State0) of not_found -> %% Insert with Literal Name. {ok, [enc_str6(Name, HuffmanOpt, 2#01)|enc_str(Value, HuffmanOpt)], State}; DynNameIndex -> #state{insert_count=ReqInsertCount} = State, %% See comment in encode_dyn for why we remove 2. DynNameIndexRel = ReqInsertCount - DynNameIndex - 2, %% Insert with Name Reference. T=0 (dynamic). {ok, [enc_int6(DynNameIndexRel, 2#10)|enc_str(Value, HuffmanOpt)], State} end end. appendix_b_encoder_test() -> %% We limit the encoder to 220 bytes for table capacity. EncState0 = init(encoder, 220, 0), %% Stream: 0 {ok, Data1, EncData1, EncState1} = encode_field_section([ {<<":path">>, <<"/index.html">>} ], 0, EncState0, #{huffman => false}), <<>> = iolist_to_binary(EncData1), << 16#0000:16, 16#510b:16, 16#2f69:16, 16#6e64:16, 16#6578:16, 16#2e68:16, 16#746d:16, 16#6c >> = iolist_to_binary(Data1), #state{ capacity=0, size=0, insert_count=0, dyn_table=[] } = EncState1, %% Simulate receiving of the SETTINGS frame enabling the dynamic table. EncState2 = encoder_set_settings(4096, 0, EncState1), #state{ settings_received=true, max_table_capacity=220, capacity=0 } = EncState2, %% Stream: 4 (and Encoder) {ok, Data3, EncData3, EncState3} = encode_field_section([ {<<":authority">>, <<"www.example.com">>}, {<<":path">>, <<"/sample/path">>} ], 4, EncState2, #{huffman => false}), << 16#3fbd01:24, 16#c00f:16, 16#7777:16, 16#772e:16, 16#6578:16, 16#616d:16, 16#706c:16, 16#652e:16, 16#636f:16, 16#6d, 16#c10c:16, 16#2f73:16, 16#616d:16, 16#706c:16, 16#652f:16, 16#7061:16, 16#7468:16 >> = iolist_to_binary(EncData3), << 16#0381:16, 16#10, 16#11 >> = iolist_to_binary(Data3), #state{ capacity=220, size=106, insert_count=2, %% The dynamic table is in reverse order. dyn_table=[ {49, {<<":path">>, <<"/sample/path">>}}, {57, {<<":authority">>, <<"www.example.com">>}} ] } = EncState3, %% Stream: Decoder {ok, EncState4} = execute_decoder_instructions(<<16#84>>, EncState3), #state{ capacity=220, size=106, insert_count=2, %% The dynamic table is in reverse order. dyn_table=[ {49, {<<":path">>, <<"/sample/path">>}}, {57, {<<":authority">>, <<"www.example.com">>}} ] } = EncState4, %% Stream: Encoder {ok, EncData5, EncState5} = encoder_insert_entry( {<<"custom-key">>, <<"custom-value">>}, EncState4, #{huffman => false}), << 16#4a63:16, 16#7573:16, 16#746f:16, 16#6d2d:16, 16#6b65:16, 16#790c:16, 16#6375:16, 16#7374:16, 16#6f6d:16, 16#2d76:16, 16#616c:16, 16#7565:16 >> = iolist_to_binary(EncData5), #state{ capacity=220, size=160, insert_count=3, %% The dynamic table is in reverse order. dyn_table=[ {54, {<<"custom-key">>, <<"custom-value">>}}, {49, {<<":path">>, <<"/sample/path">>}}, {57, {<<":authority">>, <<"www.example.com">>}} ] } = EncState5, %% Stream: Decoder {ok, EncState6} = execute_decoder_instructions(<<16#01>>, EncState5), #state{ capacity=220, size=160, insert_count=3, %% The dynamic table is in reverse order. dyn_table=[ {54, {<<"custom-key">>, <<"custom-value">>}}, {49, {<<":path">>, <<"/sample/path">>}}, {57, {<<":authority">>, <<"www.example.com">>}} ] } = EncState6, %% Stream: 8 (and Encoder) {ok, Data7, EncData7, EncState7} = encode_field_section([ {<<":authority">>, <<"www.example.com">>}, {<<":path">>, <<"/">>}, {<<"custom-key">>, <<"custom-value">>} ], 8, EncState6), <<16#02>> = iolist_to_binary(EncData7), << 16#0500:16, 16#80, 16#c1, 16#81 >> = iolist_to_binary(Data7), #state{ capacity=220, size=217, insert_count=4, %% The dynamic table is in reverse order. dyn_table=[ {57, {<<":authority">>, <<"www.example.com">>}}, {54, {<<"custom-key">>, <<"custom-value">>}}, {49, {<<":path">>, <<"/sample/path">>}}, {57, {<<":authority">>, <<"www.example.com">>}} ] } = EncState7, %% Stream: Decoder {ok, EncState8} = execute_decoder_instructions(<<16#48>>, EncState7), #state{ capacity=220, size=217, insert_count=4, %% The dynamic table is in reverse order. dyn_table=[ {57, {<<":authority">>, <<"www.example.com">>}}, {54, {<<"custom-key">>, <<"custom-value">>}}, {49, {<<":path">>, <<"/sample/path">>}}, {57, {<<":authority">>, <<"www.example.com">>}} ] } = EncState8, %% Stream: Encoder {ok, EncData9, EncState9} = encoder_insert_entry( {<<"custom-key">>, <<"custom-value2">>}, EncState8, #{huffman => false}), << 16#810d:16, 16#6375:16, 16#7374:16, 16#6f6d:16, 16#2d76:16, 16#616c:16, 16#7565:16, 16#32 >> = iolist_to_binary(EncData9), #state{ capacity=220, size=215, insert_count=5, %% The dynamic table is in reverse order. dyn_table=[ {55, {<<"custom-key">>, <<"custom-value2">>}}, {57, {<<":authority">>, <<"www.example.com">>}}, {54, {<<"custom-key">>, <<"custom-value">>}}, {49, {<<":path">>, <<"/sample/path">>}} ] } = EncState9, ok. -endif. %% Static and dynamic tables. table_find_static({<<":authority">>, <<>>}) -> 0; table_find_static({<<":path">>, <<"/">>}) -> 1; table_find_static({<<"age">>, <<"0">>}) -> 2; table_find_static({<<"content-disposition">>, <<>>}) -> 3; table_find_static({<<"content-length">>, <<"0">>}) -> 4; table_find_static({<<"cookie">>, <<>>}) -> 5; table_find_static({<<"date">>, <<>>}) -> 6; table_find_static({<<"etag">>, <<>>}) -> 7; table_find_static({<<"if-modified-since">>, <<>>}) -> 8; table_find_static({<<"if-none-match">>, <<>>}) -> 9; table_find_static({<<"last-modified">>, <<>>}) -> 10; table_find_static({<<"link">>, <<>>}) -> 11; table_find_static({<<"location">>, <<>>}) -> 12; table_find_static({<<"referer">>, <<>>}) -> 13; table_find_static({<<"set-cookie">>, <<>>}) -> 14; table_find_static({<<":method">>, <<"CONNECT">>}) -> 15; table_find_static({<<":method">>, <<"DELETE">>}) -> 16; table_find_static({<<":method">>, <<"GET">>}) -> 17; table_find_static({<<":method">>, <<"HEAD">>}) -> 18; table_find_static({<<":method">>, <<"OPTIONS">>}) -> 19; table_find_static({<<":method">>, <<"POST">>}) -> 20; table_find_static({<<":method">>, <<"PUT">>}) -> 21; table_find_static({<<":scheme">>, <<"http">>}) -> 22; table_find_static({<<":scheme">>, <<"https">>}) -> 23; table_find_static({<<":status">>, <<"103">>}) -> 24; table_find_static({<<":status">>, <<"200">>}) -> 25; table_find_static({<<":status">>, <<"304">>}) -> 26; table_find_static({<<":status">>, <<"404">>}) -> 27; table_find_static({<<":status">>, <<"503">>}) -> 28; table_find_static({<<"accept">>, <<"*/*">>}) -> 29; table_find_static({<<"accept">>, <<"application/dns-message">>}) -> 30; table_find_static({<<"accept-encoding">>, <<"gzip, deflate, br">>}) -> 31; table_find_static({<<"accept-ranges">>, <<"bytes">>}) -> 32; table_find_static({<<"access-control-allow-headers">>, <<"cache-control">>}) -> 33; table_find_static({<<"access-control-allow-headers">>, <<"content-type">>}) -> 34; table_find_static({<<"access-control-allow-origin">>, <<"*">>}) -> 35; table_find_static({<<"cache-control">>, <<"max-age=0">>}) -> 36; table_find_static({<<"cache-control">>, <<"max-age=2592000">>}) -> 37; table_find_static({<<"cache-control">>, <<"max-age=604800">>}) -> 38; table_find_static({<<"cache-control">>, <<"no-cache">>}) -> 39; table_find_static({<<"cache-control">>, <<"no-store">>}) -> 40; table_find_static({<<"cache-control">>, <<"public, max-age=31536000">>}) -> 41; table_find_static({<<"content-encoding">>, <<"br">>}) -> 42; table_find_static({<<"content-encoding">>, <<"gzip">>}) -> 43; table_find_static({<<"content-type">>, <<"application/dns-message">>}) -> 44; table_find_static({<<"content-type">>, <<"application/javascript">>}) -> 45; table_find_static({<<"content-type">>, <<"application/json">>}) -> 46; table_find_static({<<"content-type">>, <<"application/x-www-form-urlencoded">>}) -> 47; table_find_static({<<"content-type">>, <<"image/gif">>}) -> 48; table_find_static({<<"content-type">>, <<"image/jpeg">>}) -> 49; table_find_static({<<"content-type">>, <<"image/png">>}) -> 50; table_find_static({<<"content-type">>, <<"text/css">>}) -> 51; table_find_static({<<"content-type">>, <<"text/html; charset=utf-8">>}) -> 52; table_find_static({<<"content-type">>, <<"text/plain">>}) -> 53; table_find_static({<<"content-type">>, <<"text/plain;charset=utf-8">>}) -> 54; table_find_static({<<"range">>, <<"bytes=0-">>}) -> 55; table_find_static({<<"strict-transport-security">>, <<"max-age=31536000">>}) -> 56; table_find_static({<<"strict-transport-security">>, <<"max-age=31536000; includesubdomains">>}) -> 57; table_find_static({<<"strict-transport-security">>, <<"max-age=31536000; includesubdomains; preload">>}) -> 58; table_find_static({<<"vary">>, <<"accept-encoding">>}) -> 59; table_find_static({<<"vary">>, <<"origin">>}) -> 60; table_find_static({<<"x-content-type-options">>, <<"nosniff">>}) -> 61; table_find_static({<<"x-xss-protection">>, <<"1; mode=block">>}) -> 62; table_find_static({<<":status">>, <<"100">>}) -> 63; table_find_static({<<":status">>, <<"204">>}) -> 64; table_find_static({<<":status">>, <<"206">>}) -> 65; table_find_static({<<":status">>, <<"302">>}) -> 66; table_find_static({<<":status">>, <<"400">>}) -> 67; table_find_static({<<":status">>, <<"403">>}) -> 68; table_find_static({<<":status">>, <<"421">>}) -> 69; table_find_static({<<":status">>, <<"425">>}) -> 70; table_find_static({<<":status">>, <<"500">>}) -> 71; table_find_static({<<"accept-language">>, <<>>}) -> 72; %% These two values are technically invalid. An errata has already %% been submitted to the RFC. We must however continue to include %% them in the table for compatibility. table_find_static({<<"access-control-allow-credentials">>, <<"FALSE">>}) -> 73; table_find_static({<<"access-control-allow-credentials">>, <<"TRUE">>}) -> 74; table_find_static({<<"access-control-allow-headers">>, <<"*">>}) -> 75; table_find_static({<<"access-control-allow-methods">>, <<"get">>}) -> 76; table_find_static({<<"access-control-allow-methods">>, <<"get, post, options">>}) -> 77; table_find_static({<<"access-control-allow-methods">>, <<"options">>}) -> 78; table_find_static({<<"access-control-expose-headers">>, <<"content-length">>}) -> 79; table_find_static({<<"access-control-request-headers">>, <<"content-type">>}) -> 80; table_find_static({<<"access-control-request-method">>, <<"get">>}) -> 81; table_find_static({<<"access-control-request-method">>, <<"post">>}) -> 82; table_find_static({<<"alt-svc">>, <<"clear">>}) -> 83; table_find_static({<<"authorization">>, <<>>}) -> 84; table_find_static({<<"content-security-policy">>, <<"script-src 'none'; object-src 'none'; base-uri 'none'">>}) -> 85; table_find_static({<<"early-data">>, <<"1">>}) -> 86; table_find_static({<<"expect-ct">>, <<>>}) -> 87; table_find_static({<<"forwarded">>, <<>>}) -> 88; table_find_static({<<"if-range">>, <<>>}) -> 89; table_find_static({<<"origin">>, <<>>}) -> 90; table_find_static({<<"purpose">>, <<"prefetch">>}) -> 91; table_find_static({<<"server">>, <<>>}) -> 92; table_find_static({<<"timing-allow-origin">>, <<"*">>}) -> 93; table_find_static({<<"upgrade-insecure-requests">>, <<"1">>}) -> 94; table_find_static({<<"user-agent">>, <<>>}) -> 95; table_find_static({<<"x-forwarded-for">>, <<>>}) -> 96; table_find_static({<<"x-frame-options">>, <<"deny">>}) -> 97; table_find_static({<<"x-frame-options">>, <<"sameorigin">>}) -> 98; table_find_static(_) -> not_found. table_find_name_static(<<":authority">>) -> 0; table_find_name_static(<<":path">>) -> 1; table_find_name_static(<<"age">>) -> 2; table_find_name_static(<<"content-disposition">>) -> 3; table_find_name_static(<<"content-length">>) -> 4; table_find_name_static(<<"cookie">>) -> 5; table_find_name_static(<<"date">>) -> 6; table_find_name_static(<<"etag">>) -> 7; table_find_name_static(<<"if-modified-since">>) -> 8; table_find_name_static(<<"if-none-match">>) -> 9; table_find_name_static(<<"last-modified">>) -> 10; table_find_name_static(<<"link">>) -> 11; table_find_name_static(<<"location">>) -> 12; table_find_name_static(<<"referer">>) -> 13; table_find_name_static(<<"set-cookie">>) -> 14; table_find_name_static(<<":method">>) -> 15; table_find_name_static(<<":scheme">>) -> 22; table_find_name_static(<<":status">>) -> 24; table_find_name_static(<<"accept">>) -> 29; table_find_name_static(<<"accept-encoding">>) -> 31; table_find_name_static(<<"accept-ranges">>) -> 32; table_find_name_static(<<"access-control-allow-headers">>) -> 33; table_find_name_static(<<"access-control-allow-origin">>) -> 35; table_find_name_static(<<"cache-control">>) -> 36; table_find_name_static(<<"content-encoding">>) -> 42; table_find_name_static(<<"content-type">>) -> 44; table_find_name_static(<<"range">>) -> 55; table_find_name_static(<<"strict-transport-security">>) -> 56; table_find_name_static(<<"vary">>) -> 59; table_find_name_static(<<"x-content-type-options">>) -> 61; table_find_name_static(<<"x-xss-protection">>) -> 62; table_find_name_static(<<"accept-language">>) -> 72; table_find_name_static(<<"access-control-allow-credentials">>) -> 73; table_find_name_static(<<"access-control-allow-methods">>) -> 76; table_find_name_static(<<"access-control-expose-headers">>) -> 79; table_find_name_static(<<"access-control-request-headers">>) -> 80; table_find_name_static(<<"access-control-request-method">>) -> 81; table_find_name_static(<<"alt-svc">>) -> 83; table_find_name_static(<<"authorization">>) -> 84; table_find_name_static(<<"content-security-policy">>) -> 85; table_find_name_static(<<"early-data">>) -> 86; table_find_name_static(<<"expect-ct">>) -> 87; table_find_name_static(<<"forwarded">>) -> 88; table_find_name_static(<<"if-range">>) -> 89; table_find_name_static(<<"origin">>) -> 90; table_find_name_static(<<"purpose">>) -> 91; table_find_name_static(<<"server">>) -> 92; table_find_name_static(<<"timing-allow-origin">>) -> 93; table_find_name_static(<<"upgrade-insecure-requests">>) -> 94; table_find_name_static(<<"user-agent">>) -> 95; table_find_name_static(<<"x-forwarded-for">>) -> 96; table_find_name_static(<<"x-frame-options">>) -> 97; table_find_name_static(_) -> not_found. table_get_static(0) -> {<<":authority">>, <<>>}; table_get_static(1) -> {<<":path">>, <<"/">>}; table_get_static(2) -> {<<"age">>, <<"0">>}; table_get_static(3) -> {<<"content-disposition">>, <<>>}; table_get_static(4) -> {<<"content-length">>, <<"0">>}; table_get_static(5) -> {<<"cookie">>, <<>>}; table_get_static(6) -> {<<"date">>, <<>>}; table_get_static(7) -> {<<"etag">>, <<>>}; table_get_static(8) -> {<<"if-modified-since">>, <<>>}; table_get_static(9) -> {<<"if-none-match">>, <<>>}; table_get_static(10) -> {<<"last-modified">>, <<>>}; table_get_static(11) -> {<<"link">>, <<>>}; table_get_static(12) -> {<<"location">>, <<>>}; table_get_static(13) -> {<<"referer">>, <<>>}; table_get_static(14) -> {<<"set-cookie">>, <<>>}; table_get_static(15) -> {<<":method">>, <<"CONNECT">>}; table_get_static(16) -> {<<":method">>, <<"DELETE">>}; table_get_static(17) -> {<<":method">>, <<"GET">>}; table_get_static(18) -> {<<":method">>, <<"HEAD">>}; table_get_static(19) -> {<<":method">>, <<"OPTIONS">>}; table_get_static(20) -> {<<":method">>, <<"POST">>}; table_get_static(21) -> {<<":method">>, <<"PUT">>}; table_get_static(22) -> {<<":scheme">>, <<"http">>}; table_get_static(23) -> {<<":scheme">>, <<"https">>}; table_get_static(24) -> {<<":status">>, <<"103">>}; table_get_static(25) -> {<<":status">>, <<"200">>}; table_get_static(26) -> {<<":status">>, <<"304">>}; table_get_static(27) -> {<<":status">>, <<"404">>}; table_get_static(28) -> {<<":status">>, <<"503">>}; table_get_static(29) -> {<<"accept">>, <<"*/*">>}; table_get_static(30) -> {<<"accept">>, <<"application/dns-message">>}; table_get_static(31) -> {<<"accept-encoding">>, <<"gzip, deflate, br">>}; table_get_static(32) -> {<<"accept-ranges">>, <<"bytes">>}; table_get_static(33) -> {<<"access-control-allow-headers">>, <<"cache-control">>}; table_get_static(34) -> {<<"access-control-allow-headers">>, <<"content-type">>}; table_get_static(35) -> {<<"access-control-allow-origin">>, <<"*">>}; table_get_static(36) -> {<<"cache-control">>, <<"max-age=0">>}; table_get_static(37) -> {<<"cache-control">>, <<"max-age=2592000">>}; table_get_static(38) -> {<<"cache-control">>, <<"max-age=604800">>}; table_get_static(39) -> {<<"cache-control">>, <<"no-cache">>}; table_get_static(40) -> {<<"cache-control">>, <<"no-store">>}; table_get_static(41) -> {<<"cache-control">>, <<"public, max-age=31536000">>}; table_get_static(42) -> {<<"content-encoding">>, <<"br">>}; table_get_static(43) -> {<<"content-encoding">>, <<"gzip">>}; table_get_static(44) -> {<<"content-type">>, <<"application/dns-message">>}; table_get_static(45) -> {<<"content-type">>, <<"application/javascript">>}; table_get_static(46) -> {<<"content-type">>, <<"application/json">>}; table_get_static(47) -> {<<"content-type">>, <<"application/x-www-form-urlencoded">>}; table_get_static(48) -> {<<"content-type">>, <<"image/gif">>}; table_get_static(49) -> {<<"content-type">>, <<"image/jpeg">>}; table_get_static(50) -> {<<"content-type">>, <<"image/png">>}; table_get_static(51) -> {<<"content-type">>, <<"text/css">>}; table_get_static(52) -> {<<"content-type">>, <<"text/html; charset=utf-8">>}; table_get_static(53) -> {<<"content-type">>, <<"text/plain">>}; table_get_static(54) -> {<<"content-type">>, <<"text/plain;charset=utf-8">>}; table_get_static(55) -> {<<"range">>, <<"bytes=0-">>}; table_get_static(56) -> {<<"strict-transport-security">>, <<"max-age=31536000">>}; table_get_static(57) -> {<<"strict-transport-security">>, <<"max-age=31536000; includesubdomains">>}; table_get_static(58) -> {<<"strict-transport-security">>, <<"max-age=31536000; includesubdomains; preload">>}; table_get_static(59) -> {<<"vary">>, <<"accept-encoding">>}; table_get_static(60) -> {<<"vary">>, <<"origin">>}; table_get_static(61) -> {<<"x-content-type-options">>, <<"nosniff">>}; table_get_static(62) -> {<<"x-xss-protection">>, <<"1; mode=block">>}; table_get_static(63) -> {<<":status">>, <<"100">>}; table_get_static(64) -> {<<":status">>, <<"204">>}; table_get_static(65) -> {<<":status">>, <<"206">>}; table_get_static(66) -> {<<":status">>, <<"302">>}; table_get_static(67) -> {<<":status">>, <<"400">>}; table_get_static(68) -> {<<":status">>, <<"403">>}; table_get_static(69) -> {<<":status">>, <<"421">>}; table_get_static(70) -> {<<":status">>, <<"425">>}; table_get_static(71) -> {<<":status">>, <<"500">>}; table_get_static(72) -> {<<"accept-language">>, <<>>}; %% These two values are technically invalid. An errata has already %% been submitted to the RFC. We must however continue to include %% them in the table for compatibility. table_get_static(73) -> {<<"access-control-allow-credentials">>, <<"FALSE">>}; table_get_static(74) -> {<<"access-control-allow-credentials">>, <<"TRUE">>}; table_get_static(75) -> {<<"access-control-allow-headers">>, <<"*">>}; table_get_static(76) -> {<<"access-control-allow-methods">>, <<"get">>}; table_get_static(77) -> {<<"access-control-allow-methods">>, <<"get, post, options">>}; table_get_static(78) -> {<<"access-control-allow-methods">>, <<"options">>}; table_get_static(79) -> {<<"access-control-expose-headers">>, <<"content-length">>}; table_get_static(80) -> {<<"access-control-request-headers">>, <<"content-type">>}; table_get_static(81) -> {<<"access-control-request-method">>, <<"get">>}; table_get_static(82) -> {<<"access-control-request-method">>, <<"post">>}; table_get_static(83) -> {<<"alt-svc">>, <<"clear">>}; table_get_static(84) -> {<<"authorization">>, <<>>}; table_get_static(85) -> {<<"content-security-policy">>, <<"script-src 'none'; object-src 'none'; base-uri 'none'">>}; table_get_static(86) -> {<<"early-data">>, <<"1">>}; table_get_static(87) -> {<<"expect-ct">>, <<>>}; table_get_static(88) -> {<<"forwarded">>, <<>>}; table_get_static(89) -> {<<"if-range">>, <<>>}; table_get_static(90) -> {<<"origin">>, <<>>}; table_get_static(91) -> {<<"purpose">>, <<"prefetch">>}; table_get_static(92) -> {<<"server">>, <<>>}; table_get_static(93) -> {<<"timing-allow-origin">>, <<"*">>}; table_get_static(94) -> {<<"upgrade-insecure-requests">>, <<"1">>}; table_get_static(95) -> {<<"user-agent">>, <<>>}; table_get_static(96) -> {<<"x-forwarded-for">>, <<>>}; table_get_static(97) -> {<<"x-frame-options">>, <<"deny">>}; table_get_static(98) -> {<<"x-frame-options">>, <<"sameorigin">>}. table_get_name_static(0) -> <<":authority">>; table_get_name_static(1) -> <<":path">>; table_get_name_static(2) -> <<"age">>; table_get_name_static(3) -> <<"content-disposition">>; table_get_name_static(4) -> <<"content-length">>; table_get_name_static(5) -> <<"cookie">>; table_get_name_static(6) -> <<"date">>; table_get_name_static(7) -> <<"etag">>; table_get_name_static(8) -> <<"if-modified-since">>; table_get_name_static(9) -> <<"if-none-match">>; table_get_name_static(10) -> <<"last-modified">>; table_get_name_static(11) -> <<"link">>; table_get_name_static(12) -> <<"location">>; table_get_name_static(13) -> <<"referer">>; table_get_name_static(14) -> <<"set-cookie">>; table_get_name_static(15) -> <<":method">>; table_get_name_static(16) -> <<":method">>; table_get_name_static(17) -> <<":method">>; table_get_name_static(18) -> <<":method">>; table_get_name_static(19) -> <<":method">>; table_get_name_static(20) -> <<":method">>; table_get_name_static(21) -> <<":method">>; table_get_name_static(22) -> <<":scheme">>; table_get_name_static(23) -> <<":scheme">>; table_get_name_static(24) -> <<":status">>; table_get_name_static(25) -> <<":status">>; table_get_name_static(26) -> <<":status">>; table_get_name_static(27) -> <<":status">>; table_get_name_static(28) -> <<":status">>; table_get_name_static(29) -> <<"accept">>; table_get_name_static(30) -> <<"accept">>; table_get_name_static(31) -> <<"accept-encoding">>; table_get_name_static(32) -> <<"accept-ranges">>; table_get_name_static(33) -> <<"access-control-allow-headers">>; table_get_name_static(34) -> <<"access-control-allow-headers">>; table_get_name_static(35) -> <<"access-control-allow-origin">>; table_get_name_static(36) -> <<"cache-control">>; table_get_name_static(37) -> <<"cache-control">>; table_get_name_static(38) -> <<"cache-control">>; table_get_name_static(39) -> <<"cache-control">>; table_get_name_static(40) -> <<"cache-control">>; table_get_name_static(41) -> <<"cache-control">>; table_get_name_static(42) -> <<"content-encoding">>; table_get_name_static(43) -> <<"content-encoding">>; table_get_name_static(44) -> <<"content-type">>; table_get_name_static(45) -> <<"content-type">>; table_get_name_static(46) -> <<"content-type">>; table_get_name_static(47) -> <<"content-type">>; table_get_name_static(48) -> <<"content-type">>; table_get_name_static(49) -> <<"content-type">>; table_get_name_static(50) -> <<"content-type">>; table_get_name_static(51) -> <<"content-type">>; table_get_name_static(52) -> <<"content-type">>; table_get_name_static(53) -> <<"content-type">>; table_get_name_static(54) -> <<"content-type">>; table_get_name_static(55) -> <<"range">>; table_get_name_static(56) -> <<"strict-transport-security">>; table_get_name_static(57) -> <<"strict-transport-security">>; table_get_name_static(58) -> <<"strict-transport-security">>; table_get_name_static(59) -> <<"vary">>; table_get_name_static(60) -> <<"vary">>; table_get_name_static(61) -> <<"x-content-type-options">>; table_get_name_static(62) -> <<"x-xss-protection">>; table_get_name_static(63) -> <<":status">>; table_get_name_static(64) -> <<":status">>; table_get_name_static(65) -> <<":status">>; table_get_name_static(66) -> <<":status">>; table_get_name_static(67) -> <<":status">>; table_get_name_static(68) -> <<":status">>; table_get_name_static(69) -> <<":status">>; table_get_name_static(70) -> <<":status">>; table_get_name_static(71) -> <<":status">>; table_get_name_static(72) -> <<"accept-language">>; table_get_name_static(73) -> <<"access-control-allow-credentials">>; table_get_name_static(74) -> <<"access-control-allow-credentials">>; table_get_name_static(75) -> <<"access-control-allow-headers">>; table_get_name_static(76) -> <<"access-control-allow-methods">>; table_get_name_static(77) -> <<"access-control-allow-methods">>; table_get_name_static(78) -> <<"access-control-allow-methods">>; table_get_name_static(79) -> <<"access-control-expose-headers">>; table_get_name_static(80) -> <<"access-control-request-headers">>; table_get_name_static(81) -> <<"access-control-request-method">>; table_get_name_static(82) -> <<"access-control-request-method">>; table_get_name_static(83) -> <<"alt-svc">>; table_get_name_static(84) -> <<"authorization">>; table_get_name_static(85) -> <<"content-security-policy">>; table_get_name_static(86) -> <<"early-data">>; table_get_name_static(87) -> <<"expect-ct">>; table_get_name_static(88) -> <<"forwarded">>; table_get_name_static(89) -> <<"if-range">>; table_get_name_static(90) -> <<"origin">>; table_get_name_static(91) -> <<"purpose">>; table_get_name_static(92) -> <<"server">>; table_get_name_static(93) -> <<"timing-allow-origin">>; table_get_name_static(94) -> <<"upgrade-insecure-requests">>; table_get_name_static(95) -> <<"user-agent">>; table_get_name_static(96) -> <<"x-forwarded-for">>; table_get_name_static(97) -> <<"x-frame-options">>; table_get_name_static(98) -> <<"x-frame-options">>. table_insert(Entry={Name, Value}, State=#state{capacity=Capacity, size=Size0, insert_count=InsertCount, dyn_table=DynamicTable0, draining_size=DrainingSize}) -> EntrySize = byte_size(Name) + byte_size(Value) + 32, if EntrySize + Size0 =< Capacity -> {ok, State#state{size=Size0 + EntrySize, insert_count=InsertCount + 1, dyn_table=[{EntrySize, Entry}|DynamicTable0]}}; EntrySize =< Capacity -> {DynamicTable, Size} = table_evict(DynamicTable0, Capacity - EntrySize, 0, []), {ok, State#state{size=Size + EntrySize, insert_count=InsertCount + 1, dyn_table=[{EntrySize, Entry}|DynamicTable], %% We reduce the draining size by how much was gained from evicting. draining_size=DrainingSize - (Size0 - Size)}}; true -> % EntrySize > Capacity -> {connection_error, qpack_encoder_stream_error, 'Entry size larger than table capacity. (RFC9204 3.2.2)'} end. table_evict([], _, Size, Acc) -> {lists:reverse(Acc), Size}; table_evict([{EntrySize, _}|_], MaxSize, Size, Acc) when Size + EntrySize > MaxSize -> {lists:reverse(Acc), Size}; table_evict([Entry = {EntrySize, _}|Tail], MaxSize, Size, Acc) -> table_evict(Tail, MaxSize, Size + EntrySize, [Entry|Acc]). table_find_dyn(Entry, #state{insert_count=InsertCount, dyn_table=DynamicTable}) -> table_find_dyn(Entry, DynamicTable, InsertCount - 1). table_find_dyn(_, [], _) -> not_found; table_find_dyn(Entry, [{_, Entry}|_], Index) -> Index; table_find_dyn(Entry, [_|Tail], Index) -> table_find_dyn(Entry, Tail, Index - 1). table_find_name_dyn(Name, #state{insert_count=InsertCount, dyn_table=DynamicTable}) -> table_find_name_dyn(Name, DynamicTable, InsertCount - 1). 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). %% @todo These functions may error out if the encoder is invalid (2.2.3. Invalid References). table_get_dyn_abs(Index, #state{insert_count=InsertCount, dyn_table=DynamicTable}) -> {_, Header} = lists:nth(InsertCount - Index, DynamicTable), Header. table_get_dyn_rel(Index, #state{dyn_table=DynamicTable}) -> {_, Header} = lists:nth(1 + Index, DynamicTable), Header. table_get_name_dyn_rel(Index, State) -> {Name, _} = table_get_dyn_rel(Index, State), Name. table_get_dyn_pre_base(Index, Base, #state{insert_count=InsertCount, dyn_table=DynamicTable}) -> BaseOffset = InsertCount - Base, {_, Header} = lists:nth(1 + Index + BaseOffset, DynamicTable), Header. table_get_dyn_post_base(Index, Base, State) -> table_get_dyn_abs(Base + Index, State). table_get_name_dyn_post_base(Index, Base, State) -> {Name, _} = table_get_dyn_abs(Base + Index, State), Name. -ifdef(TEST). do_init() -> #state{ settings_received=false, max_table_capacity=1000, capacity=1000 }. do_table_insert(Entry, State0) -> {ok, State} = table_insert(Entry, State0), State. table_get_dyn_abs_test() -> State0 = do_init(), State1 = do_table_insert({<<"g">>, <<"h">>}, do_table_insert({<<"e">>, <<"f">>}, do_table_insert({<<"c">>, <<"d">>}, do_table_insert({<<"a">>, <<"b">>}, State0)))), {<<"a">>, <<"b">>} = table_get_dyn_abs(0, State1), {<<"c">>, <<"d">>} = table_get_dyn_abs(1, State1), {<<"e">>, <<"f">>} = table_get_dyn_abs(2, State1), {<<"g">>, <<"h">>} = table_get_dyn_abs(3, State1), %% Evict one member from the table. #state{dyn_table=DynamicTable} = State1, State2 = State1#state{dyn_table=lists:reverse(tl(lists:reverse(DynamicTable)))}, {<<"c">>, <<"d">>} = table_get_dyn_abs(1, State2), {<<"e">>, <<"f">>} = table_get_dyn_abs(2, State2), {<<"g">>, <<"h">>} = table_get_dyn_abs(3, State2), ok. table_get_dyn_rel_test() -> State0 = do_init(), State1 = do_table_insert({<<"g">>, <<"h">>}, do_table_insert({<<"e">>, <<"f">>}, do_table_insert({<<"c">>, <<"d">>}, do_table_insert({<<"a">>, <<"b">>}, State0)))), {<<"g">>, <<"h">>} = table_get_dyn_rel(0, State1), {<<"e">>, <<"f">>} = table_get_dyn_rel(1, State1), {<<"c">>, <<"d">>} = table_get_dyn_rel(2, State1), {<<"a">>, <<"b">>} = table_get_dyn_rel(3, State1), %% Evict one member from the table. #state{dyn_table=DynamicTable} = State1, State2 = State1#state{dyn_table=lists:reverse(tl(lists:reverse(DynamicTable)))}, {<<"g">>, <<"h">>} = table_get_dyn_rel(0, State2), {<<"e">>, <<"f">>} = table_get_dyn_rel(1, State2), {<<"c">>, <<"d">>} = table_get_dyn_rel(2, State2), %% Add a member to the table. State3 = do_table_insert({<<"i">>, <<"j">>}, State2), {<<"i">>, <<"j">>} = table_get_dyn_rel(0, State3), {<<"g">>, <<"h">>} = table_get_dyn_rel(1, State3), {<<"e">>, <<"f">>} = table_get_dyn_rel(2, State3), {<<"c">>, <<"d">>} = table_get_dyn_rel(3, State3), ok. table_get_dyn_pre_base_test() -> State0 = do_init(), State1 = do_table_insert({<<"g">>, <<"h">>}, do_table_insert({<<"e">>, <<"f">>}, do_table_insert({<<"c">>, <<"d">>}, do_table_insert({<<"a">>, <<"b">>}, State0)))), {<<"e">>, <<"f">>} = table_get_dyn_pre_base(0, 3, State1), {<<"c">>, <<"d">>} = table_get_dyn_pre_base(1, 3, State1), {<<"a">>, <<"b">>} = table_get_dyn_pre_base(2, 3, State1), %% Evict one member from the table. #state{dyn_table=DynamicTable} = State1, State2 = State1#state{dyn_table=lists:reverse(tl(lists:reverse(DynamicTable)))}, {<<"e">>, <<"f">>} = table_get_dyn_pre_base(0, 3, State2), {<<"c">>, <<"d">>} = table_get_dyn_pre_base(1, 3, State2), %% Add a member to the table. State3 = do_table_insert({<<"i">>, <<"j">>}, State2), {<<"e">>, <<"f">>} = table_get_dyn_pre_base(0, 3, State3), {<<"c">>, <<"d">>} = table_get_dyn_pre_base(1, 3, State3), ok. table_get_dyn_post_base_test() -> State0 = do_init(), State1 = do_table_insert({<<"g">>, <<"h">>}, do_table_insert({<<"e">>, <<"f">>}, do_table_insert({<<"c">>, <<"d">>}, do_table_insert({<<"a">>, <<"b">>}, State0)))), {<<"e">>, <<"f">>} = table_get_dyn_post_base(0, 2, State1), {<<"g">>, <<"h">>} = table_get_dyn_post_base(1, 2, State1), %% Evict one member from the table. #state{dyn_table=DynamicTable} = State1, State2 = State1#state{dyn_table=lists:reverse(tl(lists:reverse(DynamicTable)))}, {<<"e">>, <<"f">>} = table_get_dyn_post_base(0, 2, State2), {<<"g">>, <<"h">>} = table_get_dyn_post_base(1, 2, State2), %% Add a member to the table. State3 = do_table_insert({<<"i">>, <<"j">>}, State2), {<<"e">>, <<"f">>} = table_get_dyn_post_base(0, 2, State3), {<<"g">>, <<"h">>} = table_get_dyn_post_base(1, 2, State3), {<<"i">>, <<"j">>} = table_get_dyn_post_base(2, 2, State3), ok. -endif. cowlib-2.19.0/src/cow_qs.erl000066400000000000000000000460241523166326600156700ustar00rootroot00000000000000%% Copyright (c) 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). -dialyzer(no_improper_lists). -export([parse_qs/1]). -export([parse_qs/2]). -export([qs/1]). -export([urldecode/1]). -export([urlencode/1]). -include("cow_inline.hrl"). -type parse_opts() :: #{max_keys => non_neg_integer()}. -export_type([parse_opts/0]). -type qs_vals() :: [{binary(), binary() | true}]. %% 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(Binary) -> parse_qs(Binary, #{}). -spec parse_qs(binary(), parse_opts()) -> qs_vals(). parse_qs(Binary, Opts) -> MaxKeys = maps:get(max_keys, Opts, 100), parse_qs_name(Binary, Binary, 0, 0, MaxKeys). -define(IS_NOT_SEP(C), (C =/= $&) andalso (C =/= $=)). parse_qs_name(_, _, _, _, 0) -> error(limit_reached); parse_qs_name(<>, Orig, Skip, Len, MaxKeys) when ?IS_NOT_SEP(C1) andalso ?IS_NOT_SEP(C2) andalso ?IS_NOT_SEP(C3) andalso ?IS_NOT_SEP(C4) -> parse_qs_name(Rest, Orig, Skip, Len + 4, MaxKeys); parse_qs_name(<>, Orig, Skip, Len, MaxKeys) when ?IS_NOT_SEP(C) -> parse_qs_name(Rest, Orig, Skip, Len + 1, MaxKeys); parse_qs_name(<<$=, Rest/bits>>, Orig, Skip, Len, MaxKeys) when Len =/= 0 -> Name = urldecode(binary:part(Orig, Skip, Len)), parse_qs_value(Rest, Orig, Skip + Len + 1, 0, MaxKeys, Name); parse_qs_name(<<$&, Rest/bits>>, Orig, Skip, 0, MaxKeys) -> parse_qs_name(Rest, Orig, Skip + 1, 0, MaxKeys); parse_qs_name(<<$&, Rest/bits>>, Orig, Skip, Len, MaxKeys) -> Name = urldecode(binary:part(Orig, Skip, Len)), [{Name, true}|parse_qs_name(Rest, Orig, Skip + Len + 1, 0, MaxKeys - 1)]; parse_qs_name(<<>>, _, _, 0, _) -> []; parse_qs_name(<<>>, Orig, Skip, Len, _) -> Name = urldecode(binary:part(Orig, Skip, Len)), [{Name, true}]. parse_qs_value(<>, Orig, Skip, Len, MaxKeys, Name) when (C1 =/= $&) andalso (C2 =/= $&) andalso (C3 =/= $&) andalso (C4 =/= $&) -> parse_qs_value(Rest, Orig, Skip, Len + 4, MaxKeys, Name); parse_qs_value(<>, Orig, Skip, Len, MaxKeys, Name) when (C =/= $&) -> parse_qs_value(Rest, Orig, Skip, Len + 1, MaxKeys, Name); parse_qs_value(<<$&, Rest/bits>>, Orig, Skip, Len, MaxKeys, Name) -> Value = urldecode(binary:part(Orig, Skip, Len)), [{Name, Value}|parse_qs_name(Rest, Orig, Skip + Len + 1, 0, MaxKeys - 1)]; parse_qs_value(<<>>, _, _, 0, _, Name) -> [{Name, <<>>}]; parse_qs_value(<<>>, Orig, Skip, Len, _, Name) -> Value = urldecode(binary:part(Orig, Skip, Len)), [{Name, Value}]. -ifdef(TEST). parse_qs_test_() -> HundredResult = [ {<<"a", (integer_to_binary(I))/binary>>, <<"b">>} || I <- lists:seq(1, 100)], Hundred = iolist_to_binary(lists:join("&", [ [Key, $=, Value] || {Key, Value} <- HundredResult])), HundredPlusOne = <>, 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">>}]}, %% Testing max_keys option. {Hundred, HundredResult}, {HundredPlusOne, error} ], [{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. %% Build an application/x-www-form-urlencoded string. -spec qs(qs_vals()) -> binary(). qs([]) -> <<>>; qs(L) -> qs(L, []). qs([], Acc) -> iolist_to_binary(lists:join(<<$&>>, lists:reverse(Acc))); qs([{Name, true}|Tail], Acc) -> qs(Tail, [urlencode_to_iolist(Name)|Acc]); qs([{Name, Value}|Tail], Acc) -> qs(Tail, [[urlencode_to_iolist(Name), $=, urlencode_to_iolist(Value)]|Acc]). -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. %% Decode a percent encoded string (x-www-form-urlencoded rules). -spec urldecode(binary()) -> binary(). urldecode(Binary) -> skip_dec(Binary, Binary, 0). -define(IS_NOT_ENC(C), (C =/= $%) andalso (C =/= $+)). %% This functions helps avoid a binary allocation when %% there is nothing to decode. skip_dec(Binary, Orig, Len) -> case Binary of <> when ?IS_NOT_ENC(C1) andalso ?IS_NOT_ENC(C2) andalso ?IS_NOT_ENC(C3) andalso ?IS_NOT_ENC(C4) -> skip_dec(Rest, Orig, Len + 4); _ -> dec(Binary, [], Orig, 0, Len) end. %% This clause helps speed up decoding of highly encoded values. dec(<<$%, H1, L1, $%, H2, L2, $%, H3, L3, $%, H4, L4, Rest/bits>>, Acc, Orig, Skip, Len) -> C1 = ?UNHEX(H1, L1), C2 = ?UNHEX(H2, L2), C3 = ?UNHEX(H3, L3), C4 = ?UNHEX(H4, L4), case Len of 0 -> dec(Rest, [Acc|<>], Orig, Skip + 12, 0); _ -> Part = binary_part(Orig, Skip, Len), dec(Rest, [Acc, Part|<>], Orig, Skip + Len + 12, 0) end; dec(<<$%, H, L, Rest/bits>>, Acc, Orig, Skip, Len) -> C = ?UNHEX(H, L), case Len of 0 -> dec(Rest, [Acc|<>], Orig, Skip + 3, 0); _ -> Part = binary_part(Orig, Skip, Len), dec(Rest, [Acc, Part|<>], Orig, Skip + Len + 3, 0) end; dec(<<$+, Rest/bits>>, Acc, Orig, Skip, Len) -> case Len of 0 -> dec(Rest, [Acc|<<" ">>], Orig, Skip + 1, 0); _ -> Part = binary_part(Orig, Skip, Len), dec(Rest, [Acc, Part|<<" ">>], Orig, Skip + Len + 1, 0) end; %% This clause helps speed up decoding of barely encoded values. dec(<>, Acc, Orig, Skip, Len) when (C1 =/= $%) andalso ?IS_NOT_ENC(C2) andalso ?IS_NOT_ENC(C3) andalso ?IS_NOT_ENC(C4) -> dec(Rest, Acc, Orig, Skip, Len + 4); dec(<>, Acc, Orig, Skip, Len) when (C =/= $%) -> dec(Rest, Acc, Orig, Skip, Len + 1); dec(<<>>, _, Orig, 0, _) -> Orig; dec(<<>>, Acc, _, _, 0) -> iolist_to_binary(Acc); dec(<<>>, Acc, Orig, Skip, Len) -> Part = binary_part(Orig, Skip, Len), iolist_to_binary([Acc|Part]); dec(_, _, Orig, Skip, Len) -> error({invalid_byte, binary:at(Orig, Skip + Len)}). -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. %% Percent encode a string (x-www-form-urlencoded rules). -spec urlencode(binary()) -> binary(). urlencode(Binary) -> case skip_enc(Binary, Binary, 0) of orig -> Binary; IOList -> iolist_to_binary(IOList) end. %% Used in qs/1 to avoid calling iolist_to_binary unnecessarily. urlencode_to_iolist(Binary) -> case skip_enc(Binary, Binary, 0) of orig -> Binary; IOList -> IOList end. -define(IS_PLAIN(C), ( (C =:= $-) orelse (C =:= $.) orelse (C =:= $0) orelse (C =:= $1) orelse (C =:= $2) orelse (C =:= $3) orelse (C =:= $4) orelse (C =:= $5) orelse (C =:= $6) orelse (C =:= $7) orelse (C =:= $8) orelse (C =:= $9) orelse (C =:= $A) orelse (C =:= $B) orelse (C =:= $C) orelse (C =:= $D) orelse (C =:= $E) orelse (C =:= $F) orelse (C =:= $G) orelse (C =:= $H) orelse (C =:= $I) orelse (C =:= $J) orelse (C =:= $K) orelse (C =:= $L) orelse (C =:= $M) orelse (C =:= $N) orelse (C =:= $O) orelse (C =:= $P) orelse (C =:= $Q) orelse (C =:= $R) orelse (C =:= $S) orelse (C =:= $T) orelse (C =:= $U) orelse (C =:= $V) orelse (C =:= $W) orelse (C =:= $X) orelse (C =:= $Y) orelse (C =:= $Z) orelse (C =:= $_) orelse (C =:= $a) orelse (C =:= $b) orelse (C =:= $c) orelse (C =:= $d) orelse (C =:= $e) orelse (C =:= $f) orelse (C =:= $g) orelse (C =:= $h) orelse (C =:= $i) orelse (C =:= $j) orelse (C =:= $k) orelse (C =:= $l) orelse (C =:= $m) orelse (C =:= $n) orelse (C =:= $o) orelse (C =:= $p) orelse (C =:= $q) orelse (C =:= $r) orelse (C =:= $s) orelse (C =:= $t) orelse (C =:= $u) orelse (C =:= $v) orelse (C =:= $w) orelse (C =:= $x) orelse (C =:= $y) orelse (C =:= $z) )). skip_enc(Binary, Orig, Len) -> case Binary of <> when ?IS_PLAIN(C1) andalso ?IS_PLAIN(C2) andalso ?IS_PLAIN(C3) andalso ?IS_PLAIN(C4) -> skip_enc(Rest, Orig, Len + 4); _ -> enc(Binary, [], Orig, 0, Len) end. enc(<>, Acc, Orig, Skip, Len) when ?IS_PLAIN(C1) andalso ?IS_PLAIN(C2) andalso ?IS_PLAIN(C3) andalso ?IS_PLAIN(C4) -> enc(Rest, Acc, Orig, Skip, Len + 4); enc(<>, Acc, Orig, Skip, Len) when ?IS_PLAIN(C) -> enc(Rest, Acc, Orig, Skip, Len + 1); enc(<>, Acc, Orig, Skip, Len) when (not ?IS_PLAIN(C2)) andalso (not ?IS_PLAIN(C3)) andalso (not ?IS_PLAIN(C4)) -> Enc = <<$%, ?HEX(C1), $%, ?HEX(C2), $%, ?HEX(C3), $%, ?HEX(C4)>>, case Len of 0 -> enc(Rest, [Acc|Enc], Orig, Skip + 4, 0); _ -> Part = binary_part(Orig, Skip, Len), enc(Rest, [Acc, Part|Enc], Orig, Skip + Len + 4, 0) end; enc(<>, Acc, Orig, Skip, Len) -> Enc = case C of $\s -> <<$+>>; _ -> <<$%, ?HEX(C)>> end, case Len of 0 -> enc(Rest, [Acc|Enc], Orig, Skip + 1, 0); _ -> Part = binary_part(Orig, Skip, Len), enc(Rest, [Acc, Part|Enc], Orig, Skip + Len + 1, 0) end; enc(<<>>, _, _, 0, _) -> orig; enc(<<>>, Acc, _, _, 0) -> Acc; enc(<<>>, Acc, Orig, Skip, Len) -> Part = binary_part(Orig, Skip, Len), [Acc|Part]; enc(_, _, Orig, Skip, Len) -> error({invalid_byte, binary:at(Orig, Skip + Len)}). -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. cowlib-2.19.0/src/cow_sse.erl000066400000000000000000000254051523166326600160370ustar00rootroot00000000000000%% Copyright (c) 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]). -ifdef(TEST). -include_lib("stdlib/include/assert.hrl"). -endif. -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), [<<"\r\n">>, <<"\r">>, <<"\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, [<<"\r\n">>, <<"\r">>, <<"\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), [<<"\r\n">>, <<"\r">>, <<"\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"}), _ = event(#{data => "test\r\ntest"}), _ = event(#{data => "test\rtest\r"}), _ = event(#{data => "test\ntest"}), ok. event_error_test() -> ?assertError(_, event(#{id => "test\n"})), ?assertError(_, event(#{id => "test\r"})), ?assertError(_, event(#{id => "test\r\n"})), ?assertError(_, event(#{event => "test\n"})), ?assertError(_, event(#{event => "test\r"})), ?assertError(_, event(#{event => "test\r\n"})), ok. identity_test_() -> Tests = [ #{data => <<"hello">>}, #{event => <<"update">>, data => <<"hello">>}, #{id => <<"42">>, data => <<"hello">>}, #{data => <<"a\nb">>}, #{data => <<"multi\nline\ndata">>}, #{event => <<"update">>, data => <<"hello">>}, #{id => <<"abc">>, data => <<"x">>}, #{comment => <<"c1">>, data => <<"d1">>, event => <<"e1">>, id => <<"i1">>}, #{data => <<>>}, #{data => <<"data with trailing newline\n">>}, #{data => <<"\n">>}, #{data => <<"\n\n">>}, #{data => <<"">>, id => <<"1">>}, #{data => <<"z">>}, #{id => <<"17">>}, #{data => << <<$a>> || _ <- lists:seq(1,200) >>}, #{data => <<"こんにちは世界">>}, #{retry => 30000, data => <<"reconnect">>} ], [{lists:flatten(io_lib:format("~0p", [V])), fun() -> true = do_identity_result(V) =:= do_identity_build_parse(V) end} || V <- Tests]. do_identity_build_parse(Event) -> {event, Parsed, _} = parse(iolist_to_binary(event(Event)), init()), case Parsed of #{data := Data} -> Parsed#{data => iolist_to_binary(Data)}; _ -> Parsed end. do_identity_result(E=#{id := ID}) when map_size(E) =:= 1 -> #{ last_event_id => ID }; do_identity_result(Event) -> #{ event_type => maps:get(event, Event, <<"message">>), data => maps:get(data, Event, <<>>), last_event_id => maps:get(id, Event, <<>>) }. -endif. cowlib-2.19.0/src/cow_uri.erl000066400000000000000000000222101523166326600160330ustar00rootroot00000000000000%% Copyright (c) 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). -dialyzer(no_improper_lists). -export([urldecode/1]). -export([urlencode/1]). -include("cow_inline.hrl"). -define(IS_PLAIN(C), ( (C =:= $!) orelse (C =:= $$) orelse (C =:= $&) orelse (C =:= $') orelse (C =:= $() orelse (C =:= $)) orelse (C =:= $*) orelse (C =:= $+) orelse (C =:= $,) orelse (C =:= $-) orelse (C =:= $.) orelse (C =:= $0) orelse (C =:= $1) orelse (C =:= $2) orelse (C =:= $3) orelse (C =:= $4) orelse (C =:= $5) orelse (C =:= $6) orelse (C =:= $7) orelse (C =:= $8) orelse (C =:= $9) orelse (C =:= $:) orelse (C =:= $;) orelse (C =:= $=) orelse (C =:= $@) orelse (C =:= $A) orelse (C =:= $B) orelse (C =:= $C) orelse (C =:= $D) orelse (C =:= $E) orelse (C =:= $F) orelse (C =:= $G) orelse (C =:= $H) orelse (C =:= $I) orelse (C =:= $J) orelse (C =:= $K) orelse (C =:= $L) orelse (C =:= $M) orelse (C =:= $N) orelse (C =:= $O) orelse (C =:= $P) orelse (C =:= $Q) orelse (C =:= $R) orelse (C =:= $S) orelse (C =:= $T) orelse (C =:= $U) orelse (C =:= $V) orelse (C =:= $W) orelse (C =:= $X) orelse (C =:= $Y) orelse (C =:= $Z) orelse (C =:= $_) orelse (C =:= $a) orelse (C =:= $b) orelse (C =:= $c) orelse (C =:= $d) orelse (C =:= $e) orelse (C =:= $f) orelse (C =:= $g) orelse (C =:= $h) orelse (C =:= $i) orelse (C =:= $j) orelse (C =:= $k) orelse (C =:= $l) orelse (C =:= $m) orelse (C =:= $n) orelse (C =:= $o) orelse (C =:= $p) orelse (C =:= $q) orelse (C =:= $r) orelse (C =:= $s) orelse (C =:= $t) orelse (C =:= $u) orelse (C =:= $v) orelse (C =:= $w) orelse (C =:= $x) orelse (C =:= $y) orelse (C =:= $z) orelse (C =:= $~) )). %% Decode a percent encoded string. (RFC3986 2.1) %% %% Inspiration for some of the optimisations done here come %% from the new `json` module as it was in mid-2024. %% %% Possible input includes: %% %% * nothing encoded (no % character): %% We want to return the binary as-is to avoid an allocation. %% %% * small number of encoded characters: %% We can "skip" words of text. %% %% * mostly encoded characters (non-ascii languages) %% We can decode characters in bulk. -spec urldecode(binary()) -> binary(). urldecode(Binary) -> skip_dec(Binary, Binary, 0). %% This functions helps avoid a binary allocation when %% there is nothing to decode. skip_dec(Binary, Orig, Len) -> case Binary of <> when ?IS_PLAIN(C1) andalso ?IS_PLAIN(C2) andalso ?IS_PLAIN(C3) andalso ?IS_PLAIN(C4) -> skip_dec(Rest, Orig, Len + 4); _ -> dec(Binary, [], Orig, 0, Len) end. %% This clause helps speed up decoding of highly encoded values. dec(<<$%, H1, L1, $%, H2, L2, $%, H3, L3, $%, H4, L4, Rest/bits>>, Acc, Orig, Skip, Len) -> C1 = ?UNHEX(H1, L1), C2 = ?UNHEX(H2, L2), C3 = ?UNHEX(H3, L3), C4 = ?UNHEX(H4, L4), case Len of 0 -> dec(Rest, [Acc|<>], Orig, Skip + 12, 0); _ -> Part = binary_part(Orig, Skip, Len), dec(Rest, [Acc, Part|<>], Orig, Skip + Len + 12, 0) end; dec(<<$%, H, L, Rest/bits>>, Acc, Orig, Skip, Len) -> C = ?UNHEX(H, L), case Len of 0 -> dec(Rest, [Acc|<>], Orig, Skip + 3, 0); _ -> Part = binary_part(Orig, Skip, Len), dec(Rest, [Acc, Part|<>], Orig, Skip + Len + 3, 0) end; %% This clause helps speed up decoding of barely encoded values. dec(<>, Acc, Orig, Skip, Len) when ?IS_PLAIN(C1) andalso ?IS_PLAIN(C2) andalso ?IS_PLAIN(C3) andalso ?IS_PLAIN(C4) -> dec(Rest, Acc, Orig, Skip, Len + 4); dec(<>, Acc, Orig, Skip, Len) when ?IS_PLAIN(C) -> dec(Rest, Acc, Orig, Skip, Len + 1); dec(<<>>, _, Orig, 0, _) -> Orig; dec(<<>>, Acc, _, _, 0) -> iolist_to_binary(Acc); dec(<<>>, Acc, Orig, Skip, Len) -> Part = binary_part(Orig, Skip, Len), iolist_to_binary([Acc|Part]); dec(_, _, Orig, Skip, Len) -> error({invalid_byte, binary:at(Orig, Skip + Len)}). -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">>) ). horse_urldecode_jp_mixed_hex() -> horse:repeat(100000, urldecode(<<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3123%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_worst_case_hex() -> horse:repeat(100000, urldecode(<<"%e3%83123%84%e3123%82%a4123%e3%83123%b3%e3123%82%bd123">>) ). -endif. %% Percent encode a string. (RFC3986 2.1) %% %% This function is meant to be used for path components. -spec urlencode(binary()) -> binary(). urlencode(Binary) -> skip_enc(Binary, Binary, 0). skip_enc(Binary, Orig, Len) -> case Binary of <> when ?IS_PLAIN(C1) andalso ?IS_PLAIN(C2) andalso ?IS_PLAIN(C3) andalso ?IS_PLAIN(C4) -> skip_enc(Rest, Orig, Len + 4); _ -> enc(Binary, [], Orig, 0, Len) end. enc(<>, Acc, Orig, Skip, Len) when ?IS_PLAIN(C1) andalso ?IS_PLAIN(C2) andalso ?IS_PLAIN(C3) andalso ?IS_PLAIN(C4) -> enc(Rest, Acc, Orig, Skip, Len + 4); enc(<>, Acc, Orig, Skip, Len) when ?IS_PLAIN(C) -> enc(Rest, Acc, Orig, Skip, Len + 1); enc(<>, Acc, Orig, Skip, Len) when (not ?IS_PLAIN(C2)) andalso (not ?IS_PLAIN(C3)) andalso (not ?IS_PLAIN(C4)) -> Enc = <<$%, ?HEX(C1), $%, ?HEX(C2), $%, ?HEX(C3), $%, ?HEX(C4)>>, case Len of 0 -> enc(Rest, [Acc|Enc], Orig, Skip + 4, 0); _ -> Part = binary_part(Orig, Skip, Len), enc(Rest, [Acc, Part|Enc], Orig, Skip + Len + 4, 0) end; enc(<>, Acc, Orig, Skip, Len) -> Enc = <<$%, ?HEX(C)>>, case Len of 0 -> enc(Rest, [Acc|Enc], Orig, Skip + 1, 0); _ -> Part = binary_part(Orig, Skip, Len), enc(Rest, [Acc, Part|Enc], Orig, Skip + Len + 1, 0) end; enc(<<>>, _, Orig, 0, _) -> Orig; enc(<<>>, Acc, _, _, 0) -> iolist_to_binary(Acc); enc(<<>>, Acc, Orig, Skip, Len) -> Part = binary_part(Orig, Skip, Len), iolist_to_binary([Acc|Part]); enc(_, _, Orig, Skip, Len) -> error({invalid_byte, binary:at(Orig, Skip + Len)}). -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_jp_mixed() -> horse:repeat(100000, urlencode(<<227,131,132,227,130,164,227,131,179,227,130,189,227, $1, $2, $3, 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. cowlib-2.19.0/src/cow_uri_template.erl000066400000000000000000000300241523166326600177300ustar00rootroot00000000000000%% Copyright (c) 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"). -ifdef(TEST). -include_lib("stdlib/include/assert.hrl"). -endif. %% 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_var_list(<<$%,H,L,R/bits>>, Op, List) when ?IS_HEX(H), ?IS_HEX(L) -> parse_varname(R, Op, List, <<$%,H,L>>). parse_varname(<>, Op, List, Name) when ?IS_ALPHANUM(C) or (C =:= $_) -> parse_varname(R, Op, List, <>); parse_varname(<<$%,H,L,R/bits>>, Op, List, Name) when ?IS_HEX(H), ?IS_HEX(L) -> parse_varname(R, Op, List, <>); parse_varname(<<$.,C,R/bits>>, Op, List, Name) when ?IS_ALPHANUM(C) or (C =:= $_) -> parse_varname(R, Op, List, <>); parse_varname(<<$.,$%,H,L,R/bits>>, Op, List, Name) when ?IS_HEX(H), ?IS_HEX(L) -> 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) -> [urlencode(Literal, reserved)|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 -> ?assertError(_, 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. cowlib-2.19.0/src/cow_ws.erl000066400000000000000000001104551523166326600156760ustar00rootroot00000000000000%% Copyright (c) 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]). -compile({inline, [utf8_class/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; %% When the server requires a client_max_window_bits lower than the %% default, but the client does not support the parameter (the client %% didn't send the parameter), fail the negotiation. (RFC7692 7.1.2.2) {Negotiated, _} when not is_map_key(client_max_window_bits_set, Negotiated), ClientMaxWindowBits < 15 -> 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_set => true}, [<<"; 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_set => true}, [<<"; 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 | badsize}. %% 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 >>}, Exts = #{inflate := Inflate, inflate_takeover := TakeOver}, _) -> {Data2, Rest, Eof} = split_payload(Data, Len), MaxInflateSize = maps:get(max_inflate_size, Exts, infinity), case inflate_frame(unmask(Data2, MaskKey, ParsedLen), Inflate, TakeOver, MaxInflateSize, FragState, Eof) of {ok, Payload} -> validate_payload(Payload, Rest, Utf8State, ParsedLen, Type, FragState, Eof); Error -> Error end; %% Compressed frame. parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState, Exts = #{inflate := Inflate, inflate_takeover := TakeOver}, << 1:1, 0:2 >>) when Type =:= text; Type =:= binary -> {Data2, Rest, Eof} = split_payload(Data, Len), MaxInflateSize = maps:get(max_inflate_size, Exts, infinity), case inflate_frame(unmask(Data2, MaskKey, ParsedLen), Inflate, TakeOver, MaxInflateSize, FragState, Eof) of {ok, Payload} -> validate_payload(Payload, Rest, Utf8State, ParsedLen, Type, FragState, Eof); Error -> Error end; %% 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) * 8, Right = 32 - Left, MaskKey2 = (MaskKey bsl Left) + (MaskKey bsr Right), mask(Data, MaskKey2, <<>>). mask(<< O1:32, O2:32, O3:32, O4:32, Rest/bits >>, MaskKey, Acc) -> T1 = O1 bxor MaskKey, T2 = O2 bxor MaskKey, T3 = O3 bxor MaskKey, T4 = O4 bxor MaskKey, mask(Rest, MaskKey, << Acc/binary, T1:32, T2:32, T3:32, T4:32 >>); 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 >>; mask(<<>>, _, Unmasked) -> Unmasked. inflate_frame(Data, Inflate, TakeOver, MaxInflateSize, FragState, true) when FragState =:= undefined; element(1, FragState) =:= fin -> case cow_deflate:inflate(Inflate, [Data, <<0, 0, 255, 255>>], MaxInflateSize) of {ok, Data2} -> case TakeOver of no_takeover -> zlib:inflateReset(Inflate); takeover -> ok end, {ok, iolist_to_binary(Data2)}; {error, data_error} -> {error, badframe}; {error, size_error} -> {error, badsize} end; inflate_frame(Data, Inflate, _T, MaxInflateSize, _F, _E) -> case cow_deflate:inflate(Inflate, Data, MaxInflateSize) of {ok, Data2} -> {ok, iolist_to_binary(Data2)}; {error, data_error} -> {error, badframe}; {error, size_error} -> {error, badsize} end. %% 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_text(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_text(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 reworked to better adapt to %% the current Erlang VM (at the time of writing). %% %% We keep the character class table to quickly find which class %% a character is. The transition table was removed in favor of %% a separate Erlang function per state as that proved more %% efficient. %% %% We store the character class table in a tuple returned by %% an inline function. %% %% We handle ASCII characters specially because when ASCII %% characters are present we are highly likely to have mostly %% or only ASCII characters. We process them 4 at a time when %% possible. %% %% When a non-ASCII character is encountered, we switch to %% the UTF-8 decoder. When in the UTF-8 decoder we have to %% process characters one at a time. When we are in the UTF-8 %% decoder we expect there to be additional UTF-8 characters %% so we check for them instead of reverting back to ASCII %% every time. This greatly speeds up decoding of Japanese %% and other non-ASCII text. %% %% Our UTF-8 decoder functions consist of looking up the %% character class of the current byte and then using a %% case clause to determine which state we are switching to. %% %% We order clauses based on the likelihood of the character class. %% Order is determined by the number of occurrences of the class in %% the table. The order (and number of occurrences) is as follow: %% 7 (32), 2 (30), 1 and 9 (16), 3 (14), 8 (13), 6 (3), 4, 5, 10 and 11. %% %% This function returns 0 on success, 1 on error, and 2..8 on incomplete data. %% It expects a starting state value of 0. It can be called again %% to stream parse large amounts of text as long as the returned %% 2..8 state is provided when it is called back. validate_text(Text, 0) -> validate_ascii(Text); validate_text(Text, 2) -> validate_s2(Text); validate_text(Text, 3) -> validate_s3(Text); validate_text(Text, 4) -> validate_s4(Text); validate_text(Text, 5) -> validate_s5(Text); validate_text(Text, 6) -> validate_s6(Text); validate_text(Text, 7) -> validate_s7(Text); validate_text(Text, 8) -> validate_s8(Text). validate_ascii(<<>>) -> 0; validate_ascii(<>) when C1 < 128, C2 < 128, C3 < 128, C4 < 128 -> validate_ascii(R); validate_ascii(<>) when C1 < 128 -> validate_ascii(R); validate_ascii(Text) -> validate_s0(Text). %% Instead of switching back to ASCII we first have this %% function attempt to find a non-ASCII character to %% greatly speed up decoding of Japanese and other languages. validate_s0(<>) when C >= 128 -> Class = element(C - 127, utf8_class()), case Class of 2 -> validate_s2(R); 3 -> validate_s3(R); 6 -> validate_s7(R); 4 -> validate_s5(R); 5 -> validate_s8(R); 10 -> validate_s4(R); 11 -> validate_s6(R); _ -> 1 end; validate_s0(Text) -> validate_ascii(Text). validate_s2(<>) when C >= 128 -> Class = element(C - 127, utf8_class()), case Class of 7 -> validate_s0(R); 1 -> validate_s0(R); 9 -> validate_s0(R); _ -> 1 end; validate_s2(<<>>) -> 2; validate_s2(_) -> 1. validate_s3(<>) when C >= 128 -> Class = element(C - 127, utf8_class()), case Class of 7 -> validate_s2(R); 1 -> validate_s2(R); 9 -> validate_s2(R); _ -> 1 end; validate_s3(<<>>) -> 3; validate_s3(_) -> 1. validate_s4(<>) when C >= 128 -> Class = element(C - 127, utf8_class()), case Class of 7 -> validate_s2(R); _ -> 1 end; validate_s4(<<>>) -> 4; validate_s4(_) -> 1. validate_s5(<>) when C >= 128 -> Class = element(C - 127, utf8_class()), case Class of 1 -> validate_s2(R); 9 -> validate_s2(R); _ -> 1 end; validate_s5(<<>>) -> 5; validate_s5(_) -> 1. validate_s6(<>) when C >= 128 -> Class = element(C - 127, utf8_class()), case Class of 7 -> validate_s3(R); 9 -> validate_s3(R); _ -> 1 end; validate_s6(<<>>) -> 6; validate_s6(_) -> 1. validate_s7(<>) when C >= 128 -> Class = element(C - 127, utf8_class()), case Class of 7 -> validate_s3(R); 1 -> validate_s3(R); 9 -> validate_s3(R); _ -> 1 end; validate_s7(<<>>) -> 7; validate_s7(_) -> 1. validate_s8(<>) when C >= 128 -> Class = element(C - 127, utf8_class()), case Class of 1 -> validate_s3(R); _ -> 1 end; validate_s8(<<>>) -> 8; validate_s8(_) -> 1. utf8_class() -> { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, 8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8 }. -ifdef(TEST). validate_text_valid_test() -> 0 = validate_text(<<>>, 0), 0 = validate_text(<<"Hello world!">>, 0), 0 = validate_text(<<16#c2, 16#a2>>, 0), 0 = validate_text(<<16#e2, 16#82, 16#ac>>, 0), 0 = validate_text(<<16#f0, 16#9f, 16#98, 16#80>>, 0), 2 = validate_text(<<16#c2>>, 0), 0 = validate_text(<<16#a2>>, 2), ok. validate_text_rejects_ascii_after_multibyte_lead_test_() -> Tests = [ {<<"s2">>, <<16#c2, $A>>}, {<<"s3">>, <<16#e1, $A>>}, {<<"s4">>, <<16#e0, $A>>}, {<<"s5">>, <<16#ed, $A>>}, {<<"s6">>, <<16#f0, $A>>}, {<<"s7">>, <<16#f1, $A>>}, {<<"s8">>, <<16#f4, $A>>} ], [{Name, fun() -> 1 = validate_text(Text, 0) end} || {Name, Text} <- Tests]. validate_text_rejects_streamed_ascii_in_continuation_state_test_() -> Tests = [ {<<"s2">>, 2}, {<<"s3">>, 3}, {<<"s4">>, 4}, {<<"s5">>, 5}, {<<"s6">>, 6}, {<<"s7">>, 7}, {<<"s8">>, 8} ], [{Name, fun() -> 1 = validate_text(<<"A">>, State) end} || {Name, State} <- Tests]. parse_payload_text_utf8_test() -> Payload = <<16#f0, 16#9f, 16#98, 16#80>>, {ok, Payload, 0, <<>>} = parse_payload(Payload, undefined, 0, 0, text, 4, undefined, #{}, <<0:3>>), ok. parse_payload_rejects_invalid_utf8_text_test() -> {error, badencoding} = parse_payload(<<16#c2, $A>>, undefined, 0, 0, text, 2, undefined, #{}, <<0:3>>), ok. parse_payload_rejects_invalid_utf8_close_test() -> {error, badencoding} = parse_payload(<<1000:16, 16#c2, $A>>, undefined, 0, 0, close, 4, undefined, #{}, <<0:3>>), ok. parse_payload_rejects_streamed_invalid_utf8_fragment_test() -> {error, badencoding} = parse_payload(<<"A">>, undefined, 2, 0, fragment, 1, {fin, text, <<0:3>>}, #{}, <<0:3>>), ok. -endif. %% @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) -> %% Compress all the octets of the payload of the message using DEFLATE. Deflated = iolist_to_binary(zlib:deflate(Deflate, Payload, sync)), case TakeOver of no_takeover -> zlib:deflateReset(Deflate); takeover -> ok end, %% Remove 4 octets (that are 0x00 0x00 0xff 0xff) from the tail end. Len = byte_size(Deflated) - 4, case Deflated of << Body:Len/binary, 0:8, 0:8, 255:8, 255:8 >> -> Body; <<>> -> %% If the resulting data does not end with an empty DEFLATE block %% with no compression (the "BTYPE" bits are set to 00), append an %% empty DEFLATE block with no compression to the tail end. <<0>>; _ -> Deflated end.