aboutsummaryrefslogtreecommitdiff
path: root/server/_build/default/lib/cowboy
diff options
context:
space:
mode:
authorCalvin Morrison <calvin@pobox.com>2025-09-03 21:15:36 -0400
committerCalvin Morrison <calvin@pobox.com>2025-09-03 21:15:36 -0400
commit49fa5aa2a127bdf8924d02bf77e5086b39c7a447 (patch)
tree61d86a7705dacc9fddccc29fa79d075d83ab8059 /server/_build/default/lib/cowboy
i vibe coded itHEADmaster
Diffstat (limited to 'server/_build/default/lib/cowboy')
-rw-r--r--server/_build/default/lib/cowboy/LICENSE13
-rw-r--r--server/_build/default/lib/cowboy/Makefile117
-rw-r--r--server/_build/default/lib/cowboy/README.asciidoc38
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy.app9
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy.beambin0 -> 5288 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_app.beambin0 -> 1724 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_bstr.beambin0 -> 4384 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_children.beambin0 -> 7604 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_clear.beambin0 -> 3520 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_clock.beambin0 -> 9288 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_compress_h.beambin0 -> 11900 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_constraints.beambin0 -> 3996 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_handler.beambin0 -> 3452 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_http.beambin0 -> 69812 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_http2.beambin0 -> 56124 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_loop.beambin0 -> 5668 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_metrics_h.beambin0 -> 11560 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_middleware.beambin0 -> 1768 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_req.beambin0 -> 39552 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_rest.beambin0 -> 63124 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_router.beambin0 -> 16068 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_static.beambin0 -> 13632 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_stream.beambin0 -> 6976 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_stream_h.beambin0 -> 15764 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_sub_protocol.beambin0 -> 1872 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_sup.beambin0 -> 2128 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_tls.beambin0 -> 3544 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_tracer_h.beambin0 -> 8928 bytes
-rw-r--r--server/_build/default/lib/cowboy/ebin/cowboy_websocket.beambin0 -> 32744 bytes
-rw-r--r--server/_build/default/lib/cowboy/erlang.mk8373
-rw-r--r--server/_build/default/lib/cowboy/hex_metadata.config36
-rw-r--r--server/_build/default/lib/cowboy/plugins.mk75
-rw-r--r--server/_build/default/lib/cowboy/rebar.config4
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy.erl105
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_app.erl27
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_bstr.erl123
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_children.erl192
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_clear.erl60
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_clock.erl221
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_compress_h.erl249
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_constraints.erl174
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_handler.erl57
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_http.erl1523
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_http2.erl1225
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_loop.erl108
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_metrics_h.erl331
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_middleware.erl24
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_req.erl1016
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_rest.erl1637
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_router.erl603
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_static.erl418
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_stream.erl193
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_stream_h.erl324
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_sub_protocol.erl24
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_sup.erl30
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_tls.erl56
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_tracer_h.erl192
-rw-r--r--server/_build/default/lib/cowboy/src/cowboy_websocket.erl707
58 files changed, 18284 insertions, 0 deletions
diff --git a/server/_build/default/lib/cowboy/LICENSE b/server/_build/default/lib/cowboy/LICENSE
new file mode 100644
index 0000000..0b6647f
--- /dev/null
+++ b/server/_build/default/lib/cowboy/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2011-2022, Loïc Hoguin <essen@ninenines.eu>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/server/_build/default/lib/cowboy/Makefile b/server/_build/default/lib/cowboy/Makefile
new file mode 100644
index 0000000..368d30f
--- /dev/null
+++ b/server/_build/default/lib/cowboy/Makefile
@@ -0,0 +1,117 @@
+# See LICENSE for licensing information.
+
+PROJECT = cowboy
+PROJECT_DESCRIPTION = Small, fast, modern HTTP server.
+PROJECT_VERSION = 2.10.0
+PROJECT_REGISTERED = cowboy_clock
+
+# Options.
+
+PLT_APPS = public_key ssl
+CT_OPTS += -ct_hooks cowboy_ct_hook [] # -boot start_sasl
+
+# Dependencies.
+
+LOCAL_DEPS = crypto
+
+DEPS = cowlib ranch
+dep_cowlib = git https://github.com/ninenines/cowlib 2.12.1
+dep_ranch = git https://github.com/ninenines/ranch 1.8.0
+
+DOC_DEPS = asciideck
+
+TEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) ct_helper gun
+dep_ct_helper = git https://github.com/extend/ct_helper master
+dep_gun = git https://github.com/ninenines/gun 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-22+
+AUTO_CI_HIPE ?= OTP-LATEST
+# AUTO_CI_ERLLVM ?= OTP-LATEST
+AUTO_CI_WINDOWS ?= OTP-LATEST-22+
+
+# Hex configuration.
+
+define HEX_TARBALL_EXTRA_METADATA
+#{
+ licenses => [<<"ISC">>],
+ links => #{
+ <<"User guide">> => <<"https://ninenines.eu/docs/en/cowboy/2.10/guide/">>,
+ <<"Function reference">> => <<"https://ninenines.eu/docs/en/cowboy/2.10/manual/">>,
+ <<"GitHub">> => <<"https://github.com/ninenines/cowboy">>,
+ <<"Sponsor">> => <<"https://github.com/sponsors/essen">>
+ }
+}
+endef
+
+# Standard targets.
+
+include erlang.mk
+
+# Don't run the examples test suite by default.
+
+ifndef FULL
+CT_SUITES := $(filter-out examples ws_autobahn,$(CT_SUITES))
+endif
+
+# Compile options.
+
+ERLC_OPTS += +warn_missing_spec +warn_untyped_record # +bin_opt_info
+TEST_ERLC_OPTS += +'{parse_transform, eunit_autoexport}'
+
+# Generate rebar.config on build.
+
+app:: rebar.config
+
+# Dialyze the tests.
+
+DIALYZER_OPTS += --src -r test
+
+# h2spec setup.
+
+GOPATH := $(ERLANG_MK_TMP)/gopath
+export GOPATH
+
+H2SPEC := $(GOPATH)/src/github.com/summerwind/h2spec/h2spec
+export H2SPEC
+
+# @todo It would be better to allow these dependencies to be specified
+# on a per-target basis instead of for all targets.
+test-build:: $(H2SPEC)
+
+$(H2SPEC):
+ $(gen_verbose) mkdir -p $(GOPATH)/src/github.com/summerwind
+ $(verbose) git clone --depth 1 https://github.com/summerwind/h2spec $(dir $(H2SPEC)) || true
+ $(verbose) $(MAKE) -C $(dir $(H2SPEC)) build MAKEFLAGS= || true
+
+# 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 -n "GUIDE: "
+ $(verbose) grep -h dep_$(PROJECT)_commit doc/src/guide/*.asciidoc || true
+ $(verbose) echo
+ $(verbose) echo "Titles in most recent CHANGELOG:"
+ $(verbose) for f in `ls -r doc/src/guide/migrating_from_*.asciidoc | head -n1`; do \
+ echo $$f:; \
+ grep == $$f; \
+ done
+ $(verbose) echo
+ $(verbose) echo "Dependencies:"
+ $(verbose) grep ^DEPS Makefile || echo "DEPS ="
+ $(verbose) grep ^dep_ Makefile || true
+ $(verbose) echo
+ $(verbose) echo "rebar.config:"
+ $(verbose) cat rebar.config || true
diff --git a/server/_build/default/lib/cowboy/README.asciidoc b/server/_build/default/lib/cowboy/README.asciidoc
new file mode 100644
index 0000000..1fa6d3f
--- /dev/null
+++ b/server/_build/default/lib/cowboy/README.asciidoc
@@ -0,0 +1,38 @@
+= Cowboy
+
+Cowboy is a small, fast and modern HTTP server for Erlang/OTP.
+
+== Goals
+
+Cowboy aims to provide a *complete* HTTP stack in a *small* code base.
+It is optimized for *low latency* and *low memory usage*, in part
+because it uses *binary strings*.
+
+Cowboy provides *routing* capabilities, selectively dispatching requests
+to handlers written in Erlang.
+
+Because it uses Ranch for managing connections, Cowboy can easily be
+*embedded* in any other application.
+
+Cowboy is *clean* and *well tested* Erlang code.
+
+== Online documentation
+
+* https://ninenines.eu/docs/en/cowboy/2.6/guide[User guide]
+* https://ninenines.eu/docs/en/cowboy/2.6/manual[Function reference]
+
+== Offline documentation
+
+* While still online, run `make docs`
+* User guide available in `doc/` in PDF and HTML formats
+* Function reference man pages available in `doc/man3/` and `doc/man7/`
+* Run `make install-docs` to install man pages on your system
+* Full documentation in Asciidoc available in `doc/src/`
+* Examples available in `examples/`
+
+== Getting help
+
+* Official IRC Channel: #ninenines on irc.freenode.net
+* https://github.com/ninenines/cowboy/issues[Issues tracker]
+* https://ninenines.eu/services[Commercial Support]
+* https://github.com/sponsors/essen[Sponsor me!]
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy.app b/server/_build/default/lib/cowboy/ebin/cowboy.app
new file mode 100644
index 0000000..fcb5358
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy.app
@@ -0,0 +1,9 @@
+{application, 'cowboy', [
+ {description, "Small, fast, modern HTTP server."},
+ {vsn, "2.10.0"},
+ {modules, ['cowboy','cowboy_app','cowboy_bstr','cowboy_children','cowboy_clear','cowboy_clock','cowboy_compress_h','cowboy_constraints','cowboy_handler','cowboy_http','cowboy_http2','cowboy_loop','cowboy_metrics_h','cowboy_middleware','cowboy_req','cowboy_rest','cowboy_router','cowboy_static','cowboy_stream','cowboy_stream_h','cowboy_sub_protocol','cowboy_sup','cowboy_tls','cowboy_tracer_h','cowboy_websocket']},
+ {registered, [cowboy_sup,cowboy_clock]},
+ {applications, [kernel,stdlib,crypto,cowlib,ranch]},
+ {mod, {cowboy_app, []}},
+ {env, []}
+]}. \ No newline at end of file
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy.beam b/server/_build/default/lib/cowboy/ebin/cowboy.beam
new file mode 100644
index 0000000..ae69378
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_app.beam b/server/_build/default/lib/cowboy/ebin/cowboy_app.beam
new file mode 100644
index 0000000..d389997
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_app.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_bstr.beam b/server/_build/default/lib/cowboy/ebin/cowboy_bstr.beam
new file mode 100644
index 0000000..7e7bbd4
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_bstr.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_children.beam b/server/_build/default/lib/cowboy/ebin/cowboy_children.beam
new file mode 100644
index 0000000..7496b32
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_children.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_clear.beam b/server/_build/default/lib/cowboy/ebin/cowboy_clear.beam
new file mode 100644
index 0000000..a4097ae
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_clear.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_clock.beam b/server/_build/default/lib/cowboy/ebin/cowboy_clock.beam
new file mode 100644
index 0000000..183e866
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_clock.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_compress_h.beam b/server/_build/default/lib/cowboy/ebin/cowboy_compress_h.beam
new file mode 100644
index 0000000..9090904
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_compress_h.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_constraints.beam b/server/_build/default/lib/cowboy/ebin/cowboy_constraints.beam
new file mode 100644
index 0000000..60dbb93
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_constraints.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_handler.beam b/server/_build/default/lib/cowboy/ebin/cowboy_handler.beam
new file mode 100644
index 0000000..8b70c5f
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_handler.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_http.beam b/server/_build/default/lib/cowboy/ebin/cowboy_http.beam
new file mode 100644
index 0000000..a30529c
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_http.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_http2.beam b/server/_build/default/lib/cowboy/ebin/cowboy_http2.beam
new file mode 100644
index 0000000..eb08845
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_http2.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_loop.beam b/server/_build/default/lib/cowboy/ebin/cowboy_loop.beam
new file mode 100644
index 0000000..bf780e9
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_loop.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_metrics_h.beam b/server/_build/default/lib/cowboy/ebin/cowboy_metrics_h.beam
new file mode 100644
index 0000000..22b02bd
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_metrics_h.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_middleware.beam b/server/_build/default/lib/cowboy/ebin/cowboy_middleware.beam
new file mode 100644
index 0000000..c8cb7ce
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_middleware.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_req.beam b/server/_build/default/lib/cowboy/ebin/cowboy_req.beam
new file mode 100644
index 0000000..b8d9ec8
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_req.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_rest.beam b/server/_build/default/lib/cowboy/ebin/cowboy_rest.beam
new file mode 100644
index 0000000..d55f11e
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_rest.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_router.beam b/server/_build/default/lib/cowboy/ebin/cowboy_router.beam
new file mode 100644
index 0000000..0a3c6ce
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_router.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_static.beam b/server/_build/default/lib/cowboy/ebin/cowboy_static.beam
new file mode 100644
index 0000000..0c1d012
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_static.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_stream.beam b/server/_build/default/lib/cowboy/ebin/cowboy_stream.beam
new file mode 100644
index 0000000..9aca40c
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_stream.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_stream_h.beam b/server/_build/default/lib/cowboy/ebin/cowboy_stream_h.beam
new file mode 100644
index 0000000..ad9c559
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_stream_h.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_sub_protocol.beam b/server/_build/default/lib/cowboy/ebin/cowboy_sub_protocol.beam
new file mode 100644
index 0000000..2fdea51
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_sub_protocol.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_sup.beam b/server/_build/default/lib/cowboy/ebin/cowboy_sup.beam
new file mode 100644
index 0000000..f61e430
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_sup.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_tls.beam b/server/_build/default/lib/cowboy/ebin/cowboy_tls.beam
new file mode 100644
index 0000000..ba7f0d4
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_tls.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_tracer_h.beam b/server/_build/default/lib/cowboy/ebin/cowboy_tracer_h.beam
new file mode 100644
index 0000000..b4cc6a9
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_tracer_h.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/ebin/cowboy_websocket.beam b/server/_build/default/lib/cowboy/ebin/cowboy_websocket.beam
new file mode 100644
index 0000000..56f2286
--- /dev/null
+++ b/server/_build/default/lib/cowboy/ebin/cowboy_websocket.beam
Binary files differ
diff --git a/server/_build/default/lib/cowboy/erlang.mk b/server/_build/default/lib/cowboy/erlang.mk
new file mode 100644
index 0000000..e8492ae
--- /dev/null
+++ b/server/_build/default/lib/cowboy/erlang.mk
@@ -0,0 +1,8373 @@
+# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+.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 = 94718f7
+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)_app
+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 +A1 -noinput -boot no_dot_erlang
+
+# 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 <essen@ninenines.eu>" \
+ "" \
+ "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)/rebar/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.
+core_find = $(if $(wildcard $1),$(shell find $(1:%/=%) \( -type l -o -type f \) -name $(subst *,\*,$2) | grep -v " "))
+
+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 <essen@ninenines.eu>
+# 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
+
+define kerl_hipe_target
+$(KERL_INSTALL_DIR)/$1-native: $(KERL)
+ $(verbose) if [ ! -d $$@ ]; then \
+ KERL_CONFIGURE_OPTIONS=--enable-native-libs \
+ MAKEFLAGS="$(KERL_MAKEFLAGS)" $(KERL) build git $(OTP_GIT) $1 $1-native; \
+ $(KERL) install $1-native $(KERL_INSTALL_DIR)/$1-native; \
+ 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 ?=
+ERLANG_HIPE ?=
+
+# 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
+
+else
+# Same for a HiPE enabled VM.
+ifneq ($(strip $(ERLANG_HIPE)),)
+export PATH := $(KERL_INSTALL_DIR)/$(ERLANG_HIPE)-native/bin:$(PATH)
+SHELL := env PATH=$(PATH) $(SHELL)
+$(eval $(call kerl_hipe_target,$(ERLANG_HIPE)))
+
+# Build Erlang/OTP only if it doesn't already exist.
+ifeq ($(wildcard $(KERL_INSTALL_DIR)/$(ERLANG_HIPE)-native)$(BUILD_ERLANG_OTP),)
+$(info Building HiPE-enabled Erlang/OTP $(ERLANG_OTP)... Please wait...)
+$(shell $(MAKE) $(KERL_INSTALL_DIR)/$(ERLANG_HIPE)-native ERLANG_HIPE=$(ERLANG_HIPE) BUILD_ERLANG_OTP=1 >&2)
+endif
+
+endif
+endif
+
+PACKAGES += aberth
+pkg_aberth_name = aberth
+pkg_aberth_description = Generic BERT-RPC server in Erlang
+pkg_aberth_homepage = https://github.com/a13x/aberth
+pkg_aberth_fetch = git
+pkg_aberth_repo = https://github.com/a13x/aberth
+pkg_aberth_commit = master
+
+PACKAGES += active
+pkg_active_name = active
+pkg_active_description = Active development for Erlang: rebuild and reload source/binary files while the VM is running
+pkg_active_homepage = https://github.com/proger/active
+pkg_active_fetch = git
+pkg_active_repo = https://github.com/proger/active
+pkg_active_commit = master
+
+PACKAGES += actordb_core
+pkg_actordb_core_name = actordb_core
+pkg_actordb_core_description = ActorDB main source
+pkg_actordb_core_homepage = http://www.actordb.com/
+pkg_actordb_core_fetch = git
+pkg_actordb_core_repo = https://github.com/biokoda/actordb_core
+pkg_actordb_core_commit = master
+
+PACKAGES += actordb_thrift
+pkg_actordb_thrift_name = actordb_thrift
+pkg_actordb_thrift_description = Thrift API for ActorDB
+pkg_actordb_thrift_homepage = http://www.actordb.com/
+pkg_actordb_thrift_fetch = git
+pkg_actordb_thrift_repo = https://github.com/biokoda/actordb_thrift
+pkg_actordb_thrift_commit = master
+
+PACKAGES += aleppo
+pkg_aleppo_name = aleppo
+pkg_aleppo_description = Alternative Erlang Pre-Processor
+pkg_aleppo_homepage = https://github.com/ErlyORM/aleppo
+pkg_aleppo_fetch = git
+pkg_aleppo_repo = https://github.com/ErlyORM/aleppo
+pkg_aleppo_commit = master
+
+PACKAGES += alog
+pkg_alog_name = alog
+pkg_alog_description = Simply the best logging framework for Erlang
+pkg_alog_homepage = https://github.com/siberian-fast-food/alogger
+pkg_alog_fetch = git
+pkg_alog_repo = https://github.com/siberian-fast-food/alogger
+pkg_alog_commit = master
+
+PACKAGES += amqp_client
+pkg_amqp_client_name = amqp_client
+pkg_amqp_client_description = RabbitMQ Erlang AMQP client
+pkg_amqp_client_homepage = https://www.rabbitmq.com/erlang-client-user-guide.html
+pkg_amqp_client_fetch = git
+pkg_amqp_client_repo = https://github.com/rabbitmq/rabbitmq-erlang-client.git
+pkg_amqp_client_commit = master
+
+PACKAGES += annotations
+pkg_annotations_name = annotations
+pkg_annotations_description = Simple code instrumentation utilities
+pkg_annotations_homepage = https://github.com/hyperthunk/annotations
+pkg_annotations_fetch = git
+pkg_annotations_repo = https://github.com/hyperthunk/annotations
+pkg_annotations_commit = master
+
+PACKAGES += antidote
+pkg_antidote_name = antidote
+pkg_antidote_description = Large-scale computation without synchronisation
+pkg_antidote_homepage = https://syncfree.lip6.fr/
+pkg_antidote_fetch = git
+pkg_antidote_repo = https://github.com/SyncFree/antidote
+pkg_antidote_commit = master
+
+PACKAGES += apns
+pkg_apns_name = apns
+pkg_apns_description = Apple Push Notification Server for Erlang
+pkg_apns_homepage = http://inaka.github.com/apns4erl
+pkg_apns_fetch = git
+pkg_apns_repo = https://github.com/inaka/apns4erl
+pkg_apns_commit = master
+
+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 += azdht
+pkg_azdht_name = azdht
+pkg_azdht_description = Azureus Distributed Hash Table (DHT) in Erlang
+pkg_azdht_homepage = https://github.com/arcusfelis/azdht
+pkg_azdht_fetch = git
+pkg_azdht_repo = https://github.com/arcusfelis/azdht
+pkg_azdht_commit = master
+
+PACKAGES += backoff
+pkg_backoff_name = backoff
+pkg_backoff_description = Simple exponential backoffs in Erlang
+pkg_backoff_homepage = https://github.com/ferd/backoff
+pkg_backoff_fetch = git
+pkg_backoff_repo = https://github.com/ferd/backoff
+pkg_backoff_commit = master
+
+PACKAGES += barrel_tcp
+pkg_barrel_tcp_name = barrel_tcp
+pkg_barrel_tcp_description = barrel is a generic TCP acceptor pool with low latency in Erlang.
+pkg_barrel_tcp_homepage = https://github.com/benoitc-attic/barrel_tcp
+pkg_barrel_tcp_fetch = git
+pkg_barrel_tcp_repo = https://github.com/benoitc-attic/barrel_tcp
+pkg_barrel_tcp_commit = master
+
+PACKAGES += basho_bench
+pkg_basho_bench_name = basho_bench
+pkg_basho_bench_description = A load-generation and testing tool for basically whatever you can write a returning Erlang function for.
+pkg_basho_bench_homepage = https://github.com/basho/basho_bench
+pkg_basho_bench_fetch = git
+pkg_basho_bench_repo = https://github.com/basho/basho_bench
+pkg_basho_bench_commit = master
+
+PACKAGES += bcrypt
+pkg_bcrypt_name = bcrypt
+pkg_bcrypt_description = Bcrypt Erlang / C library
+pkg_bcrypt_homepage = https://github.com/erlangpack/bcrypt
+pkg_bcrypt_fetch = git
+pkg_bcrypt_repo = https://github.com/erlangpack/bcrypt.git
+pkg_bcrypt_commit = master
+
+PACKAGES += beam
+pkg_beam_name = beam
+pkg_beam_description = BEAM emulator written in Erlang
+pkg_beam_homepage = https://github.com/tonyrog/beam
+pkg_beam_fetch = git
+pkg_beam_repo = https://github.com/tonyrog/beam
+pkg_beam_commit = master
+
+PACKAGES += beanstalk
+pkg_beanstalk_name = beanstalk
+pkg_beanstalk_description = An Erlang client for beanstalkd
+pkg_beanstalk_homepage = https://github.com/tim/erlang-beanstalk
+pkg_beanstalk_fetch = git
+pkg_beanstalk_repo = https://github.com/tim/erlang-beanstalk
+pkg_beanstalk_commit = master
+
+PACKAGES += bear
+pkg_bear_name = bear
+pkg_bear_description = a set of statistics functions for erlang
+pkg_bear_homepage = https://github.com/boundary/bear
+pkg_bear_fetch = git
+pkg_bear_repo = https://github.com/boundary/bear
+pkg_bear_commit = master
+
+PACKAGES += bertconf
+pkg_bertconf_name = bertconf
+pkg_bertconf_description = Make ETS tables out of statc BERT files that are auto-reloaded
+pkg_bertconf_homepage = https://github.com/ferd/bertconf
+pkg_bertconf_fetch = git
+pkg_bertconf_repo = https://github.com/ferd/bertconf
+pkg_bertconf_commit = master
+
+PACKAGES += bifrost
+pkg_bifrost_name = bifrost
+pkg_bifrost_description = Erlang FTP Server Framework
+pkg_bifrost_homepage = https://github.com/thorstadt/bifrost
+pkg_bifrost_fetch = git
+pkg_bifrost_repo = https://github.com/thorstadt/bifrost
+pkg_bifrost_commit = master
+
+PACKAGES += binpp
+pkg_binpp_name = binpp
+pkg_binpp_description = Erlang Binary Pretty Printer
+pkg_binpp_homepage = https://github.com/jtendo/binpp
+pkg_binpp_fetch = git
+pkg_binpp_repo = https://github.com/jtendo/binpp
+pkg_binpp_commit = master
+
+PACKAGES += bisect
+pkg_bisect_name = bisect
+pkg_bisect_description = Ordered fixed-size binary dictionary in Erlang
+pkg_bisect_homepage = https://github.com/knutin/bisect
+pkg_bisect_fetch = git
+pkg_bisect_repo = https://github.com/knutin/bisect
+pkg_bisect_commit = master
+
+PACKAGES += bitcask
+pkg_bitcask_name = bitcask
+pkg_bitcask_description = because you need another a key/value storage engine
+pkg_bitcask_homepage = https://github.com/basho/bitcask
+pkg_bitcask_fetch = git
+pkg_bitcask_repo = https://github.com/basho/bitcask
+pkg_bitcask_commit = develop
+
+PACKAGES += bitstore
+pkg_bitstore_name = bitstore
+pkg_bitstore_description = A document based ontology development environment
+pkg_bitstore_homepage = https://github.com/bdionne/bitstore
+pkg_bitstore_fetch = git
+pkg_bitstore_repo = https://github.com/bdionne/bitstore
+pkg_bitstore_commit = master
+
+PACKAGES += bootstrap
+pkg_bootstrap_name = bootstrap
+pkg_bootstrap_description = A simple, yet powerful Erlang cluster bootstrapping application.
+pkg_bootstrap_homepage = https://github.com/schlagert/bootstrap
+pkg_bootstrap_fetch = git
+pkg_bootstrap_repo = https://github.com/schlagert/bootstrap
+pkg_bootstrap_commit = master
+
+PACKAGES += boss
+pkg_boss_name = boss
+pkg_boss_description = Erlang web MVC, now featuring Comet
+pkg_boss_homepage = https://github.com/ChicagoBoss/ChicagoBoss
+pkg_boss_fetch = git
+pkg_boss_repo = https://github.com/ChicagoBoss/ChicagoBoss
+pkg_boss_commit = master
+
+PACKAGES += boss_db
+pkg_boss_db_name = boss_db
+pkg_boss_db_description = BossDB: a sharded, caching, pooling, evented ORM for Erlang
+pkg_boss_db_homepage = https://github.com/ErlyORM/boss_db
+pkg_boss_db_fetch = git
+pkg_boss_db_repo = https://github.com/ErlyORM/boss_db
+pkg_boss_db_commit = master
+
+PACKAGES += brod
+pkg_brod_name = brod
+pkg_brod_description = Kafka client in Erlang
+pkg_brod_homepage = https://github.com/klarna/brod
+pkg_brod_fetch = git
+pkg_brod_repo = https://github.com/klarna/brod.git
+pkg_brod_commit = master
+
+PACKAGES += bson
+pkg_bson_name = bson
+pkg_bson_description = BSON documents in Erlang, see bsonspec.org
+pkg_bson_homepage = https://github.com/comtihon/bson-erlang
+pkg_bson_fetch = git
+pkg_bson_repo = https://github.com/comtihon/bson-erlang
+pkg_bson_commit = master
+
+PACKAGES += bullet
+pkg_bullet_name = bullet
+pkg_bullet_description = Simple, reliable, efficient streaming for Cowboy.
+pkg_bullet_homepage = http://ninenines.eu
+pkg_bullet_fetch = git
+pkg_bullet_repo = https://github.com/ninenines/bullet
+pkg_bullet_commit = master
+
+PACKAGES += cache
+pkg_cache_name = cache
+pkg_cache_description = Erlang in-memory cache
+pkg_cache_homepage = https://github.com/fogfish/cache
+pkg_cache_fetch = git
+pkg_cache_repo = https://github.com/fogfish/cache
+pkg_cache_commit = master
+
+PACKAGES += cake
+pkg_cake_name = cake
+pkg_cake_description = Really simple terminal colorization
+pkg_cake_homepage = https://github.com/darach/cake-erl
+pkg_cake_fetch = git
+pkg_cake_repo = https://github.com/darach/cake-erl
+pkg_cake_commit = master
+
+PACKAGES += carotene
+pkg_carotene_name = carotene
+pkg_carotene_description = Real-time server
+pkg_carotene_homepage = https://github.com/carotene/carotene
+pkg_carotene_fetch = git
+pkg_carotene_repo = https://github.com/carotene/carotene
+pkg_carotene_commit = master
+
+PACKAGES += cberl
+pkg_cberl_name = cberl
+pkg_cberl_description = NIF based Erlang bindings for Couchbase
+pkg_cberl_homepage = https://github.com/chitika/cberl
+pkg_cberl_fetch = git
+pkg_cberl_repo = https://github.com/chitika/cberl
+pkg_cberl_commit = master
+
+PACKAGES += cecho
+pkg_cecho_name = cecho
+pkg_cecho_description = An ncurses library for Erlang
+pkg_cecho_homepage = https://github.com/mazenharake/cecho
+pkg_cecho_fetch = git
+pkg_cecho_repo = https://github.com/mazenharake/cecho
+pkg_cecho_commit = master
+
+PACKAGES += cferl
+pkg_cferl_name = cferl
+pkg_cferl_description = Rackspace / Open Stack Cloud Files Erlang Client
+pkg_cferl_homepage = https://github.com/ddossot/cferl
+pkg_cferl_fetch = git
+pkg_cferl_repo = https://github.com/ddossot/cferl
+pkg_cferl_commit = master
+
+PACKAGES += chaos_monkey
+pkg_chaos_monkey_name = chaos_monkey
+pkg_chaos_monkey_description = This is The CHAOS MONKEY. It will kill your processes.
+pkg_chaos_monkey_homepage = https://github.com/dLuna/chaos_monkey
+pkg_chaos_monkey_fetch = git
+pkg_chaos_monkey_repo = https://github.com/dLuna/chaos_monkey
+pkg_chaos_monkey_commit = master
+
+PACKAGES += check_node
+pkg_check_node_name = check_node
+pkg_check_node_description = Nagios Scripts for monitoring Riak
+pkg_check_node_homepage = https://github.com/basho-labs/riak_nagios
+pkg_check_node_fetch = git
+pkg_check_node_repo = https://github.com/basho-labs/riak_nagios
+pkg_check_node_commit = master
+
+PACKAGES += chronos
+pkg_chronos_name = chronos
+pkg_chronos_description = Timer module for Erlang that makes it easy to abstact time out of the tests.
+pkg_chronos_homepage = https://github.com/lehoff/chronos
+pkg_chronos_fetch = git
+pkg_chronos_repo = https://github.com/lehoff/chronos
+pkg_chronos_commit = master
+
+PACKAGES += chumak
+pkg_chumak_name = chumak
+pkg_chumak_description = Pure Erlang implementation of ZeroMQ Message Transport Protocol.
+pkg_chumak_homepage = http://choven.ca
+pkg_chumak_fetch = git
+pkg_chumak_repo = https://github.com/chovencorp/chumak
+pkg_chumak_commit = master
+
+PACKAGES += cl
+pkg_cl_name = cl
+pkg_cl_description = OpenCL binding for Erlang
+pkg_cl_homepage = https://github.com/tonyrog/cl
+pkg_cl_fetch = git
+pkg_cl_repo = https://github.com/tonyrog/cl
+pkg_cl_commit = master
+
+PACKAGES += clique
+pkg_clique_name = clique
+pkg_clique_description = CLI Framework for Erlang
+pkg_clique_homepage = https://github.com/basho/clique
+pkg_clique_fetch = git
+pkg_clique_repo = https://github.com/basho/clique
+pkg_clique_commit = develop
+
+PACKAGES += cloudi_core
+pkg_cloudi_core_name = cloudi_core
+pkg_cloudi_core_description = CloudI internal service runtime
+pkg_cloudi_core_homepage = http://cloudi.org/
+pkg_cloudi_core_fetch = git
+pkg_cloudi_core_repo = https://github.com/CloudI/cloudi_core
+pkg_cloudi_core_commit = master
+
+PACKAGES += cloudi_service_api_requests
+pkg_cloudi_service_api_requests_name = cloudi_service_api_requests
+pkg_cloudi_service_api_requests_description = CloudI Service API requests (JSON-RPC/Erlang-term support)
+pkg_cloudi_service_api_requests_homepage = http://cloudi.org/
+pkg_cloudi_service_api_requests_fetch = git
+pkg_cloudi_service_api_requests_repo = https://github.com/CloudI/cloudi_service_api_requests
+pkg_cloudi_service_api_requests_commit = master
+
+PACKAGES += cloudi_service_db
+pkg_cloudi_service_db_name = cloudi_service_db
+pkg_cloudi_service_db_description = CloudI Database (in-memory/testing/generic)
+pkg_cloudi_service_db_homepage = http://cloudi.org/
+pkg_cloudi_service_db_fetch = git
+pkg_cloudi_service_db_repo = https://github.com/CloudI/cloudi_service_db
+pkg_cloudi_service_db_commit = master
+
+PACKAGES += cloudi_service_db_cassandra
+pkg_cloudi_service_db_cassandra_name = cloudi_service_db_cassandra
+pkg_cloudi_service_db_cassandra_description = Cassandra CloudI Service
+pkg_cloudi_service_db_cassandra_homepage = http://cloudi.org/
+pkg_cloudi_service_db_cassandra_fetch = git
+pkg_cloudi_service_db_cassandra_repo = https://github.com/CloudI/cloudi_service_db_cassandra
+pkg_cloudi_service_db_cassandra_commit = master
+
+PACKAGES += cloudi_service_db_cassandra_cql
+pkg_cloudi_service_db_cassandra_cql_name = cloudi_service_db_cassandra_cql
+pkg_cloudi_service_db_cassandra_cql_description = Cassandra CQL CloudI Service
+pkg_cloudi_service_db_cassandra_cql_homepage = http://cloudi.org/
+pkg_cloudi_service_db_cassandra_cql_fetch = git
+pkg_cloudi_service_db_cassandra_cql_repo = https://github.com/CloudI/cloudi_service_db_cassandra_cql
+pkg_cloudi_service_db_cassandra_cql_commit = master
+
+PACKAGES += cloudi_service_db_couchdb
+pkg_cloudi_service_db_couchdb_name = cloudi_service_db_couchdb
+pkg_cloudi_service_db_couchdb_description = CouchDB CloudI Service
+pkg_cloudi_service_db_couchdb_homepage = http://cloudi.org/
+pkg_cloudi_service_db_couchdb_fetch = git
+pkg_cloudi_service_db_couchdb_repo = https://github.com/CloudI/cloudi_service_db_couchdb
+pkg_cloudi_service_db_couchdb_commit = master
+
+PACKAGES += cloudi_service_db_elasticsearch
+pkg_cloudi_service_db_elasticsearch_name = cloudi_service_db_elasticsearch
+pkg_cloudi_service_db_elasticsearch_description = elasticsearch CloudI Service
+pkg_cloudi_service_db_elasticsearch_homepage = http://cloudi.org/
+pkg_cloudi_service_db_elasticsearch_fetch = git
+pkg_cloudi_service_db_elasticsearch_repo = https://github.com/CloudI/cloudi_service_db_elasticsearch
+pkg_cloudi_service_db_elasticsearch_commit = master
+
+PACKAGES += cloudi_service_db_memcached
+pkg_cloudi_service_db_memcached_name = cloudi_service_db_memcached
+pkg_cloudi_service_db_memcached_description = memcached CloudI Service
+pkg_cloudi_service_db_memcached_homepage = http://cloudi.org/
+pkg_cloudi_service_db_memcached_fetch = git
+pkg_cloudi_service_db_memcached_repo = https://github.com/CloudI/cloudi_service_db_memcached
+pkg_cloudi_service_db_memcached_commit = master
+
+PACKAGES += cloudi_service_db_mysql
+pkg_cloudi_service_db_mysql_name = cloudi_service_db_mysql
+pkg_cloudi_service_db_mysql_description = MySQL CloudI Service
+pkg_cloudi_service_db_mysql_homepage = http://cloudi.org/
+pkg_cloudi_service_db_mysql_fetch = git
+pkg_cloudi_service_db_mysql_repo = https://github.com/CloudI/cloudi_service_db_mysql
+pkg_cloudi_service_db_mysql_commit = master
+
+PACKAGES += cloudi_service_db_pgsql
+pkg_cloudi_service_db_pgsql_name = cloudi_service_db_pgsql
+pkg_cloudi_service_db_pgsql_description = PostgreSQL CloudI Service
+pkg_cloudi_service_db_pgsql_homepage = http://cloudi.org/
+pkg_cloudi_service_db_pgsql_fetch = git
+pkg_cloudi_service_db_pgsql_repo = https://github.com/CloudI/cloudi_service_db_pgsql
+pkg_cloudi_service_db_pgsql_commit = master
+
+PACKAGES += cloudi_service_db_riak
+pkg_cloudi_service_db_riak_name = cloudi_service_db_riak
+pkg_cloudi_service_db_riak_description = Riak CloudI Service
+pkg_cloudi_service_db_riak_homepage = http://cloudi.org/
+pkg_cloudi_service_db_riak_fetch = git
+pkg_cloudi_service_db_riak_repo = https://github.com/CloudI/cloudi_service_db_riak
+pkg_cloudi_service_db_riak_commit = master
+
+PACKAGES += cloudi_service_db_tokyotyrant
+pkg_cloudi_service_db_tokyotyrant_name = cloudi_service_db_tokyotyrant
+pkg_cloudi_service_db_tokyotyrant_description = Tokyo Tyrant CloudI Service
+pkg_cloudi_service_db_tokyotyrant_homepage = http://cloudi.org/
+pkg_cloudi_service_db_tokyotyrant_fetch = git
+pkg_cloudi_service_db_tokyotyrant_repo = https://github.com/CloudI/cloudi_service_db_tokyotyrant
+pkg_cloudi_service_db_tokyotyrant_commit = master
+
+PACKAGES += cloudi_service_filesystem
+pkg_cloudi_service_filesystem_name = cloudi_service_filesystem
+pkg_cloudi_service_filesystem_description = Filesystem CloudI Service
+pkg_cloudi_service_filesystem_homepage = http://cloudi.org/
+pkg_cloudi_service_filesystem_fetch = git
+pkg_cloudi_service_filesystem_repo = https://github.com/CloudI/cloudi_service_filesystem
+pkg_cloudi_service_filesystem_commit = master
+
+PACKAGES += cloudi_service_http_client
+pkg_cloudi_service_http_client_name = cloudi_service_http_client
+pkg_cloudi_service_http_client_description = HTTP client CloudI Service
+pkg_cloudi_service_http_client_homepage = http://cloudi.org/
+pkg_cloudi_service_http_client_fetch = git
+pkg_cloudi_service_http_client_repo = https://github.com/CloudI/cloudi_service_http_client
+pkg_cloudi_service_http_client_commit = master
+
+PACKAGES += cloudi_service_http_cowboy
+pkg_cloudi_service_http_cowboy_name = cloudi_service_http_cowboy
+pkg_cloudi_service_http_cowboy_description = cowboy HTTP/HTTPS CloudI Service
+pkg_cloudi_service_http_cowboy_homepage = http://cloudi.org/
+pkg_cloudi_service_http_cowboy_fetch = git
+pkg_cloudi_service_http_cowboy_repo = https://github.com/CloudI/cloudi_service_http_cowboy
+pkg_cloudi_service_http_cowboy_commit = master
+
+PACKAGES += cloudi_service_http_elli
+pkg_cloudi_service_http_elli_name = cloudi_service_http_elli
+pkg_cloudi_service_http_elli_description = elli HTTP CloudI Service
+pkg_cloudi_service_http_elli_homepage = http://cloudi.org/
+pkg_cloudi_service_http_elli_fetch = git
+pkg_cloudi_service_http_elli_repo = https://github.com/CloudI/cloudi_service_http_elli
+pkg_cloudi_service_http_elli_commit = master
+
+PACKAGES += cloudi_service_map_reduce
+pkg_cloudi_service_map_reduce_name = cloudi_service_map_reduce
+pkg_cloudi_service_map_reduce_description = Map/Reduce CloudI Service
+pkg_cloudi_service_map_reduce_homepage = http://cloudi.org/
+pkg_cloudi_service_map_reduce_fetch = git
+pkg_cloudi_service_map_reduce_repo = https://github.com/CloudI/cloudi_service_map_reduce
+pkg_cloudi_service_map_reduce_commit = master
+
+PACKAGES += cloudi_service_oauth1
+pkg_cloudi_service_oauth1_name = cloudi_service_oauth1
+pkg_cloudi_service_oauth1_description = OAuth v1.0 CloudI Service
+pkg_cloudi_service_oauth1_homepage = http://cloudi.org/
+pkg_cloudi_service_oauth1_fetch = git
+pkg_cloudi_service_oauth1_repo = https://github.com/CloudI/cloudi_service_oauth1
+pkg_cloudi_service_oauth1_commit = master
+
+PACKAGES += cloudi_service_queue
+pkg_cloudi_service_queue_name = cloudi_service_queue
+pkg_cloudi_service_queue_description = Persistent Queue Service
+pkg_cloudi_service_queue_homepage = http://cloudi.org/
+pkg_cloudi_service_queue_fetch = git
+pkg_cloudi_service_queue_repo = https://github.com/CloudI/cloudi_service_queue
+pkg_cloudi_service_queue_commit = master
+
+PACKAGES += cloudi_service_quorum
+pkg_cloudi_service_quorum_name = cloudi_service_quorum
+pkg_cloudi_service_quorum_description = CloudI Quorum Service
+pkg_cloudi_service_quorum_homepage = http://cloudi.org/
+pkg_cloudi_service_quorum_fetch = git
+pkg_cloudi_service_quorum_repo = https://github.com/CloudI/cloudi_service_quorum
+pkg_cloudi_service_quorum_commit = master
+
+PACKAGES += cloudi_service_router
+pkg_cloudi_service_router_name = cloudi_service_router
+pkg_cloudi_service_router_description = CloudI Router Service
+pkg_cloudi_service_router_homepage = http://cloudi.org/
+pkg_cloudi_service_router_fetch = git
+pkg_cloudi_service_router_repo = https://github.com/CloudI/cloudi_service_router
+pkg_cloudi_service_router_commit = master
+
+PACKAGES += cloudi_service_tcp
+pkg_cloudi_service_tcp_name = cloudi_service_tcp
+pkg_cloudi_service_tcp_description = TCP CloudI Service
+pkg_cloudi_service_tcp_homepage = http://cloudi.org/
+pkg_cloudi_service_tcp_fetch = git
+pkg_cloudi_service_tcp_repo = https://github.com/CloudI/cloudi_service_tcp
+pkg_cloudi_service_tcp_commit = master
+
+PACKAGES += cloudi_service_timers
+pkg_cloudi_service_timers_name = cloudi_service_timers
+pkg_cloudi_service_timers_description = Timers CloudI Service
+pkg_cloudi_service_timers_homepage = http://cloudi.org/
+pkg_cloudi_service_timers_fetch = git
+pkg_cloudi_service_timers_repo = https://github.com/CloudI/cloudi_service_timers
+pkg_cloudi_service_timers_commit = master
+
+PACKAGES += cloudi_service_udp
+pkg_cloudi_service_udp_name = cloudi_service_udp
+pkg_cloudi_service_udp_description = UDP CloudI Service
+pkg_cloudi_service_udp_homepage = http://cloudi.org/
+pkg_cloudi_service_udp_fetch = git
+pkg_cloudi_service_udp_repo = https://github.com/CloudI/cloudi_service_udp
+pkg_cloudi_service_udp_commit = master
+
+PACKAGES += cloudi_service_validate
+pkg_cloudi_service_validate_name = cloudi_service_validate
+pkg_cloudi_service_validate_description = CloudI Validate Service
+pkg_cloudi_service_validate_homepage = http://cloudi.org/
+pkg_cloudi_service_validate_fetch = git
+pkg_cloudi_service_validate_repo = https://github.com/CloudI/cloudi_service_validate
+pkg_cloudi_service_validate_commit = master
+
+PACKAGES += cloudi_service_zeromq
+pkg_cloudi_service_zeromq_name = cloudi_service_zeromq
+pkg_cloudi_service_zeromq_description = ZeroMQ CloudI Service
+pkg_cloudi_service_zeromq_homepage = http://cloudi.org/
+pkg_cloudi_service_zeromq_fetch = git
+pkg_cloudi_service_zeromq_repo = https://github.com/CloudI/cloudi_service_zeromq
+pkg_cloudi_service_zeromq_commit = master
+
+PACKAGES += cluster_info
+pkg_cluster_info_name = cluster_info
+pkg_cluster_info_description = Fork of Hibari's nifty cluster_info OTP app
+pkg_cluster_info_homepage = https://github.com/basho/cluster_info
+pkg_cluster_info_fetch = git
+pkg_cluster_info_repo = https://github.com/basho/cluster_info
+pkg_cluster_info_commit = master
+
+PACKAGES += color
+pkg_color_name = color
+pkg_color_description = ANSI colors for your Erlang
+pkg_color_homepage = https://github.com/julianduque/erlang-color
+pkg_color_fetch = git
+pkg_color_repo = https://github.com/julianduque/erlang-color
+pkg_color_commit = master
+
+PACKAGES += confetti
+pkg_confetti_name = confetti
+pkg_confetti_description = Erlang configuration provider / application:get_env/2 on steroids
+pkg_confetti_homepage = https://github.com/jtendo/confetti
+pkg_confetti_fetch = git
+pkg_confetti_repo = https://github.com/jtendo/confetti
+pkg_confetti_commit = master
+
+PACKAGES += couchbeam
+pkg_couchbeam_name = couchbeam
+pkg_couchbeam_description = Apache CouchDB client in Erlang
+pkg_couchbeam_homepage = https://github.com/benoitc/couchbeam
+pkg_couchbeam_fetch = git
+pkg_couchbeam_repo = https://github.com/benoitc/couchbeam
+pkg_couchbeam_commit = master
+
+PACKAGES += covertool
+pkg_covertool_name = covertool
+pkg_covertool_description = Tool to convert Erlang cover data files into Cobertura XML reports
+pkg_covertool_homepage = https://github.com/idubrov/covertool
+pkg_covertool_fetch = git
+pkg_covertool_repo = https://github.com/idubrov/covertool
+pkg_covertool_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 = 1.0.4
+
+PACKAGES += cowdb
+pkg_cowdb_name = cowdb
+pkg_cowdb_description = Pure Key/Value database library for Erlang Applications
+pkg_cowdb_homepage = https://github.com/refuge/cowdb
+pkg_cowdb_fetch = git
+pkg_cowdb_repo = https://github.com/refuge/cowdb
+pkg_cowdb_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 = 1.0.2
+
+PACKAGES += cpg
+pkg_cpg_name = cpg
+pkg_cpg_description = CloudI Process Groups
+pkg_cpg_homepage = https://github.com/okeuday/cpg
+pkg_cpg_fetch = git
+pkg_cpg_repo = https://github.com/okeuday/cpg
+pkg_cpg_commit = master
+
+PACKAGES += cqerl
+pkg_cqerl_name = cqerl
+pkg_cqerl_description = Native Erlang CQL client for Cassandra
+pkg_cqerl_homepage = https://matehat.github.io/cqerl/
+pkg_cqerl_fetch = git
+pkg_cqerl_repo = https://github.com/matehat/cqerl
+pkg_cqerl_commit = master
+
+PACKAGES += cr
+pkg_cr_name = cr
+pkg_cr_description = Chain Replication
+pkg_cr_homepage = https://synrc.com/apps/cr/doc/cr.htm
+pkg_cr_fetch = git
+pkg_cr_repo = https://github.com/spawnproc/cr
+pkg_cr_commit = master
+
+PACKAGES += cuttlefish
+pkg_cuttlefish_name = cuttlefish
+pkg_cuttlefish_description = cuttlefish configuration abstraction
+pkg_cuttlefish_homepage = https://github.com/Kyorai/cuttlefish
+pkg_cuttlefish_fetch = git
+pkg_cuttlefish_repo = https://github.com/Kyorai/cuttlefish
+pkg_cuttlefish_commit = master
+
+PACKAGES += damocles
+pkg_damocles_name = damocles
+pkg_damocles_description = Erlang library for generating adversarial network conditions for QAing distributed applications/systems on a single Linux box.
+pkg_damocles_homepage = https://github.com/lostcolony/damocles
+pkg_damocles_fetch = git
+pkg_damocles_repo = https://github.com/lostcolony/damocles
+pkg_damocles_commit = master
+
+PACKAGES += debbie
+pkg_debbie_name = debbie
+pkg_debbie_description = .DEB Built In Erlang
+pkg_debbie_homepage = https://github.com/crownedgrouse/debbie
+pkg_debbie_fetch = git
+pkg_debbie_repo = https://github.com/crownedgrouse/debbie
+pkg_debbie_commit = master
+
+PACKAGES += decimal
+pkg_decimal_name = decimal
+pkg_decimal_description = An Erlang decimal arithmetic library
+pkg_decimal_homepage = https://github.com/tim/erlang-decimal
+pkg_decimal_fetch = git
+pkg_decimal_repo = https://github.com/tim/erlang-decimal
+pkg_decimal_commit = master
+
+PACKAGES += detergent
+pkg_detergent_name = detergent
+pkg_detergent_description = An emulsifying Erlang SOAP library
+pkg_detergent_homepage = https://github.com/devinus/detergent
+pkg_detergent_fetch = git
+pkg_detergent_repo = https://github.com/devinus/detergent
+pkg_detergent_commit = master
+
+PACKAGES += detest
+pkg_detest_name = detest
+pkg_detest_description = Tool for running tests on a cluster of erlang nodes
+pkg_detest_homepage = https://github.com/biokoda/detest
+pkg_detest_fetch = git
+pkg_detest_repo = https://github.com/biokoda/detest
+pkg_detest_commit = master
+
+PACKAGES += dh_date
+pkg_dh_date_name = dh_date
+pkg_dh_date_description = Date formatting / parsing library for erlang
+pkg_dh_date_homepage = https://github.com/daleharvey/dh_date
+pkg_dh_date_fetch = git
+pkg_dh_date_repo = https://github.com/daleharvey/dh_date
+pkg_dh_date_commit = master
+
+PACKAGES += dirbusterl
+pkg_dirbusterl_name = dirbusterl
+pkg_dirbusterl_description = DirBuster successor in Erlang
+pkg_dirbusterl_homepage = https://github.com/silentsignal/DirBustErl
+pkg_dirbusterl_fetch = git
+pkg_dirbusterl_repo = https://github.com/silentsignal/DirBustErl
+pkg_dirbusterl_commit = master
+
+PACKAGES += dispcount
+pkg_dispcount_name = dispcount
+pkg_dispcount_description = Erlang task dispatcher based on ETS counters.
+pkg_dispcount_homepage = https://github.com/ferd/dispcount
+pkg_dispcount_fetch = git
+pkg_dispcount_repo = https://github.com/ferd/dispcount
+pkg_dispcount_commit = master
+
+PACKAGES += dlhttpc
+pkg_dlhttpc_name = dlhttpc
+pkg_dlhttpc_description = dispcount-based lhttpc fork for massive amounts of requests to limited endpoints
+pkg_dlhttpc_homepage = https://github.com/ferd/dlhttpc
+pkg_dlhttpc_fetch = git
+pkg_dlhttpc_repo = https://github.com/ferd/dlhttpc
+pkg_dlhttpc_commit = master
+
+PACKAGES += dns
+pkg_dns_name = dns
+pkg_dns_description = Erlang DNS library
+pkg_dns_homepage = https://github.com/aetrion/dns_erlang
+pkg_dns_fetch = git
+pkg_dns_repo = https://github.com/aetrion/dns_erlang
+pkg_dns_commit = master
+
+PACKAGES += dnssd
+pkg_dnssd_name = dnssd
+pkg_dnssd_description = Erlang interface to Apple's Bonjour D NS Service Discovery implementation
+pkg_dnssd_homepage = https://github.com/benoitc/dnssd_erlang
+pkg_dnssd_fetch = git
+pkg_dnssd_repo = https://github.com/benoitc/dnssd_erlang
+pkg_dnssd_commit = master
+
+PACKAGES += dynamic_compile
+pkg_dynamic_compile_name = dynamic_compile
+pkg_dynamic_compile_description = compile and load erlang modules from string input
+pkg_dynamic_compile_homepage = https://github.com/jkvor/dynamic_compile
+pkg_dynamic_compile_fetch = git
+pkg_dynamic_compile_repo = https://github.com/jkvor/dynamic_compile
+pkg_dynamic_compile_commit = master
+
+PACKAGES += e2
+pkg_e2_name = e2
+pkg_e2_description = Library to simply writing correct OTP applications.
+pkg_e2_homepage = http://e2project.org
+pkg_e2_fetch = git
+pkg_e2_repo = https://github.com/gar1t/e2
+pkg_e2_commit = master
+
+PACKAGES += eamf
+pkg_eamf_name = eamf
+pkg_eamf_description = eAMF provides Action Message Format (AMF) support for Erlang
+pkg_eamf_homepage = https://github.com/mrinalwadhwa/eamf
+pkg_eamf_fetch = git
+pkg_eamf_repo = https://github.com/mrinalwadhwa/eamf
+pkg_eamf_commit = master
+
+PACKAGES += eavro
+pkg_eavro_name = eavro
+pkg_eavro_description = Apache Avro encoder/decoder
+pkg_eavro_homepage = https://github.com/SIfoxDevTeam/eavro
+pkg_eavro_fetch = git
+pkg_eavro_repo = https://github.com/SIfoxDevTeam/eavro
+pkg_eavro_commit = master
+
+PACKAGES += ecapnp
+pkg_ecapnp_name = ecapnp
+pkg_ecapnp_description = Cap'n Proto library for Erlang
+pkg_ecapnp_homepage = https://github.com/kaos/ecapnp
+pkg_ecapnp_fetch = git
+pkg_ecapnp_repo = https://github.com/kaos/ecapnp
+pkg_ecapnp_commit = master
+
+PACKAGES += econfig
+pkg_econfig_name = econfig
+pkg_econfig_description = simple Erlang config handler using INI files
+pkg_econfig_homepage = https://github.com/benoitc/econfig
+pkg_econfig_fetch = git
+pkg_econfig_repo = https://github.com/benoitc/econfig
+pkg_econfig_commit = master
+
+PACKAGES += edate
+pkg_edate_name = edate
+pkg_edate_description = date manipulation library for erlang
+pkg_edate_homepage = https://github.com/dweldon/edate
+pkg_edate_fetch = git
+pkg_edate_repo = https://github.com/dweldon/edate
+pkg_edate_commit = master
+
+PACKAGES += edgar
+pkg_edgar_name = edgar
+pkg_edgar_description = Erlang Does GNU AR
+pkg_edgar_homepage = https://github.com/crownedgrouse/edgar
+pkg_edgar_fetch = git
+pkg_edgar_repo = https://github.com/crownedgrouse/edgar
+pkg_edgar_commit = master
+
+PACKAGES += edis
+pkg_edis_name = edis
+pkg_edis_description = An Erlang implementation of Redis KV Store
+pkg_edis_homepage = http://inaka.github.com/edis/
+pkg_edis_fetch = git
+pkg_edis_repo = https://github.com/inaka/edis
+pkg_edis_commit = master
+
+PACKAGES += edns
+pkg_edns_name = edns
+pkg_edns_description = Erlang/OTP DNS server
+pkg_edns_homepage = https://github.com/hcvst/erlang-dns
+pkg_edns_fetch = git
+pkg_edns_repo = https://github.com/hcvst/erlang-dns
+pkg_edns_commit = master
+
+PACKAGES += edown
+pkg_edown_name = edown
+pkg_edown_description = EDoc extension for generating Github-flavored Markdown
+pkg_edown_homepage = https://github.com/uwiger/edown
+pkg_edown_fetch = git
+pkg_edown_repo = https://github.com/uwiger/edown
+pkg_edown_commit = master
+
+PACKAGES += eep
+pkg_eep_name = eep
+pkg_eep_description = Erlang Easy Profiling (eep) application provides a way to analyze application performance and call hierarchy
+pkg_eep_homepage = https://github.com/virtan/eep
+pkg_eep_fetch = git
+pkg_eep_repo = https://github.com/virtan/eep
+pkg_eep_commit = master
+
+PACKAGES += eep_app
+pkg_eep_app_name = eep_app
+pkg_eep_app_description = Embedded Event Processing
+pkg_eep_app_homepage = https://github.com/darach/eep-erl
+pkg_eep_app_fetch = git
+pkg_eep_app_repo = https://github.com/darach/eep-erl
+pkg_eep_app_commit = master
+
+PACKAGES += efene
+pkg_efene_name = efene
+pkg_efene_description = Alternative syntax for the Erlang Programming Language focusing on simplicity, ease of use and programmer UX
+pkg_efene_homepage = https://github.com/efene/efene
+pkg_efene_fetch = git
+pkg_efene_repo = https://github.com/efene/efene
+pkg_efene_commit = master
+
+PACKAGES += egeoip
+pkg_egeoip_name = egeoip
+pkg_egeoip_description = Erlang IP Geolocation module, currently supporting the MaxMind GeoLite City Database.
+pkg_egeoip_homepage = https://github.com/mochi/egeoip
+pkg_egeoip_fetch = git
+pkg_egeoip_repo = https://github.com/mochi/egeoip
+pkg_egeoip_commit = master
+
+PACKAGES += ehsa
+pkg_ehsa_name = ehsa
+pkg_ehsa_description = Erlang HTTP server basic and digest authentication modules
+pkg_ehsa_homepage = https://bitbucket.org/a12n/ehsa
+pkg_ehsa_fetch = hg
+pkg_ehsa_repo = https://bitbucket.org/a12n/ehsa
+pkg_ehsa_commit = default
+
+PACKAGES += ej
+pkg_ej_name = ej
+pkg_ej_description = Helper module for working with Erlang terms representing JSON
+pkg_ej_homepage = https://github.com/seth/ej
+pkg_ej_fetch = git
+pkg_ej_repo = https://github.com/seth/ej
+pkg_ej_commit = master
+
+PACKAGES += ejabberd
+pkg_ejabberd_name = ejabberd
+pkg_ejabberd_description = Robust, ubiquitous and massively scalable Jabber / XMPP Instant Messaging platform
+pkg_ejabberd_homepage = https://github.com/processone/ejabberd
+pkg_ejabberd_fetch = git
+pkg_ejabberd_repo = https://github.com/processone/ejabberd
+pkg_ejabberd_commit = master
+
+PACKAGES += ejwt
+pkg_ejwt_name = ejwt
+pkg_ejwt_description = erlang library for JSON Web Token
+pkg_ejwt_homepage = https://github.com/artefactop/ejwt
+pkg_ejwt_fetch = git
+pkg_ejwt_repo = https://github.com/artefactop/ejwt
+pkg_ejwt_commit = master
+
+PACKAGES += ekaf
+pkg_ekaf_name = ekaf
+pkg_ekaf_description = A minimal, high-performance Kafka client in Erlang.
+pkg_ekaf_homepage = https://github.com/helpshift/ekaf
+pkg_ekaf_fetch = git
+pkg_ekaf_repo = https://github.com/helpshift/ekaf
+pkg_ekaf_commit = master
+
+PACKAGES += elarm
+pkg_elarm_name = elarm
+pkg_elarm_description = Alarm Manager for Erlang.
+pkg_elarm_homepage = https://github.com/esl/elarm
+pkg_elarm_fetch = git
+pkg_elarm_repo = https://github.com/esl/elarm
+pkg_elarm_commit = master
+
+PACKAGES += eleveldb
+pkg_eleveldb_name = eleveldb
+pkg_eleveldb_description = Erlang LevelDB API
+pkg_eleveldb_homepage = https://github.com/basho/eleveldb
+pkg_eleveldb_fetch = git
+pkg_eleveldb_repo = https://github.com/basho/eleveldb
+pkg_eleveldb_commit = master
+
+PACKAGES += elixir
+pkg_elixir_name = elixir
+pkg_elixir_description = Elixir is a dynamic, functional language designed 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 = master
+
+PACKAGES += elli
+pkg_elli_name = elli
+pkg_elli_description = Simple, robust and performant Erlang web server
+pkg_elli_homepage = https://github.com/elli-lib/elli
+pkg_elli_fetch = git
+pkg_elli_repo = https://github.com/elli-lib/elli
+pkg_elli_commit = master
+
+PACKAGES += elvis
+pkg_elvis_name = elvis
+pkg_elvis_description = Erlang Style Reviewer
+pkg_elvis_homepage = https://github.com/inaka/elvis
+pkg_elvis_fetch = git
+pkg_elvis_repo = https://github.com/inaka/elvis
+pkg_elvis_commit = master
+
+PACKAGES += emagick
+pkg_emagick_name = emagick
+pkg_emagick_description = Wrapper for Graphics/ImageMagick command line tool.
+pkg_emagick_homepage = https://github.com/kivra/emagick
+pkg_emagick_fetch = git
+pkg_emagick_repo = https://github.com/kivra/emagick
+pkg_emagick_commit = master
+
+PACKAGES += emysql
+pkg_emysql_name = emysql
+pkg_emysql_description = Stable, pure Erlang MySQL driver.
+pkg_emysql_homepage = https://github.com/Eonblast/Emysql
+pkg_emysql_fetch = git
+pkg_emysql_repo = https://github.com/Eonblast/Emysql
+pkg_emysql_commit = master
+
+PACKAGES += enm
+pkg_enm_name = enm
+pkg_enm_description = Erlang driver for nanomsg
+pkg_enm_homepage = https://github.com/basho/enm
+pkg_enm_fetch = git
+pkg_enm_repo = https://github.com/basho/enm
+pkg_enm_commit = master
+
+PACKAGES += entop
+pkg_entop_name = entop
+pkg_entop_description = A top-like tool for monitoring an Erlang node
+pkg_entop_homepage = https://github.com/mazenharake/entop
+pkg_entop_fetch = git
+pkg_entop_repo = https://github.com/mazenharake/entop
+pkg_entop_commit = master
+
+PACKAGES += epcap
+pkg_epcap_name = epcap
+pkg_epcap_description = Erlang packet capture interface using pcap
+pkg_epcap_homepage = https://github.com/msantos/epcap
+pkg_epcap_fetch = git
+pkg_epcap_repo = https://github.com/msantos/epcap
+pkg_epcap_commit = master
+
+PACKAGES += eper
+pkg_eper_name = eper
+pkg_eper_description = Erlang performance and debugging tools.
+pkg_eper_homepage = https://github.com/massemanet/eper
+pkg_eper_fetch = git
+pkg_eper_repo = https://github.com/massemanet/eper
+pkg_eper_commit = master
+
+PACKAGES += epgsql
+pkg_epgsql_name = epgsql
+pkg_epgsql_description = Erlang PostgreSQL client library.
+pkg_epgsql_homepage = https://github.com/epgsql/epgsql
+pkg_epgsql_fetch = git
+pkg_epgsql_repo = https://github.com/epgsql/epgsql
+pkg_epgsql_commit = master
+
+PACKAGES += episcina
+pkg_episcina_name = episcina
+pkg_episcina_description = A simple non intrusive resource pool for connections
+pkg_episcina_homepage = https://github.com/erlware/episcina
+pkg_episcina_fetch = git
+pkg_episcina_repo = https://github.com/erlware/episcina
+pkg_episcina_commit = master
+
+PACKAGES += eplot
+pkg_eplot_name = eplot
+pkg_eplot_description = A plot engine written in erlang.
+pkg_eplot_homepage = https://github.com/psyeugenic/eplot
+pkg_eplot_fetch = git
+pkg_eplot_repo = https://github.com/psyeugenic/eplot
+pkg_eplot_commit = master
+
+PACKAGES += epocxy
+pkg_epocxy_name = epocxy
+pkg_epocxy_description = Erlang Patterns of Concurrency
+pkg_epocxy_homepage = https://github.com/duomark/epocxy
+pkg_epocxy_fetch = git
+pkg_epocxy_repo = https://github.com/duomark/epocxy
+pkg_epocxy_commit = master
+
+PACKAGES += epubnub
+pkg_epubnub_name = epubnub
+pkg_epubnub_description = Erlang PubNub API
+pkg_epubnub_homepage = https://github.com/tsloughter/epubnub
+pkg_epubnub_fetch = git
+pkg_epubnub_repo = https://github.com/tsloughter/epubnub
+pkg_epubnub_commit = master
+
+PACKAGES += eqm
+pkg_eqm_name = eqm
+pkg_eqm_description = Erlang pub sub with supply-demand channels
+pkg_eqm_homepage = https://github.com/loucash/eqm
+pkg_eqm_fetch = git
+pkg_eqm_repo = https://github.com/loucash/eqm
+pkg_eqm_commit = master
+
+PACKAGES += eredis
+pkg_eredis_name = eredis
+pkg_eredis_description = Erlang Redis client
+pkg_eredis_homepage = https://github.com/wooga/eredis
+pkg_eredis_fetch = git
+pkg_eredis_repo = https://github.com/wooga/eredis
+pkg_eredis_commit = master
+
+PACKAGES += eredis_pool
+pkg_eredis_pool_name = eredis_pool
+pkg_eredis_pool_description = eredis_pool is Pool of Redis clients, using eredis and poolboy.
+pkg_eredis_pool_homepage = https://github.com/hiroeorz/eredis_pool
+pkg_eredis_pool_fetch = git
+pkg_eredis_pool_repo = https://github.com/hiroeorz/eredis_pool
+pkg_eredis_pool_commit = master
+
+PACKAGES += erl_streams
+pkg_erl_streams_name = erl_streams
+pkg_erl_streams_description = Streams in Erlang
+pkg_erl_streams_homepage = https://github.com/epappas/erl_streams
+pkg_erl_streams_fetch = git
+pkg_erl_streams_repo = https://github.com/epappas/erl_streams
+pkg_erl_streams_commit = master
+
+PACKAGES += erlang_cep
+pkg_erlang_cep_name = erlang_cep
+pkg_erlang_cep_description = A basic CEP package written in erlang
+pkg_erlang_cep_homepage = https://github.com/danmacklin/erlang_cep
+pkg_erlang_cep_fetch = git
+pkg_erlang_cep_repo = https://github.com/danmacklin/erlang_cep
+pkg_erlang_cep_commit = master
+
+PACKAGES += erlang_js
+pkg_erlang_js_name = erlang_js
+pkg_erlang_js_description = A linked-in driver for Erlang to Mozilla's Spidermonkey Javascript runtime.
+pkg_erlang_js_homepage = https://github.com/basho/erlang_js
+pkg_erlang_js_fetch = git
+pkg_erlang_js_repo = https://github.com/basho/erlang_js
+pkg_erlang_js_commit = master
+
+PACKAGES += erlang_localtime
+pkg_erlang_localtime_name = erlang_localtime
+pkg_erlang_localtime_description = Erlang library for conversion from one local time to another
+pkg_erlang_localtime_homepage = https://github.com/dmitryme/erlang_localtime
+pkg_erlang_localtime_fetch = git
+pkg_erlang_localtime_repo = https://github.com/dmitryme/erlang_localtime
+pkg_erlang_localtime_commit = master
+
+PACKAGES += erlang_smtp
+pkg_erlang_smtp_name = erlang_smtp
+pkg_erlang_smtp_description = Erlang SMTP and POP3 server code.
+pkg_erlang_smtp_homepage = https://github.com/tonyg/erlang-smtp
+pkg_erlang_smtp_fetch = git
+pkg_erlang_smtp_repo = https://github.com/tonyg/erlang-smtp
+pkg_erlang_smtp_commit = master
+
+PACKAGES += erlang_term
+pkg_erlang_term_name = erlang_term
+pkg_erlang_term_description = Erlang Term Info
+pkg_erlang_term_homepage = https://github.com/okeuday/erlang_term
+pkg_erlang_term_fetch = git
+pkg_erlang_term_repo = https://github.com/okeuday/erlang_term
+pkg_erlang_term_commit = master
+
+PACKAGES += erlastic_search
+pkg_erlastic_search_name = erlastic_search
+pkg_erlastic_search_description = An Erlang app for communicating with Elastic Search's rest interface.
+pkg_erlastic_search_homepage = https://github.com/tsloughter/erlastic_search
+pkg_erlastic_search_fetch = git
+pkg_erlastic_search_repo = https://github.com/tsloughter/erlastic_search
+pkg_erlastic_search_commit = master
+
+PACKAGES += erlasticsearch
+pkg_erlasticsearch_name = erlasticsearch
+pkg_erlasticsearch_description = Erlang thrift interface to elastic_search
+pkg_erlasticsearch_homepage = https://github.com/dieswaytoofast/erlasticsearch
+pkg_erlasticsearch_fetch = git
+pkg_erlasticsearch_repo = https://github.com/dieswaytoofast/erlasticsearch
+pkg_erlasticsearch_commit = master
+
+PACKAGES += erlbrake
+pkg_erlbrake_name = erlbrake
+pkg_erlbrake_description = Erlang Airbrake notification client
+pkg_erlbrake_homepage = https://github.com/kenpratt/erlbrake
+pkg_erlbrake_fetch = git
+pkg_erlbrake_repo = https://github.com/kenpratt/erlbrake
+pkg_erlbrake_commit = master
+
+PACKAGES += erlcloud
+pkg_erlcloud_name = erlcloud
+pkg_erlcloud_description = Cloud Computing library for erlang (Amazon EC2, S3, SQS, SimpleDB, Mechanical Turk, ELB)
+pkg_erlcloud_homepage = https://github.com/gleber/erlcloud
+pkg_erlcloud_fetch = git
+pkg_erlcloud_repo = https://github.com/gleber/erlcloud
+pkg_erlcloud_commit = master
+
+PACKAGES += erlcron
+pkg_erlcron_name = erlcron
+pkg_erlcron_description = Erlang cronish system
+pkg_erlcron_homepage = https://github.com/erlware/erlcron
+pkg_erlcron_fetch = git
+pkg_erlcron_repo = https://github.com/erlware/erlcron
+pkg_erlcron_commit = master
+
+PACKAGES += erldb
+pkg_erldb_name = erldb
+pkg_erldb_description = ORM (Object-relational mapping) application implemented in Erlang
+pkg_erldb_homepage = http://erldb.org
+pkg_erldb_fetch = git
+pkg_erldb_repo = https://github.com/erldb/erldb
+pkg_erldb_commit = master
+
+PACKAGES += erldis
+pkg_erldis_name = erldis
+pkg_erldis_description = redis erlang client library
+pkg_erldis_homepage = https://github.com/cstar/erldis
+pkg_erldis_fetch = git
+pkg_erldis_repo = https://github.com/cstar/erldis
+pkg_erldis_commit = master
+
+PACKAGES += erldns
+pkg_erldns_name = erldns
+pkg_erldns_description = DNS server, in erlang.
+pkg_erldns_homepage = https://github.com/aetrion/erl-dns
+pkg_erldns_fetch = git
+pkg_erldns_repo = https://github.com/aetrion/erl-dns
+pkg_erldns_commit = master
+
+PACKAGES += erldocker
+pkg_erldocker_name = erldocker
+pkg_erldocker_description = Docker Remote API client for Erlang
+pkg_erldocker_homepage = https://github.com/proger/erldocker
+pkg_erldocker_fetch = git
+pkg_erldocker_repo = https://github.com/proger/erldocker
+pkg_erldocker_commit = master
+
+PACKAGES += erlfsmon
+pkg_erlfsmon_name = erlfsmon
+pkg_erlfsmon_description = Erlang filesystem event watcher for Linux and OSX
+pkg_erlfsmon_homepage = https://github.com/proger/erlfsmon
+pkg_erlfsmon_fetch = git
+pkg_erlfsmon_repo = https://github.com/proger/erlfsmon
+pkg_erlfsmon_commit = master
+
+PACKAGES += erlgit
+pkg_erlgit_name = erlgit
+pkg_erlgit_description = Erlang convenience wrapper around git executable
+pkg_erlgit_homepage = https://github.com/gleber/erlgit
+pkg_erlgit_fetch = git
+pkg_erlgit_repo = https://github.com/gleber/erlgit
+pkg_erlgit_commit = master
+
+PACKAGES += erlguten
+pkg_erlguten_name = erlguten
+pkg_erlguten_description = ErlGuten is a system for high-quality typesetting, written purely in Erlang.
+pkg_erlguten_homepage = https://github.com/richcarl/erlguten
+pkg_erlguten_fetch = git
+pkg_erlguten_repo = https://github.com/richcarl/erlguten
+pkg_erlguten_commit = master
+
+PACKAGES += erlmc
+pkg_erlmc_name = erlmc
+pkg_erlmc_description = Erlang memcached binary protocol client
+pkg_erlmc_homepage = https://github.com/jkvor/erlmc
+pkg_erlmc_fetch = git
+pkg_erlmc_repo = https://github.com/jkvor/erlmc
+pkg_erlmc_commit = master
+
+PACKAGES += erlmongo
+pkg_erlmongo_name = erlmongo
+pkg_erlmongo_description = Record based Erlang driver for MongoDB with gridfs support
+pkg_erlmongo_homepage = https://github.com/SergejJurecko/erlmongo
+pkg_erlmongo_fetch = git
+pkg_erlmongo_repo = https://github.com/SergejJurecko/erlmongo
+pkg_erlmongo_commit = master
+
+PACKAGES += erlog
+pkg_erlog_name = erlog
+pkg_erlog_description = Prolog interpreter in and for Erlang
+pkg_erlog_homepage = https://github.com/rvirding/erlog
+pkg_erlog_fetch = git
+pkg_erlog_repo = https://github.com/rvirding/erlog
+pkg_erlog_commit = master
+
+PACKAGES += erlpass
+pkg_erlpass_name = erlpass
+pkg_erlpass_description = A library to handle password hashing and changing in a safe manner, independent from any kind of storage whatsoever.
+pkg_erlpass_homepage = https://github.com/ferd/erlpass
+pkg_erlpass_fetch = git
+pkg_erlpass_repo = https://github.com/ferd/erlpass
+pkg_erlpass_commit = master
+
+PACKAGES += erlport
+pkg_erlport_name = erlport
+pkg_erlport_description = ErlPort - connect Erlang to other languages
+pkg_erlport_homepage = https://github.com/hdima/erlport
+pkg_erlport_fetch = git
+pkg_erlport_repo = https://github.com/hdima/erlport
+pkg_erlport_commit = master
+
+PACKAGES += erlsh
+pkg_erlsh_name = erlsh
+pkg_erlsh_description = Erlang shell tools
+pkg_erlsh_homepage = https://github.com/proger/erlsh
+pkg_erlsh_fetch = git
+pkg_erlsh_repo = https://github.com/proger/erlsh
+pkg_erlsh_commit = master
+
+PACKAGES += erlsha2
+pkg_erlsha2_name = erlsha2
+pkg_erlsha2_description = SHA-224, SHA-256, SHA-384, SHA-512 implemented in Erlang NIFs.
+pkg_erlsha2_homepage = https://github.com/vinoski/erlsha2
+pkg_erlsha2_fetch = git
+pkg_erlsha2_repo = https://github.com/vinoski/erlsha2
+pkg_erlsha2_commit = master
+
+PACKAGES += erlsom
+pkg_erlsom_name = erlsom
+pkg_erlsom_description = XML parser for Erlang
+pkg_erlsom_homepage = https://github.com/willemdj/erlsom
+pkg_erlsom_fetch = git
+pkg_erlsom_repo = https://github.com/willemdj/erlsom
+pkg_erlsom_commit = master
+
+PACKAGES += erlubi
+pkg_erlubi_name = erlubi
+pkg_erlubi_description = Ubigraph Erlang Client (and Process Visualizer)
+pkg_erlubi_homepage = https://github.com/krestenkrab/erlubi
+pkg_erlubi_fetch = git
+pkg_erlubi_repo = https://github.com/krestenkrab/erlubi
+pkg_erlubi_commit = master
+
+PACKAGES += erlvolt
+pkg_erlvolt_name = erlvolt
+pkg_erlvolt_description = VoltDB Erlang Client Driver
+pkg_erlvolt_homepage = https://github.com/VoltDB/voltdb-client-erlang
+pkg_erlvolt_fetch = git
+pkg_erlvolt_repo = https://github.com/VoltDB/voltdb-client-erlang
+pkg_erlvolt_commit = master
+
+PACKAGES += erlware_commons
+pkg_erlware_commons_name = erlware_commons
+pkg_erlware_commons_description = Erlware Commons is an Erlware project focused on all aspects of reusable Erlang components.
+pkg_erlware_commons_homepage = https://github.com/erlware/erlware_commons
+pkg_erlware_commons_fetch = git
+pkg_erlware_commons_repo = https://github.com/erlware/erlware_commons
+pkg_erlware_commons_commit = master
+
+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 += errd
+pkg_errd_name = errd
+pkg_errd_description = Erlang RRDTool library
+pkg_errd_homepage = https://github.com/archaelus/errd
+pkg_errd_fetch = git
+pkg_errd_repo = https://github.com/archaelus/errd
+pkg_errd_commit = master
+
+PACKAGES += erserve
+pkg_erserve_name = erserve
+pkg_erserve_description = Erlang/Rserve communication interface
+pkg_erserve_homepage = https://github.com/del/erserve
+pkg_erserve_fetch = git
+pkg_erserve_repo = https://github.com/del/erserve
+pkg_erserve_commit = master
+
+PACKAGES += erwa
+pkg_erwa_name = erwa
+pkg_erwa_description = A WAMP router and client written in Erlang.
+pkg_erwa_homepage = https://github.com/bwegh/erwa
+pkg_erwa_fetch = git
+pkg_erwa_repo = https://github.com/bwegh/erwa
+pkg_erwa_commit = master
+
+PACKAGES += escalus
+pkg_escalus_name = escalus
+pkg_escalus_description = An XMPP client library in Erlang for conveniently testing XMPP servers
+pkg_escalus_homepage = https://github.com/esl/escalus
+pkg_escalus_fetch = git
+pkg_escalus_repo = https://github.com/esl/escalus
+pkg_escalus_commit = master
+
+PACKAGES += esh_mk
+pkg_esh_mk_name = esh_mk
+pkg_esh_mk_description = esh template engine plugin for erlang.mk
+pkg_esh_mk_homepage = https://github.com/crownedgrouse/esh.mk
+pkg_esh_mk_fetch = git
+pkg_esh_mk_repo = https://github.com/crownedgrouse/esh.mk.git
+pkg_esh_mk_commit = master
+
+PACKAGES += espec
+pkg_espec_name = espec
+pkg_espec_description = ESpec: Behaviour driven development framework for Erlang
+pkg_espec_homepage = https://github.com/lucaspiller/espec
+pkg_espec_fetch = git
+pkg_espec_repo = https://github.com/lucaspiller/espec
+pkg_espec_commit = master
+
+PACKAGES += estatsd
+pkg_estatsd_name = estatsd
+pkg_estatsd_description = Erlang stats aggregation app that periodically flushes data to graphite
+pkg_estatsd_homepage = https://github.com/RJ/estatsd
+pkg_estatsd_fetch = git
+pkg_estatsd_repo = https://github.com/RJ/estatsd
+pkg_estatsd_commit = master
+
+PACKAGES += etap
+pkg_etap_name = etap
+pkg_etap_description = etap is a simple erlang testing library that provides TAP compliant output.
+pkg_etap_homepage = https://github.com/ngerakines/etap
+pkg_etap_fetch = git
+pkg_etap_repo = https://github.com/ngerakines/etap
+pkg_etap_commit = master
+
+PACKAGES += etest
+pkg_etest_name = etest
+pkg_etest_description = A lightweight, convention over configuration test framework for Erlang
+pkg_etest_homepage = https://github.com/wooga/etest
+pkg_etest_fetch = git
+pkg_etest_repo = https://github.com/wooga/etest
+pkg_etest_commit = master
+
+PACKAGES += etest_http
+pkg_etest_http_name = etest_http
+pkg_etest_http_description = etest Assertions around HTTP (client-side)
+pkg_etest_http_homepage = https://github.com/wooga/etest_http
+pkg_etest_http_fetch = git
+pkg_etest_http_repo = https://github.com/wooga/etest_http
+pkg_etest_http_commit = master
+
+PACKAGES += etoml
+pkg_etoml_name = etoml
+pkg_etoml_description = TOML language erlang parser
+pkg_etoml_homepage = https://github.com/kalta/etoml
+pkg_etoml_fetch = git
+pkg_etoml_repo = https://github.com/kalta/etoml
+pkg_etoml_commit = master
+
+PACKAGES += eunit
+pkg_eunit_name = eunit
+pkg_eunit_description = The EUnit lightweight unit testing framework for Erlang - this is the canonical development repository.
+pkg_eunit_homepage = https://github.com/richcarl/eunit
+pkg_eunit_fetch = git
+pkg_eunit_repo = https://github.com/richcarl/eunit
+pkg_eunit_commit = master
+
+PACKAGES += eunit_formatters
+pkg_eunit_formatters_name = eunit_formatters
+pkg_eunit_formatters_description = Because eunit's output sucks. Let's make it better.
+pkg_eunit_formatters_homepage = https://github.com/seancribbs/eunit_formatters
+pkg_eunit_formatters_fetch = git
+pkg_eunit_formatters_repo = https://github.com/seancribbs/eunit_formatters
+pkg_eunit_formatters_commit = master
+
+PACKAGES += euthanasia
+pkg_euthanasia_name = euthanasia
+pkg_euthanasia_description = Merciful killer for your Erlang processes
+pkg_euthanasia_homepage = https://github.com/doubleyou/euthanasia
+pkg_euthanasia_fetch = git
+pkg_euthanasia_repo = https://github.com/doubleyou/euthanasia
+pkg_euthanasia_commit = master
+
+PACKAGES += evum
+pkg_evum_name = evum
+pkg_evum_description = Spawn Linux VMs as Erlang processes in the Erlang VM
+pkg_evum_homepage = https://github.com/msantos/evum
+pkg_evum_fetch = git
+pkg_evum_repo = https://github.com/msantos/evum
+pkg_evum_commit = master
+
+PACKAGES += exec
+pkg_exec_name = erlexec
+pkg_exec_description = Execute and control OS processes from Erlang/OTP.
+pkg_exec_homepage = http://saleyn.github.com/erlexec
+pkg_exec_fetch = git
+pkg_exec_repo = https://github.com/saleyn/erlexec
+pkg_exec_commit = master
+
+PACKAGES += exml
+pkg_exml_name = exml
+pkg_exml_description = XML parsing library in Erlang
+pkg_exml_homepage = https://github.com/paulgray/exml
+pkg_exml_fetch = git
+pkg_exml_repo = https://github.com/paulgray/exml
+pkg_exml_commit = master
+
+PACKAGES += exometer
+pkg_exometer_name = exometer
+pkg_exometer_description = Basic measurement objects and probe behavior
+pkg_exometer_homepage = https://github.com/Feuerlabs/exometer
+pkg_exometer_fetch = git
+pkg_exometer_repo = https://github.com/Feuerlabs/exometer
+pkg_exometer_commit = master
+
+PACKAGES += exs1024
+pkg_exs1024_name = exs1024
+pkg_exs1024_description = Xorshift1024star pseudo random number generator for Erlang.
+pkg_exs1024_homepage = https://github.com/jj1bdx/exs1024
+pkg_exs1024_fetch = git
+pkg_exs1024_repo = https://github.com/jj1bdx/exs1024
+pkg_exs1024_commit = master
+
+PACKAGES += exs64
+pkg_exs64_name = exs64
+pkg_exs64_description = Xorshift64star pseudo random number generator for Erlang.
+pkg_exs64_homepage = https://github.com/jj1bdx/exs64
+pkg_exs64_fetch = git
+pkg_exs64_repo = https://github.com/jj1bdx/exs64
+pkg_exs64_commit = master
+
+PACKAGES += exsplus116
+pkg_exsplus116_name = exsplus116
+pkg_exsplus116_description = Xorshift116plus for Erlang
+pkg_exsplus116_homepage = https://github.com/jj1bdx/exsplus116
+pkg_exsplus116_fetch = git
+pkg_exsplus116_repo = https://github.com/jj1bdx/exsplus116
+pkg_exsplus116_commit = master
+
+PACKAGES += exsplus128
+pkg_exsplus128_name = exsplus128
+pkg_exsplus128_description = Xorshift128plus pseudo random number generator for Erlang.
+pkg_exsplus128_homepage = https://github.com/jj1bdx/exsplus128
+pkg_exsplus128_fetch = git
+pkg_exsplus128_repo = https://github.com/jj1bdx/exsplus128
+pkg_exsplus128_commit = master
+
+PACKAGES += ezmq
+pkg_ezmq_name = ezmq
+pkg_ezmq_description = zMQ implemented in Erlang
+pkg_ezmq_homepage = https://github.com/RoadRunnr/ezmq
+pkg_ezmq_fetch = git
+pkg_ezmq_repo = https://github.com/RoadRunnr/ezmq
+pkg_ezmq_commit = master
+
+PACKAGES += ezmtp
+pkg_ezmtp_name = ezmtp
+pkg_ezmtp_description = ZMTP protocol in pure Erlang.
+pkg_ezmtp_homepage = https://github.com/a13x/ezmtp
+pkg_ezmtp_fetch = git
+pkg_ezmtp_repo = https://github.com/a13x/ezmtp
+pkg_ezmtp_commit = master
+
+PACKAGES += fast_disk_log
+pkg_fast_disk_log_name = fast_disk_log
+pkg_fast_disk_log_description = Pool-based asynchronous Erlang disk logger
+pkg_fast_disk_log_homepage = https://github.com/lpgauth/fast_disk_log
+pkg_fast_disk_log_fetch = git
+pkg_fast_disk_log_repo = https://github.com/lpgauth/fast_disk_log
+pkg_fast_disk_log_commit = master
+
+PACKAGES += feeder
+pkg_feeder_name = feeder
+pkg_feeder_description = Stream parse RSS and Atom formatted XML feeds.
+pkg_feeder_homepage = https://github.com/michaelnisi/feeder
+pkg_feeder_fetch = git
+pkg_feeder_repo = https://github.com/michaelnisi/feeder
+pkg_feeder_commit = master
+
+PACKAGES += find_crate
+pkg_find_crate_name = find_crate
+pkg_find_crate_description = Find Rust libs and exes in Erlang application priv directory
+pkg_find_crate_homepage = https://github.com/goertzenator/find_crate
+pkg_find_crate_fetch = git
+pkg_find_crate_repo = https://github.com/goertzenator/find_crate
+pkg_find_crate_commit = master
+
+PACKAGES += fix
+pkg_fix_name = fix
+pkg_fix_description = http://fixprotocol.org/ implementation.
+pkg_fix_homepage = https://github.com/maxlapshin/fix
+pkg_fix_fetch = git
+pkg_fix_repo = https://github.com/maxlapshin/fix
+pkg_fix_commit = master
+
+PACKAGES += flower
+pkg_flower_name = flower
+pkg_flower_description = FlowER - a Erlang OpenFlow development platform
+pkg_flower_homepage = https://github.com/travelping/flower
+pkg_flower_fetch = git
+pkg_flower_repo = https://github.com/travelping/flower
+pkg_flower_commit = master
+
+PACKAGES += fn
+pkg_fn_name = fn
+pkg_fn_description = Function utilities for Erlang
+pkg_fn_homepage = https://github.com/reiddraper/fn
+pkg_fn_fetch = git
+pkg_fn_repo = https://github.com/reiddraper/fn
+pkg_fn_commit = master
+
+PACKAGES += folsom
+pkg_folsom_name = folsom
+pkg_folsom_description = Expose Erlang Events and Metrics
+pkg_folsom_homepage = https://github.com/boundary/folsom
+pkg_folsom_fetch = git
+pkg_folsom_repo = https://github.com/boundary/folsom
+pkg_folsom_commit = master
+
+PACKAGES += folsom_cowboy
+pkg_folsom_cowboy_name = folsom_cowboy
+pkg_folsom_cowboy_description = A Cowboy based Folsom HTTP Wrapper.
+pkg_folsom_cowboy_homepage = https://github.com/boundary/folsom_cowboy
+pkg_folsom_cowboy_fetch = git
+pkg_folsom_cowboy_repo = https://github.com/boundary/folsom_cowboy
+pkg_folsom_cowboy_commit = master
+
+PACKAGES += folsomite
+pkg_folsomite_name = folsomite
+pkg_folsomite_description = blow up your graphite / riemann server with folsom metrics
+pkg_folsomite_homepage = https://github.com/campanja/folsomite
+pkg_folsomite_fetch = git
+pkg_folsomite_repo = https://github.com/campanja/folsomite
+pkg_folsomite_commit = master
+
+PACKAGES += fs
+pkg_fs_name = fs
+pkg_fs_description = Erlang FileSystem Listener
+pkg_fs_homepage = https://github.com/synrc/fs
+pkg_fs_fetch = git
+pkg_fs_repo = https://github.com/synrc/fs
+pkg_fs_commit = master
+
+PACKAGES += fuse
+pkg_fuse_name = fuse
+pkg_fuse_description = A Circuit Breaker for Erlang
+pkg_fuse_homepage = https://github.com/jlouis/fuse
+pkg_fuse_fetch = git
+pkg_fuse_repo = https://github.com/jlouis/fuse
+pkg_fuse_commit = master
+
+PACKAGES += gcm
+pkg_gcm_name = gcm
+pkg_gcm_description = An Erlang application for Google Cloud Messaging
+pkg_gcm_homepage = https://github.com/pdincau/gcm-erlang
+pkg_gcm_fetch = git
+pkg_gcm_repo = https://github.com/pdincau/gcm-erlang
+pkg_gcm_commit = master
+
+PACKAGES += gcprof
+pkg_gcprof_name = gcprof
+pkg_gcprof_description = Garbage Collection profiler for Erlang
+pkg_gcprof_homepage = https://github.com/knutin/gcprof
+pkg_gcprof_fetch = git
+pkg_gcprof_repo = https://github.com/knutin/gcprof
+pkg_gcprof_commit = master
+
+PACKAGES += geas
+pkg_geas_name = geas
+pkg_geas_description = Guess Erlang Application Scattering
+pkg_geas_homepage = https://github.com/crownedgrouse/geas
+pkg_geas_fetch = git
+pkg_geas_repo = https://github.com/crownedgrouse/geas
+pkg_geas_commit = master
+
+PACKAGES += geef
+pkg_geef_name = geef
+pkg_geef_description = Git NEEEEF (Erlang NIF)
+pkg_geef_homepage = https://github.com/carlosmn/geef
+pkg_geef_fetch = git
+pkg_geef_repo = https://github.com/carlosmn/geef
+pkg_geef_commit = master
+
+PACKAGES += gen_coap
+pkg_gen_coap_name = gen_coap
+pkg_gen_coap_description = Generic Erlang CoAP Client/Server
+pkg_gen_coap_homepage = https://github.com/gotthardp/gen_coap
+pkg_gen_coap_fetch = git
+pkg_gen_coap_repo = https://github.com/gotthardp/gen_coap
+pkg_gen_coap_commit = master
+
+PACKAGES += gen_cycle
+pkg_gen_cycle_name = gen_cycle
+pkg_gen_cycle_description = Simple, generic OTP behaviour for recurring tasks
+pkg_gen_cycle_homepage = https://github.com/aerosol/gen_cycle
+pkg_gen_cycle_fetch = git
+pkg_gen_cycle_repo = https://github.com/aerosol/gen_cycle
+pkg_gen_cycle_commit = develop
+
+PACKAGES += gen_icmp
+pkg_gen_icmp_name = gen_icmp
+pkg_gen_icmp_description = Erlang interface to ICMP sockets
+pkg_gen_icmp_homepage = https://github.com/msantos/gen_icmp
+pkg_gen_icmp_fetch = git
+pkg_gen_icmp_repo = https://github.com/msantos/gen_icmp
+pkg_gen_icmp_commit = master
+
+PACKAGES += gen_leader
+pkg_gen_leader_name = gen_leader
+pkg_gen_leader_description = leader election behavior
+pkg_gen_leader_homepage = https://github.com/garret-smith/gen_leader_revival
+pkg_gen_leader_fetch = git
+pkg_gen_leader_repo = https://github.com/garret-smith/gen_leader_revival
+pkg_gen_leader_commit = master
+
+PACKAGES += gen_nb_server
+pkg_gen_nb_server_name = gen_nb_server
+pkg_gen_nb_server_description = OTP behavior for writing non-blocking servers
+pkg_gen_nb_server_homepage = https://github.com/kevsmith/gen_nb_server
+pkg_gen_nb_server_fetch = git
+pkg_gen_nb_server_repo = https://github.com/kevsmith/gen_nb_server
+pkg_gen_nb_server_commit = master
+
+PACKAGES += gen_paxos
+pkg_gen_paxos_name = gen_paxos
+pkg_gen_paxos_description = An Erlang/OTP-style implementation of the PAXOS distributed consensus protocol
+pkg_gen_paxos_homepage = https://github.com/gburd/gen_paxos
+pkg_gen_paxos_fetch = git
+pkg_gen_paxos_repo = https://github.com/gburd/gen_paxos
+pkg_gen_paxos_commit = master
+
+PACKAGES += gen_rpc
+pkg_gen_rpc_name = gen_rpc
+pkg_gen_rpc_description = A scalable RPC library for Erlang-VM based languages
+pkg_gen_rpc_homepage = https://github.com/priestjim/gen_rpc.git
+pkg_gen_rpc_fetch = git
+pkg_gen_rpc_repo = https://github.com/priestjim/gen_rpc.git
+pkg_gen_rpc_commit = master
+
+PACKAGES += gen_smtp
+pkg_gen_smtp_name = gen_smtp
+pkg_gen_smtp_description = A generic Erlang SMTP server and client that can be extended via callback modules
+pkg_gen_smtp_homepage = https://github.com/Vagabond/gen_smtp
+pkg_gen_smtp_fetch = git
+pkg_gen_smtp_repo = https://github.com/Vagabond/gen_smtp
+pkg_gen_smtp_commit = master
+
+PACKAGES += gen_tracker
+pkg_gen_tracker_name = gen_tracker
+pkg_gen_tracker_description = supervisor with ets handling of children and their metadata
+pkg_gen_tracker_homepage = https://github.com/erlyvideo/gen_tracker
+pkg_gen_tracker_fetch = git
+pkg_gen_tracker_repo = https://github.com/erlyvideo/gen_tracker
+pkg_gen_tracker_commit = master
+
+PACKAGES += gen_unix
+pkg_gen_unix_name = gen_unix
+pkg_gen_unix_description = Erlang Unix socket interface
+pkg_gen_unix_homepage = https://github.com/msantos/gen_unix
+pkg_gen_unix_fetch = git
+pkg_gen_unix_repo = https://github.com/msantos/gen_unix
+pkg_gen_unix_commit = master
+
+PACKAGES += geode
+pkg_geode_name = geode
+pkg_geode_description = geohash/proximity lookup in pure, uncut erlang.
+pkg_geode_homepage = https://github.com/bradfordw/geode
+pkg_geode_fetch = git
+pkg_geode_repo = https://github.com/bradfordw/geode
+pkg_geode_commit = master
+
+PACKAGES += getopt
+pkg_getopt_name = getopt
+pkg_getopt_description = Module to parse command line arguments using the GNU getopt syntax
+pkg_getopt_homepage = https://github.com/jcomellas/getopt
+pkg_getopt_fetch = git
+pkg_getopt_repo = https://github.com/jcomellas/getopt
+pkg_getopt_commit = master
+
+PACKAGES += gettext
+pkg_gettext_name = gettext
+pkg_gettext_description = Erlang internationalization library.
+pkg_gettext_homepage = https://github.com/etnt/gettext
+pkg_gettext_fetch = git
+pkg_gettext_repo = https://github.com/etnt/gettext
+pkg_gettext_commit = master
+
+PACKAGES += giallo
+pkg_giallo_name = giallo
+pkg_giallo_description = Small and flexible web framework on top of Cowboy
+pkg_giallo_homepage = https://github.com/kivra/giallo
+pkg_giallo_fetch = git
+pkg_giallo_repo = https://github.com/kivra/giallo
+pkg_giallo_commit = master
+
+PACKAGES += gin
+pkg_gin_name = gin
+pkg_gin_description = The guards and for Erlang parse_transform
+pkg_gin_homepage = https://github.com/mad-cocktail/gin
+pkg_gin_fetch = git
+pkg_gin_repo = https://github.com/mad-cocktail/gin
+pkg_gin_commit = master
+
+PACKAGES += gitty
+pkg_gitty_name = gitty
+pkg_gitty_description = Git access in erlang
+pkg_gitty_homepage = https://github.com/maxlapshin/gitty
+pkg_gitty_fetch = git
+pkg_gitty_repo = https://github.com/maxlapshin/gitty
+pkg_gitty_commit = master
+
+PACKAGES += gold_fever
+pkg_gold_fever_name = gold_fever
+pkg_gold_fever_description = A Treasure Hunt for Erlangers
+pkg_gold_fever_homepage = https://github.com/inaka/gold_fever
+pkg_gold_fever_fetch = git
+pkg_gold_fever_repo = https://github.com/inaka/gold_fever
+pkg_gold_fever_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 += gproc
+pkg_gproc_name = gproc
+pkg_gproc_description = Extended process registry for Erlang
+pkg_gproc_homepage = https://github.com/uwiger/gproc
+pkg_gproc_fetch = git
+pkg_gproc_repo = https://github.com/uwiger/gproc
+pkg_gproc_commit = master
+
+PACKAGES += grapherl
+pkg_grapherl_name = grapherl
+pkg_grapherl_description = Create graphs of Erlang systems and programs
+pkg_grapherl_homepage = https://github.com/eproxus/grapherl
+pkg_grapherl_fetch = git
+pkg_grapherl_repo = https://github.com/eproxus/grapherl
+pkg_grapherl_commit = master
+
+PACKAGES += grpc
+pkg_grpc_name = grpc
+pkg_grpc_description = gRPC server in Erlang
+pkg_grpc_homepage = https://github.com/Bluehouse-Technology/grpc
+pkg_grpc_fetch = git
+pkg_grpc_repo = https://github.com/Bluehouse-Technology/grpc
+pkg_grpc_commit = master
+
+PACKAGES += grpc_client
+pkg_grpc_client_name = grpc_client
+pkg_grpc_client_description = gRPC client in Erlang
+pkg_grpc_client_homepage = https://github.com/Bluehouse-Technology/grpc_client
+pkg_grpc_client_fetch = git
+pkg_grpc_client_repo = https://github.com/Bluehouse-Technology/grpc_client
+pkg_grpc_client_commit = master
+
+PACKAGES += gun
+pkg_gun_name = gun
+pkg_gun_description = Asynchronous SPDY, HTTP and Websocket client written in Erlang.
+pkg_gun_homepage = http//ninenines.eu
+pkg_gun_fetch = git
+pkg_gun_repo = https://github.com/ninenines/gun
+pkg_gun_commit = master
+
+PACKAGES += gut
+pkg_gut_name = gut
+pkg_gut_description = gut is a template printing, aka scaffolding, tool for Erlang. Like rails generate or yeoman
+pkg_gut_homepage = https://github.com/unbalancedparentheses/gut
+pkg_gut_fetch = git
+pkg_gut_repo = https://github.com/unbalancedparentheses/gut
+pkg_gut_commit = master
+
+PACKAGES += hackney
+pkg_hackney_name = hackney
+pkg_hackney_description = simple HTTP client in Erlang
+pkg_hackney_homepage = https://github.com/benoitc/hackney
+pkg_hackney_fetch = git
+pkg_hackney_repo = https://github.com/benoitc/hackney
+pkg_hackney_commit = master
+
+PACKAGES += hamcrest
+pkg_hamcrest_name = hamcrest
+pkg_hamcrest_description = Erlang port of Hamcrest
+pkg_hamcrest_homepage = https://github.com/hyperthunk/hamcrest-erlang
+pkg_hamcrest_fetch = git
+pkg_hamcrest_repo = https://github.com/hyperthunk/hamcrest-erlang
+pkg_hamcrest_commit = master
+
+PACKAGES += hanoidb
+pkg_hanoidb_name = hanoidb
+pkg_hanoidb_description = Erlang LSM BTree Storage
+pkg_hanoidb_homepage = https://github.com/krestenkrab/hanoidb
+pkg_hanoidb_fetch = git
+pkg_hanoidb_repo = https://github.com/krestenkrab/hanoidb
+pkg_hanoidb_commit = master
+
+PACKAGES += hottub
+pkg_hottub_name = hottub
+pkg_hottub_description = Permanent Erlang Worker Pool
+pkg_hottub_homepage = https://github.com/bfrog/hottub
+pkg_hottub_fetch = git
+pkg_hottub_repo = https://github.com/bfrog/hottub
+pkg_hottub_commit = master
+
+PACKAGES += hpack
+pkg_hpack_name = hpack
+pkg_hpack_description = HPACK Implementation for Erlang
+pkg_hpack_homepage = https://github.com/joedevivo/hpack
+pkg_hpack_fetch = git
+pkg_hpack_repo = https://github.com/joedevivo/hpack
+pkg_hpack_commit = master
+
+PACKAGES += hyper
+pkg_hyper_name = hyper
+pkg_hyper_description = Erlang implementation of HyperLogLog
+pkg_hyper_homepage = https://github.com/GameAnalytics/hyper
+pkg_hyper_fetch = git
+pkg_hyper_repo = https://github.com/GameAnalytics/hyper
+pkg_hyper_commit = master
+
+PACKAGES += i18n
+pkg_i18n_name = i18n
+pkg_i18n_description = International components for unicode from Erlang (unicode, date, string, number, format, locale, localization, transliteration, icu4e)
+pkg_i18n_homepage = https://github.com/erlang-unicode/i18n
+pkg_i18n_fetch = git
+pkg_i18n_repo = https://github.com/erlang-unicode/i18n
+pkg_i18n_commit = master
+
+PACKAGES += ibrowse
+pkg_ibrowse_name = ibrowse
+pkg_ibrowse_description = Erlang HTTP client
+pkg_ibrowse_homepage = https://github.com/cmullaparthi/ibrowse
+pkg_ibrowse_fetch = git
+pkg_ibrowse_repo = https://github.com/cmullaparthi/ibrowse
+pkg_ibrowse_commit = master
+
+PACKAGES += idna
+pkg_idna_name = idna
+pkg_idna_description = Erlang IDNA lib
+pkg_idna_homepage = https://github.com/benoitc/erlang-idna
+pkg_idna_fetch = git
+pkg_idna_repo = https://github.com/benoitc/erlang-idna
+pkg_idna_commit = master
+
+PACKAGES += ierlang
+pkg_ierlang_name = ierlang
+pkg_ierlang_description = An Erlang language kernel for IPython.
+pkg_ierlang_homepage = https://github.com/robbielynch/ierlang
+pkg_ierlang_fetch = git
+pkg_ierlang_repo = https://github.com/robbielynch/ierlang
+pkg_ierlang_commit = master
+
+PACKAGES += iota
+pkg_iota_name = iota
+pkg_iota_description = iota (Inter-dependency Objective Testing Apparatus) - a tool to enforce clean separation of responsibilities in Erlang code
+pkg_iota_homepage = https://github.com/jpgneves/iota
+pkg_iota_fetch = git
+pkg_iota_repo = https://github.com/jpgneves/iota
+pkg_iota_commit = master
+
+PACKAGES += irc_lib
+pkg_irc_lib_name = irc_lib
+pkg_irc_lib_description = Erlang irc client library
+pkg_irc_lib_homepage = https://github.com/OtpChatBot/irc_lib
+pkg_irc_lib_fetch = git
+pkg_irc_lib_repo = https://github.com/OtpChatBot/irc_lib
+pkg_irc_lib_commit = master
+
+PACKAGES += ircd
+pkg_ircd_name = ircd
+pkg_ircd_description = A pluggable IRC daemon application/library for Erlang.
+pkg_ircd_homepage = https://github.com/tonyg/erlang-ircd
+pkg_ircd_fetch = git
+pkg_ircd_repo = https://github.com/tonyg/erlang-ircd
+pkg_ircd_commit = master
+
+PACKAGES += iris
+pkg_iris_name = iris
+pkg_iris_description = Iris Erlang binding
+pkg_iris_homepage = https://github.com/project-iris/iris-erl
+pkg_iris_fetch = git
+pkg_iris_repo = https://github.com/project-iris/iris-erl
+pkg_iris_commit = master
+
+PACKAGES += iso8601
+pkg_iso8601_name = iso8601
+pkg_iso8601_description = Erlang ISO 8601 date formatter/parser
+pkg_iso8601_homepage = https://github.com/seansawyer/erlang_iso8601
+pkg_iso8601_fetch = git
+pkg_iso8601_repo = https://github.com/seansawyer/erlang_iso8601
+pkg_iso8601_commit = master
+
+PACKAGES += jamdb_sybase
+pkg_jamdb_sybase_name = jamdb_sybase
+pkg_jamdb_sybase_description = Erlang driver for SAP Sybase ASE
+pkg_jamdb_sybase_homepage = https://github.com/erlangbureau/jamdb_sybase
+pkg_jamdb_sybase_fetch = git
+pkg_jamdb_sybase_repo = https://github.com/erlangbureau/jamdb_sybase
+pkg_jamdb_sybase_commit = master
+
+PACKAGES += jerg
+pkg_jerg_name = jerg
+pkg_jerg_description = JSON Schema to Erlang Records Generator
+pkg_jerg_homepage = https://github.com/ddossot/jerg
+pkg_jerg_fetch = git
+pkg_jerg_repo = https://github.com/ddossot/jerg
+pkg_jerg_commit = master
+
+PACKAGES += jesse
+pkg_jesse_name = jesse
+pkg_jesse_description = jesse (JSon Schema Erlang) is an implementation of a json schema validator for Erlang.
+pkg_jesse_homepage = https://github.com/for-GET/jesse
+pkg_jesse_fetch = git
+pkg_jesse_repo = https://github.com/for-GET/jesse
+pkg_jesse_commit = master
+
+PACKAGES += jiffy
+pkg_jiffy_name = jiffy
+pkg_jiffy_description = JSON NIFs for Erlang.
+pkg_jiffy_homepage = https://github.com/davisp/jiffy
+pkg_jiffy_fetch = git
+pkg_jiffy_repo = https://github.com/davisp/jiffy
+pkg_jiffy_commit = master
+
+PACKAGES += jiffy_v
+pkg_jiffy_v_name = jiffy_v
+pkg_jiffy_v_description = JSON validation utility
+pkg_jiffy_v_homepage = https://github.com/shizzard/jiffy-v
+pkg_jiffy_v_fetch = git
+pkg_jiffy_v_repo = https://github.com/shizzard/jiffy-v
+pkg_jiffy_v_commit = master
+
+PACKAGES += jobs
+pkg_jobs_name = jobs
+pkg_jobs_description = a Job scheduler for load regulation
+pkg_jobs_homepage = https://github.com/esl/jobs
+pkg_jobs_fetch = git
+pkg_jobs_repo = https://github.com/esl/jobs
+pkg_jobs_commit = master
+
+PACKAGES += joxa
+pkg_joxa_name = joxa
+pkg_joxa_description = A Modern Lisp for the Erlang VM
+pkg_joxa_homepage = https://github.com/joxa/joxa
+pkg_joxa_fetch = git
+pkg_joxa_repo = https://github.com/joxa/joxa
+pkg_joxa_commit = master
+
+PACKAGES += json
+pkg_json_name = json
+pkg_json_description = a high level json library for erlang (17.0+)
+pkg_json_homepage = https://github.com/talentdeficit/json
+pkg_json_fetch = git
+pkg_json_repo = https://github.com/talentdeficit/json
+pkg_json_commit = master
+
+PACKAGES += json_rec
+pkg_json_rec_name = json_rec
+pkg_json_rec_description = JSON to erlang record
+pkg_json_rec_homepage = https://github.com/justinkirby/json_rec
+pkg_json_rec_fetch = git
+pkg_json_rec_repo = https://github.com/justinkirby/json_rec
+pkg_json_rec_commit = master
+
+PACKAGES += jsone
+pkg_jsone_name = jsone
+pkg_jsone_description = An Erlang library for encoding, decoding JSON data.
+pkg_jsone_homepage = https://github.com/sile/jsone.git
+pkg_jsone_fetch = git
+pkg_jsone_repo = https://github.com/sile/jsone.git
+pkg_jsone_commit = master
+
+PACKAGES += jsonerl
+pkg_jsonerl_name = jsonerl
+pkg_jsonerl_description = yet another but slightly different erlang <-> json encoder/decoder
+pkg_jsonerl_homepage = https://github.com/lambder/jsonerl
+pkg_jsonerl_fetch = git
+pkg_jsonerl_repo = https://github.com/lambder/jsonerl
+pkg_jsonerl_commit = master
+
+PACKAGES += jsonpath
+pkg_jsonpath_name = jsonpath
+pkg_jsonpath_description = Fast Erlang JSON data retrieval and updates via javascript-like notation
+pkg_jsonpath_homepage = https://github.com/GeneStevens/jsonpath
+pkg_jsonpath_fetch = git
+pkg_jsonpath_repo = https://github.com/GeneStevens/jsonpath
+pkg_jsonpath_commit = master
+
+PACKAGES += jsonx
+pkg_jsonx_name = jsonx
+pkg_jsonx_description = JSONX is an Erlang library for efficient decode and encode JSON, written in C.
+pkg_jsonx_homepage = https://github.com/iskra/jsonx
+pkg_jsonx_fetch = git
+pkg_jsonx_repo = https://github.com/iskra/jsonx
+pkg_jsonx_commit = master
+
+PACKAGES += jsx
+pkg_jsx_name = jsx
+pkg_jsx_description = An Erlang application for consuming, producing and manipulating JSON.
+pkg_jsx_homepage = https://github.com/talentdeficit/jsx
+pkg_jsx_fetch = git
+pkg_jsx_repo = https://github.com/talentdeficit/jsx
+pkg_jsx_commit = main
+
+PACKAGES += kafka
+pkg_kafka_name = kafka
+pkg_kafka_description = Kafka consumer and producer in Erlang
+pkg_kafka_homepage = https://github.com/wooga/kafka-erlang
+pkg_kafka_fetch = git
+pkg_kafka_repo = https://github.com/wooga/kafka-erlang
+pkg_kafka_commit = master
+
+PACKAGES += kafka_protocol
+pkg_kafka_protocol_name = kafka_protocol
+pkg_kafka_protocol_description = Kafka protocol Erlang library
+pkg_kafka_protocol_homepage = https://github.com/klarna/kafka_protocol
+pkg_kafka_protocol_fetch = git
+pkg_kafka_protocol_repo = https://github.com/klarna/kafka_protocol.git
+pkg_kafka_protocol_commit = master
+
+PACKAGES += kai
+pkg_kai_name = kai
+pkg_kai_description = DHT storage by Takeshi Inoue
+pkg_kai_homepage = https://github.com/synrc/kai
+pkg_kai_fetch = git
+pkg_kai_repo = https://github.com/synrc/kai
+pkg_kai_commit = master
+
+PACKAGES += katja
+pkg_katja_name = katja
+pkg_katja_description = A simple Riemann client written in Erlang.
+pkg_katja_homepage = https://github.com/nifoc/katja
+pkg_katja_fetch = git
+pkg_katja_repo = https://github.com/nifoc/katja
+pkg_katja_commit = master
+
+PACKAGES += kdht
+pkg_kdht_name = kdht
+pkg_kdht_description = kdht is an erlang DHT implementation
+pkg_kdht_homepage = https://github.com/kevinlynx/kdht
+pkg_kdht_fetch = git
+pkg_kdht_repo = https://github.com/kevinlynx/kdht
+pkg_kdht_commit = master
+
+PACKAGES += key2value
+pkg_key2value_name = key2value
+pkg_key2value_description = Erlang 2-way map
+pkg_key2value_homepage = https://github.com/okeuday/key2value
+pkg_key2value_fetch = git
+pkg_key2value_repo = https://github.com/okeuday/key2value
+pkg_key2value_commit = master
+
+PACKAGES += keys1value
+pkg_keys1value_name = keys1value
+pkg_keys1value_description = Erlang set associative map for key lists
+pkg_keys1value_homepage = https://github.com/okeuday/keys1value
+pkg_keys1value_fetch = git
+pkg_keys1value_repo = https://github.com/okeuday/keys1value
+pkg_keys1value_commit = master
+
+PACKAGES += kinetic
+pkg_kinetic_name = kinetic
+pkg_kinetic_description = Erlang Kinesis Client
+pkg_kinetic_homepage = https://github.com/AdRoll/kinetic
+pkg_kinetic_fetch = git
+pkg_kinetic_repo = https://github.com/AdRoll/kinetic
+pkg_kinetic_commit = master
+
+PACKAGES += kjell
+pkg_kjell_name = kjell
+pkg_kjell_description = Erlang Shell
+pkg_kjell_homepage = https://github.com/karlll/kjell
+pkg_kjell_fetch = git
+pkg_kjell_repo = https://github.com/karlll/kjell
+pkg_kjell_commit = master
+
+PACKAGES += kraken
+pkg_kraken_name = kraken
+pkg_kraken_description = Distributed Pubsub Server for Realtime Apps
+pkg_kraken_homepage = https://github.com/Asana/kraken
+pkg_kraken_fetch = git
+pkg_kraken_repo = https://github.com/Asana/kraken
+pkg_kraken_commit = master
+
+PACKAGES += kucumberl
+pkg_kucumberl_name = kucumberl
+pkg_kucumberl_description = A pure-erlang, open-source, implementation of Cucumber
+pkg_kucumberl_homepage = https://github.com/openshine/kucumberl
+pkg_kucumberl_fetch = git
+pkg_kucumberl_repo = https://github.com/openshine/kucumberl
+pkg_kucumberl_commit = master
+
+PACKAGES += kvc
+pkg_kvc_name = kvc
+pkg_kvc_description = KVC - Key Value Coding for Erlang data structures
+pkg_kvc_homepage = https://github.com/etrepum/kvc
+pkg_kvc_fetch = git
+pkg_kvc_repo = https://github.com/etrepum/kvc
+pkg_kvc_commit = master
+
+PACKAGES += kvlists
+pkg_kvlists_name = kvlists
+pkg_kvlists_description = Lists of key-value pairs (decoded JSON) in Erlang
+pkg_kvlists_homepage = https://github.com/jcomellas/kvlists
+pkg_kvlists_fetch = git
+pkg_kvlists_repo = https://github.com/jcomellas/kvlists
+pkg_kvlists_commit = master
+
+PACKAGES += kvs
+pkg_kvs_name = kvs
+pkg_kvs_description = Container and Iterator
+pkg_kvs_homepage = https://github.com/synrc/kvs
+pkg_kvs_fetch = git
+pkg_kvs_repo = https://github.com/synrc/kvs
+pkg_kvs_commit = master
+
+PACKAGES += lager
+pkg_lager_name = lager
+pkg_lager_description = A logging framework for Erlang/OTP.
+pkg_lager_homepage = https://github.com/erlang-lager/lager
+pkg_lager_fetch = git
+pkg_lager_repo = https://github.com/erlang-lager/lager
+pkg_lager_commit = master
+
+PACKAGES += lager_amqp_backend
+pkg_lager_amqp_backend_name = lager_amqp_backend
+pkg_lager_amqp_backend_description = AMQP RabbitMQ Lager backend
+pkg_lager_amqp_backend_homepage = https://github.com/jbrisbin/lager_amqp_backend
+pkg_lager_amqp_backend_fetch = git
+pkg_lager_amqp_backend_repo = https://github.com/jbrisbin/lager_amqp_backend
+pkg_lager_amqp_backend_commit = master
+
+PACKAGES += lager_syslog
+pkg_lager_syslog_name = lager_syslog
+pkg_lager_syslog_description = Syslog backend for lager
+pkg_lager_syslog_homepage = https://github.com/erlang-lager/lager_syslog
+pkg_lager_syslog_fetch = git
+pkg_lager_syslog_repo = https://github.com/erlang-lager/lager_syslog
+pkg_lager_syslog_commit = master
+
+PACKAGES += lambdapad
+pkg_lambdapad_name = lambdapad
+pkg_lambdapad_description = Static site generator using Erlang. Yes, Erlang.
+pkg_lambdapad_homepage = https://github.com/gar1t/lambdapad
+pkg_lambdapad_fetch = git
+pkg_lambdapad_repo = https://github.com/gar1t/lambdapad
+pkg_lambdapad_commit = master
+
+PACKAGES += lasp
+pkg_lasp_name = lasp
+pkg_lasp_description = A Language for Distributed, Eventually Consistent Computations
+pkg_lasp_homepage = http://lasp-lang.org/
+pkg_lasp_fetch = git
+pkg_lasp_repo = https://github.com/lasp-lang/lasp
+pkg_lasp_commit = master
+
+PACKAGES += lasse
+pkg_lasse_name = lasse
+pkg_lasse_description = SSE handler for Cowboy
+pkg_lasse_homepage = https://github.com/inaka/lasse
+pkg_lasse_fetch = git
+pkg_lasse_repo = https://github.com/inaka/lasse
+pkg_lasse_commit = master
+
+PACKAGES += ldap
+pkg_ldap_name = ldap
+pkg_ldap_description = LDAP server written in Erlang
+pkg_ldap_homepage = https://github.com/spawnproc/ldap
+pkg_ldap_fetch = git
+pkg_ldap_repo = https://github.com/spawnproc/ldap
+pkg_ldap_commit = master
+
+PACKAGES += lethink
+pkg_lethink_name = lethink
+pkg_lethink_description = erlang driver for rethinkdb
+pkg_lethink_homepage = https://github.com/taybin/lethink
+pkg_lethink_fetch = git
+pkg_lethink_repo = https://github.com/taybin/lethink
+pkg_lethink_commit = master
+
+PACKAGES += lfe
+pkg_lfe_name = lfe
+pkg_lfe_description = Lisp Flavoured Erlang (LFE)
+pkg_lfe_homepage = https://github.com/rvirding/lfe
+pkg_lfe_fetch = git
+pkg_lfe_repo = https://github.com/rvirding/lfe
+pkg_lfe_commit = master
+
+PACKAGES += ling
+pkg_ling_name = ling
+pkg_ling_description = Erlang on Xen
+pkg_ling_homepage = https://github.com/cloudozer/ling
+pkg_ling_fetch = git
+pkg_ling_repo = https://github.com/cloudozer/ling
+pkg_ling_commit = master
+
+PACKAGES += live
+pkg_live_name = live
+pkg_live_description = Automated module and configuration reloader.
+pkg_live_homepage = http://ninenines.eu
+pkg_live_fetch = git
+pkg_live_repo = https://github.com/ninenines/live
+pkg_live_commit = master
+
+PACKAGES += lmq
+pkg_lmq_name = lmq
+pkg_lmq_description = Lightweight Message Queue
+pkg_lmq_homepage = https://github.com/iij/lmq
+pkg_lmq_fetch = git
+pkg_lmq_repo = https://github.com/iij/lmq
+pkg_lmq_commit = master
+
+PACKAGES += locker
+pkg_locker_name = locker
+pkg_locker_description = Atomic distributed 'check and set' for short-lived keys
+pkg_locker_homepage = https://github.com/wooga/locker
+pkg_locker_fetch = git
+pkg_locker_repo = https://github.com/wooga/locker
+pkg_locker_commit = master
+
+PACKAGES += locks
+pkg_locks_name = locks
+pkg_locks_description = A scalable, deadlock-resolving resource locker
+pkg_locks_homepage = https://github.com/uwiger/locks
+pkg_locks_fetch = git
+pkg_locks_repo = https://github.com/uwiger/locks
+pkg_locks_commit = master
+
+PACKAGES += log4erl
+pkg_log4erl_name = log4erl
+pkg_log4erl_description = A logger for erlang in the spirit of Log4J.
+pkg_log4erl_homepage = https://github.com/ahmednawras/log4erl
+pkg_log4erl_fetch = git
+pkg_log4erl_repo = https://github.com/ahmednawras/log4erl
+pkg_log4erl_commit = master
+
+PACKAGES += lol
+pkg_lol_name = lol
+pkg_lol_description = Lisp on erLang, and programming is fun again
+pkg_lol_homepage = https://github.com/b0oh/lol
+pkg_lol_fetch = git
+pkg_lol_repo = https://github.com/b0oh/lol
+pkg_lol_commit = master
+
+PACKAGES += lucid
+pkg_lucid_name = lucid
+pkg_lucid_description = HTTP/2 server written in Erlang
+pkg_lucid_homepage = https://github.com/tatsuhiro-t/lucid
+pkg_lucid_fetch = git
+pkg_lucid_repo = https://github.com/tatsuhiro-t/lucid
+pkg_lucid_commit = master
+
+PACKAGES += luerl
+pkg_luerl_name = luerl
+pkg_luerl_description = Lua in Erlang
+pkg_luerl_homepage = https://github.com/rvirding/luerl
+pkg_luerl_fetch = git
+pkg_luerl_repo = https://github.com/rvirding/luerl
+pkg_luerl_commit = develop
+
+PACKAGES += luwak
+pkg_luwak_name = luwak
+pkg_luwak_description = Large-object storage interface for Riak
+pkg_luwak_homepage = https://github.com/basho/luwak
+pkg_luwak_fetch = git
+pkg_luwak_repo = https://github.com/basho/luwak
+pkg_luwak_commit = master
+
+PACKAGES += lux
+pkg_lux_name = lux
+pkg_lux_description = Lux (LUcid eXpect scripting) simplifies test automation and provides an Expect-style execution of commands
+pkg_lux_homepage = https://github.com/hawk/lux
+pkg_lux_fetch = git
+pkg_lux_repo = https://github.com/hawk/lux
+pkg_lux_commit = master
+
+PACKAGES += machi
+pkg_machi_name = machi
+pkg_machi_description = Machi file store
+pkg_machi_homepage = https://github.com/basho/machi
+pkg_machi_fetch = git
+pkg_machi_repo = https://github.com/basho/machi
+pkg_machi_commit = master
+
+PACKAGES += mad
+pkg_mad_name = mad
+pkg_mad_description = Small and Fast Rebar Replacement
+pkg_mad_homepage = https://github.com/synrc/mad
+pkg_mad_fetch = git
+pkg_mad_repo = https://github.com/synrc/mad
+pkg_mad_commit = master
+
+PACKAGES += marina
+pkg_marina_name = marina
+pkg_marina_description = Non-blocking Erlang Cassandra CQL3 client
+pkg_marina_homepage = https://github.com/lpgauth/marina
+pkg_marina_fetch = git
+pkg_marina_repo = https://github.com/lpgauth/marina
+pkg_marina_commit = master
+
+PACKAGES += mavg
+pkg_mavg_name = mavg
+pkg_mavg_description = Erlang :: Exponential moving average library
+pkg_mavg_homepage = https://github.com/EchoTeam/mavg
+pkg_mavg_fetch = git
+pkg_mavg_repo = https://github.com/EchoTeam/mavg
+pkg_mavg_commit = master
+
+PACKAGES += mc_erl
+pkg_mc_erl_name = mc_erl
+pkg_mc_erl_description = mc-erl is a server for Minecraft 1.4.7 written in Erlang.
+pkg_mc_erl_homepage = https://github.com/clonejo/mc-erl
+pkg_mc_erl_fetch = git
+pkg_mc_erl_repo = https://github.com/clonejo/mc-erl
+pkg_mc_erl_commit = master
+
+PACKAGES += mcd
+pkg_mcd_name = mcd
+pkg_mcd_description = Fast memcached protocol client in pure Erlang
+pkg_mcd_homepage = https://github.com/EchoTeam/mcd
+pkg_mcd_fetch = git
+pkg_mcd_repo = https://github.com/EchoTeam/mcd
+pkg_mcd_commit = master
+
+PACKAGES += mcerlang
+pkg_mcerlang_name = mcerlang
+pkg_mcerlang_description = The McErlang model checker for Erlang
+pkg_mcerlang_homepage = https://github.com/fredlund/McErlang
+pkg_mcerlang_fetch = git
+pkg_mcerlang_repo = https://github.com/fredlund/McErlang
+pkg_mcerlang_commit = master
+
+PACKAGES += meck
+pkg_meck_name = meck
+pkg_meck_description = A mocking library for Erlang
+pkg_meck_homepage = https://github.com/eproxus/meck
+pkg_meck_fetch = git
+pkg_meck_repo = https://github.com/eproxus/meck
+pkg_meck_commit = master
+
+PACKAGES += mekao
+pkg_mekao_name = mekao
+pkg_mekao_description = SQL constructor
+pkg_mekao_homepage = https://github.com/ddosia/mekao
+pkg_mekao_fetch = git
+pkg_mekao_repo = https://github.com/ddosia/mekao
+pkg_mekao_commit = master
+
+PACKAGES += memo
+pkg_memo_name = memo
+pkg_memo_description = Erlang memoization server
+pkg_memo_homepage = https://github.com/tuncer/memo
+pkg_memo_fetch = git
+pkg_memo_repo = https://github.com/tuncer/memo
+pkg_memo_commit = master
+
+PACKAGES += merge_index
+pkg_merge_index_name = merge_index
+pkg_merge_index_description = MergeIndex is an Erlang library for storing ordered sets on disk. It is very similar to an SSTable (in Google's Bigtable) or an HFile (in Hadoop).
+pkg_merge_index_homepage = https://github.com/basho/merge_index
+pkg_merge_index_fetch = git
+pkg_merge_index_repo = https://github.com/basho/merge_index
+pkg_merge_index_commit = master
+
+PACKAGES += merl
+pkg_merl_name = merl
+pkg_merl_description = Metaprogramming in Erlang
+pkg_merl_homepage = https://github.com/richcarl/merl
+pkg_merl_fetch = git
+pkg_merl_repo = https://github.com/richcarl/merl
+pkg_merl_commit = master
+
+PACKAGES += mimerl
+pkg_mimerl_name = mimerl
+pkg_mimerl_description = library to handle mimetypes
+pkg_mimerl_homepage = https://github.com/benoitc/mimerl
+pkg_mimerl_fetch = git
+pkg_mimerl_repo = https://github.com/benoitc/mimerl
+pkg_mimerl_commit = master
+
+PACKAGES += mimetypes
+pkg_mimetypes_name = mimetypes
+pkg_mimetypes_description = Erlang MIME types library
+pkg_mimetypes_homepage = https://github.com/spawngrid/mimetypes
+pkg_mimetypes_fetch = git
+pkg_mimetypes_repo = https://github.com/spawngrid/mimetypes
+pkg_mimetypes_commit = master
+
+PACKAGES += mixer
+pkg_mixer_name = mixer
+pkg_mixer_description = Mix in functions from other modules
+pkg_mixer_homepage = https://github.com/chef/mixer
+pkg_mixer_fetch = git
+pkg_mixer_repo = https://github.com/chef/mixer
+pkg_mixer_commit = master
+
+PACKAGES += mochiweb
+pkg_mochiweb_name = mochiweb
+pkg_mochiweb_description = MochiWeb is an Erlang library for building lightweight HTTP servers.
+pkg_mochiweb_homepage = https://github.com/mochi/mochiweb
+pkg_mochiweb_fetch = git
+pkg_mochiweb_repo = https://github.com/mochi/mochiweb
+pkg_mochiweb_commit = main
+
+PACKAGES += mochiweb_xpath
+pkg_mochiweb_xpath_name = mochiweb_xpath
+pkg_mochiweb_xpath_description = XPath support for mochiweb's html parser
+pkg_mochiweb_xpath_homepage = https://github.com/retnuh/mochiweb_xpath
+pkg_mochiweb_xpath_fetch = git
+pkg_mochiweb_xpath_repo = https://github.com/retnuh/mochiweb_xpath
+pkg_mochiweb_xpath_commit = master
+
+PACKAGES += mockgyver
+pkg_mockgyver_name = mockgyver
+pkg_mockgyver_description = A mocking library for Erlang
+pkg_mockgyver_homepage = https://github.com/klajo/mockgyver
+pkg_mockgyver_fetch = git
+pkg_mockgyver_repo = https://github.com/klajo/mockgyver
+pkg_mockgyver_commit = master
+
+PACKAGES += modlib
+pkg_modlib_name = modlib
+pkg_modlib_description = Web framework based on Erlang's inets httpd
+pkg_modlib_homepage = https://github.com/gar1t/modlib
+pkg_modlib_fetch = git
+pkg_modlib_repo = https://github.com/gar1t/modlib
+pkg_modlib_commit = master
+
+PACKAGES += mongodb
+pkg_mongodb_name = mongodb
+pkg_mongodb_description = MongoDB driver for Erlang
+pkg_mongodb_homepage = https://github.com/comtihon/mongodb-erlang
+pkg_mongodb_fetch = git
+pkg_mongodb_repo = https://github.com/comtihon/mongodb-erlang
+pkg_mongodb_commit = master
+
+PACKAGES += mongooseim
+pkg_mongooseim_name = mongooseim
+pkg_mongooseim_description = Jabber / XMPP server with focus on performance and scalability, by Erlang Solutions
+pkg_mongooseim_homepage = https://www.erlang-solutions.com/products/mongooseim-massively-scalable-ejabberd-platform
+pkg_mongooseim_fetch = git
+pkg_mongooseim_repo = https://github.com/esl/MongooseIM
+pkg_mongooseim_commit = master
+
+PACKAGES += moyo
+pkg_moyo_name = moyo
+pkg_moyo_description = Erlang utility functions library
+pkg_moyo_homepage = https://github.com/dwango/moyo
+pkg_moyo_fetch = git
+pkg_moyo_repo = https://github.com/dwango/moyo
+pkg_moyo_commit = master
+
+PACKAGES += msgpack
+pkg_msgpack_name = msgpack
+pkg_msgpack_description = MessagePack (de)serializer implementation for Erlang
+pkg_msgpack_homepage = https://github.com/msgpack/msgpack-erlang
+pkg_msgpack_fetch = git
+pkg_msgpack_repo = https://github.com/msgpack/msgpack-erlang
+pkg_msgpack_commit = master
+
+PACKAGES += mu2
+pkg_mu2_name = mu2
+pkg_mu2_description = Erlang mutation testing tool
+pkg_mu2_homepage = https://github.com/ramsay-t/mu2
+pkg_mu2_fetch = git
+pkg_mu2_repo = https://github.com/ramsay-t/mu2
+pkg_mu2_commit = master
+
+PACKAGES += mustache
+pkg_mustache_name = mustache
+pkg_mustache_description = Mustache template engine for Erlang.
+pkg_mustache_homepage = https://github.com/mojombo/mustache.erl
+pkg_mustache_fetch = git
+pkg_mustache_repo = https://github.com/mojombo/mustache.erl
+pkg_mustache_commit = master
+
+PACKAGES += myproto
+pkg_myproto_name = myproto
+pkg_myproto_description = MySQL Server Protocol in Erlang
+pkg_myproto_homepage = https://github.com/altenwald/myproto
+pkg_myproto_fetch = git
+pkg_myproto_repo = https://github.com/altenwald/myproto
+pkg_myproto_commit = master
+
+PACKAGES += mysql
+pkg_mysql_name = mysql
+pkg_mysql_description = MySQL client library for Erlang/OTP
+pkg_mysql_homepage = https://github.com/mysql-otp/mysql-otp
+pkg_mysql_fetch = git
+pkg_mysql_repo = https://github.com/mysql-otp/mysql-otp
+pkg_mysql_commit = 1.7.0
+
+PACKAGES += n2o
+pkg_n2o_name = n2o
+pkg_n2o_description = WebSocket Application Server
+pkg_n2o_homepage = https://github.com/5HT/n2o
+pkg_n2o_fetch = git
+pkg_n2o_repo = https://github.com/5HT/n2o
+pkg_n2o_commit = master
+
+PACKAGES += nat_upnp
+pkg_nat_upnp_name = nat_upnp
+pkg_nat_upnp_description = Erlang library to map your internal port to an external using UNP IGD
+pkg_nat_upnp_homepage = https://github.com/benoitc/nat_upnp
+pkg_nat_upnp_fetch = git
+pkg_nat_upnp_repo = https://github.com/benoitc/nat_upnp
+pkg_nat_upnp_commit = master
+
+PACKAGES += neo4j
+pkg_neo4j_name = neo4j
+pkg_neo4j_description = Erlang client library for Neo4J.
+pkg_neo4j_homepage = https://github.com/dmitriid/neo4j-erlang
+pkg_neo4j_fetch = git
+pkg_neo4j_repo = https://github.com/dmitriid/neo4j-erlang
+pkg_neo4j_commit = master
+
+PACKAGES += neotoma
+pkg_neotoma_name = neotoma
+pkg_neotoma_description = Erlang library and packrat parser-generator for parsing expression grammars.
+pkg_neotoma_homepage = https://github.com/seancribbs/neotoma
+pkg_neotoma_fetch = git
+pkg_neotoma_repo = https://github.com/seancribbs/neotoma
+pkg_neotoma_commit = master
+
+PACKAGES += newrelic
+pkg_newrelic_name = newrelic
+pkg_newrelic_description = Erlang library for sending metrics to New Relic
+pkg_newrelic_homepage = https://github.com/wooga/newrelic-erlang
+pkg_newrelic_fetch = git
+pkg_newrelic_repo = https://github.com/wooga/newrelic-erlang
+pkg_newrelic_commit = master
+
+PACKAGES += nifty
+pkg_nifty_name = nifty
+pkg_nifty_description = Erlang NIF wrapper generator
+pkg_nifty_homepage = https://github.com/parapluu/nifty
+pkg_nifty_fetch = git
+pkg_nifty_repo = https://github.com/parapluu/nifty
+pkg_nifty_commit = master
+
+PACKAGES += nitrogen_core
+pkg_nitrogen_core_name = nitrogen_core
+pkg_nitrogen_core_description = The core Nitrogen library.
+pkg_nitrogen_core_homepage = http://nitrogenproject.com/
+pkg_nitrogen_core_fetch = git
+pkg_nitrogen_core_repo = https://github.com/nitrogen/nitrogen_core
+pkg_nitrogen_core_commit = master
+
+PACKAGES += nkbase
+pkg_nkbase_name = nkbase
+pkg_nkbase_description = NkBASE distributed database
+pkg_nkbase_homepage = https://github.com/Nekso/nkbase
+pkg_nkbase_fetch = git
+pkg_nkbase_repo = https://github.com/Nekso/nkbase
+pkg_nkbase_commit = develop
+
+PACKAGES += nkdocker
+pkg_nkdocker_name = nkdocker
+pkg_nkdocker_description = Erlang Docker client
+pkg_nkdocker_homepage = https://github.com/Nekso/nkdocker
+pkg_nkdocker_fetch = git
+pkg_nkdocker_repo = https://github.com/Nekso/nkdocker
+pkg_nkdocker_commit = master
+
+PACKAGES += nkpacket
+pkg_nkpacket_name = nkpacket
+pkg_nkpacket_description = Generic Erlang transport layer
+pkg_nkpacket_homepage = https://github.com/Nekso/nkpacket
+pkg_nkpacket_fetch = git
+pkg_nkpacket_repo = https://github.com/Nekso/nkpacket
+pkg_nkpacket_commit = master
+
+PACKAGES += nksip
+pkg_nksip_name = nksip
+pkg_nksip_description = Erlang SIP application server
+pkg_nksip_homepage = https://github.com/kalta/nksip
+pkg_nksip_fetch = git
+pkg_nksip_repo = https://github.com/kalta/nksip
+pkg_nksip_commit = master
+
+PACKAGES += nodefinder
+pkg_nodefinder_name = nodefinder
+pkg_nodefinder_description = automatic node discovery via UDP multicast
+pkg_nodefinder_homepage = https://github.com/erlanger/nodefinder
+pkg_nodefinder_fetch = git
+pkg_nodefinder_repo = https://github.com/okeuday/nodefinder
+pkg_nodefinder_commit = master
+
+PACKAGES += nprocreg
+pkg_nprocreg_name = nprocreg
+pkg_nprocreg_description = Minimal Distributed Erlang Process Registry
+pkg_nprocreg_homepage = http://nitrogenproject.com/
+pkg_nprocreg_fetch = git
+pkg_nprocreg_repo = https://github.com/nitrogen/nprocreg
+pkg_nprocreg_commit = master
+
+PACKAGES += oauth
+pkg_oauth_name = oauth
+pkg_oauth_description = An Erlang OAuth 1.0 implementation
+pkg_oauth_homepage = https://github.com/tim/erlang-oauth
+pkg_oauth_fetch = git
+pkg_oauth_repo = https://github.com/tim/erlang-oauth
+pkg_oauth_commit = master
+
+PACKAGES += oauth2
+pkg_oauth2_name = oauth2
+pkg_oauth2_description = Erlang Oauth2 implementation
+pkg_oauth2_homepage = https://github.com/kivra/oauth2
+pkg_oauth2_fetch = git
+pkg_oauth2_repo = https://github.com/kivra/oauth2
+pkg_oauth2_commit = master
+
+PACKAGES += observer_cli
+pkg_observer_cli_name = observer_cli
+pkg_observer_cli_description = Visualize Erlang/Elixir Nodes On The Command Line
+pkg_observer_cli_homepage = http://zhongwencool.github.io/observer_cli
+pkg_observer_cli_fetch = git
+pkg_observer_cli_repo = https://github.com/zhongwencool/observer_cli
+pkg_observer_cli_commit = master
+
+PACKAGES += octopus
+pkg_octopus_name = octopus
+pkg_octopus_description = Small and flexible pool manager written in Erlang
+pkg_octopus_homepage = https://github.com/erlangbureau/octopus
+pkg_octopus_fetch = git
+pkg_octopus_repo = https://github.com/erlangbureau/octopus
+pkg_octopus_commit = master
+
+PACKAGES += of_protocol
+pkg_of_protocol_name = of_protocol
+pkg_of_protocol_description = OpenFlow Protocol Library for Erlang
+pkg_of_protocol_homepage = https://github.com/FlowForwarding/of_protocol
+pkg_of_protocol_fetch = git
+pkg_of_protocol_repo = https://github.com/FlowForwarding/of_protocol
+pkg_of_protocol_commit = master
+
+PACKAGES += opencouch
+pkg_opencouch_name = couch
+pkg_opencouch_description = A embeddable document oriented database compatible with Apache CouchDB
+pkg_opencouch_homepage = https://github.com/benoitc/opencouch
+pkg_opencouch_fetch = git
+pkg_opencouch_repo = https://github.com/benoitc/opencouch
+pkg_opencouch_commit = master
+
+PACKAGES += openflow
+pkg_openflow_name = openflow
+pkg_openflow_description = An OpenFlow controller written in pure erlang
+pkg_openflow_homepage = https://github.com/renatoaguiar/erlang-openflow
+pkg_openflow_fetch = git
+pkg_openflow_repo = https://github.com/renatoaguiar/erlang-openflow
+pkg_openflow_commit = master
+
+PACKAGES += openid
+pkg_openid_name = openid
+pkg_openid_description = Erlang OpenID
+pkg_openid_homepage = https://github.com/brendonh/erl_openid
+pkg_openid_fetch = git
+pkg_openid_repo = https://github.com/brendonh/erl_openid
+pkg_openid_commit = master
+
+PACKAGES += openpoker
+pkg_openpoker_name = openpoker
+pkg_openpoker_description = Genesis Texas hold'em Game Server
+pkg_openpoker_homepage = https://github.com/hpyhacking/openpoker
+pkg_openpoker_fetch = git
+pkg_openpoker_repo = https://github.com/hpyhacking/openpoker
+pkg_openpoker_commit = master
+
+PACKAGES += otpbp
+pkg_otpbp_name = otpbp
+pkg_otpbp_description = Parse transformer for use new OTP functions in old Erlang/OTP releases (R15, R16, 17, 18, 19)
+pkg_otpbp_homepage = https://github.com/Ledest/otpbp
+pkg_otpbp_fetch = git
+pkg_otpbp_repo = https://github.com/Ledest/otpbp
+pkg_otpbp_commit = master
+
+PACKAGES += pal
+pkg_pal_name = pal
+pkg_pal_description = Pragmatic Authentication Library
+pkg_pal_homepage = https://github.com/manifest/pal
+pkg_pal_fetch = git
+pkg_pal_repo = https://github.com/manifest/pal
+pkg_pal_commit = master
+
+PACKAGES += parse_trans
+pkg_parse_trans_name = parse_trans
+pkg_parse_trans_description = Parse transform utilities for Erlang
+pkg_parse_trans_homepage = https://github.com/uwiger/parse_trans
+pkg_parse_trans_fetch = git
+pkg_parse_trans_repo = https://github.com/uwiger/parse_trans
+pkg_parse_trans_commit = master
+
+PACKAGES += parsexml
+pkg_parsexml_name = parsexml
+pkg_parsexml_description = Simple DOM XML parser with convenient and very simple API
+pkg_parsexml_homepage = https://github.com/maxlapshin/parsexml
+pkg_parsexml_fetch = git
+pkg_parsexml_repo = https://github.com/maxlapshin/parsexml
+pkg_parsexml_commit = master
+
+PACKAGES += partisan
+pkg_partisan_name = partisan
+pkg_partisan_description = High-performance, high-scalability distributed computing with Erlang and Elixir.
+pkg_partisan_homepage = http://partisan.cloud
+pkg_partisan_fetch = git
+pkg_partisan_repo = https://github.com/lasp-lang/partisan
+pkg_partisan_commit = master
+
+PACKAGES += pegjs
+pkg_pegjs_name = pegjs
+pkg_pegjs_description = An implementation of PEG.js grammar for Erlang.
+pkg_pegjs_homepage = https://github.com/dmitriid/pegjs
+pkg_pegjs_fetch = git
+pkg_pegjs_repo = https://github.com/dmitriid/pegjs
+pkg_pegjs_commit = master
+
+PACKAGES += percept2
+pkg_percept2_name = percept2
+pkg_percept2_description = Concurrent profiling tool for Erlang
+pkg_percept2_homepage = https://github.com/huiqing/percept2
+pkg_percept2_fetch = git
+pkg_percept2_repo = https://github.com/huiqing/percept2
+pkg_percept2_commit = master
+
+PACKAGES += pgo
+pkg_pgo_name = pgo
+pkg_pgo_description = Erlang Postgres client and connection pool
+pkg_pgo_homepage = https://github.com/erleans/pgo.git
+pkg_pgo_fetch = git
+pkg_pgo_repo = https://github.com/erleans/pgo.git
+pkg_pgo_commit = master
+
+PACKAGES += pgsql
+pkg_pgsql_name = pgsql
+pkg_pgsql_description = Erlang PostgreSQL driver
+pkg_pgsql_homepage = https://github.com/semiocast/pgsql
+pkg_pgsql_fetch = git
+pkg_pgsql_repo = https://github.com/semiocast/pgsql
+pkg_pgsql_commit = master
+
+PACKAGES += pkgx
+pkg_pkgx_name = pkgx
+pkg_pkgx_description = Build .deb packages from Erlang releases
+pkg_pkgx_homepage = https://github.com/arjan/pkgx
+pkg_pkgx_fetch = git
+pkg_pkgx_repo = https://github.com/arjan/pkgx
+pkg_pkgx_commit = master
+
+PACKAGES += pkt
+pkg_pkt_name = pkt
+pkg_pkt_description = Erlang network protocol library
+pkg_pkt_homepage = https://github.com/msantos/pkt
+pkg_pkt_fetch = git
+pkg_pkt_repo = https://github.com/msantos/pkt
+pkg_pkt_commit = master
+
+PACKAGES += plain_fsm
+pkg_plain_fsm_name = plain_fsm
+pkg_plain_fsm_description = A behaviour/support library for writing plain Erlang FSMs.
+pkg_plain_fsm_homepage = https://github.com/uwiger/plain_fsm
+pkg_plain_fsm_fetch = git
+pkg_plain_fsm_repo = https://github.com/uwiger/plain_fsm
+pkg_plain_fsm_commit = master
+
+PACKAGES += plumtree
+pkg_plumtree_name = plumtree
+pkg_plumtree_description = Epidemic Broadcast Trees
+pkg_plumtree_homepage = https://github.com/helium/plumtree
+pkg_plumtree_fetch = git
+pkg_plumtree_repo = https://github.com/helium/plumtree
+pkg_plumtree_commit = master
+
+PACKAGES += pmod_transform
+pkg_pmod_transform_name = pmod_transform
+pkg_pmod_transform_description = Parse transform for parameterized modules
+pkg_pmod_transform_homepage = https://github.com/erlang/pmod_transform
+pkg_pmod_transform_fetch = git
+pkg_pmod_transform_repo = https://github.com/erlang/pmod_transform
+pkg_pmod_transform_commit = master
+
+PACKAGES += pobox
+pkg_pobox_name = pobox
+pkg_pobox_description = External buffer processes to protect against mailbox overflow in Erlang
+pkg_pobox_homepage = https://github.com/ferd/pobox
+pkg_pobox_fetch = git
+pkg_pobox_repo = https://github.com/ferd/pobox
+pkg_pobox_commit = master
+
+PACKAGES += ponos
+pkg_ponos_name = ponos
+pkg_ponos_description = ponos is a simple yet powerful load generator written in erlang
+pkg_ponos_homepage = https://github.com/klarna/ponos
+pkg_ponos_fetch = git
+pkg_ponos_repo = https://github.com/klarna/ponos
+pkg_ponos_commit = master
+
+PACKAGES += poolboy
+pkg_poolboy_name = poolboy
+pkg_poolboy_description = A hunky Erlang worker pool factory
+pkg_poolboy_homepage = https://github.com/devinus/poolboy
+pkg_poolboy_fetch = git
+pkg_poolboy_repo = https://github.com/devinus/poolboy
+pkg_poolboy_commit = master
+
+PACKAGES += pooler
+pkg_pooler_name = pooler
+pkg_pooler_description = An OTP Process Pool Application
+pkg_pooler_homepage = https://github.com/seth/pooler
+pkg_pooler_fetch = git
+pkg_pooler_repo = https://github.com/seth/pooler
+pkg_pooler_commit = master
+
+PACKAGES += pqueue
+pkg_pqueue_name = pqueue
+pkg_pqueue_description = Erlang Priority Queues
+pkg_pqueue_homepage = https://github.com/okeuday/pqueue
+pkg_pqueue_fetch = git
+pkg_pqueue_repo = https://github.com/okeuday/pqueue
+pkg_pqueue_commit = master
+
+PACKAGES += procket
+pkg_procket_name = procket
+pkg_procket_description = Erlang interface to low level socket operations
+pkg_procket_homepage = http://blog.listincomprehension.com/search/label/procket
+pkg_procket_fetch = git
+pkg_procket_repo = https://github.com/msantos/procket
+pkg_procket_commit = master
+
+PACKAGES += prometheus
+pkg_prometheus_name = prometheus
+pkg_prometheus_description = Prometheus.io client in Erlang
+pkg_prometheus_homepage = https://github.com/deadtrickster/prometheus.erl
+pkg_prometheus_fetch = git
+pkg_prometheus_repo = https://github.com/deadtrickster/prometheus.erl
+pkg_prometheus_commit = master
+
+PACKAGES += prop
+pkg_prop_name = prop
+pkg_prop_description = An Erlang code scaffolding and generator system.
+pkg_prop_homepage = https://github.com/nuex/prop
+pkg_prop_fetch = git
+pkg_prop_repo = https://github.com/nuex/prop
+pkg_prop_commit = master
+
+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 += props
+pkg_props_name = props
+pkg_props_description = Property structure library
+pkg_props_homepage = https://github.com/greyarea/props
+pkg_props_fetch = git
+pkg_props_repo = https://github.com/greyarea/props
+pkg_props_commit = master
+
+PACKAGES += protobuffs
+pkg_protobuffs_name = protobuffs
+pkg_protobuffs_description = An implementation of Google's Protocol Buffers for Erlang, based on ngerakines/erlang_protobuffs.
+pkg_protobuffs_homepage = https://github.com/basho/erlang_protobuffs
+pkg_protobuffs_fetch = git
+pkg_protobuffs_repo = https://github.com/basho/erlang_protobuffs
+pkg_protobuffs_commit = master
+
+PACKAGES += psycho
+pkg_psycho_name = psycho
+pkg_psycho_description = HTTP server that provides a WSGI-like interface for applications and middleware.
+pkg_psycho_homepage = https://github.com/gar1t/psycho
+pkg_psycho_fetch = git
+pkg_psycho_repo = https://github.com/gar1t/psycho
+pkg_psycho_commit = master
+
+PACKAGES += purity
+pkg_purity_name = purity
+pkg_purity_description = A side-effect analyzer for Erlang
+pkg_purity_homepage = https://github.com/mpitid/purity
+pkg_purity_fetch = git
+pkg_purity_repo = https://github.com/mpitid/purity
+pkg_purity_commit = master
+
+PACKAGES += push_service
+pkg_push_service_name = push_service
+pkg_push_service_description = Push service
+pkg_push_service_homepage = https://github.com/hairyhum/push_service
+pkg_push_service_fetch = git
+pkg_push_service_repo = https://github.com/hairyhum/push_service
+pkg_push_service_commit = master
+
+PACKAGES += qdate
+pkg_qdate_name = qdate
+pkg_qdate_description = Date, time, and timezone parsing, formatting, and conversion for Erlang.
+pkg_qdate_homepage = https://github.com/choptastic/qdate
+pkg_qdate_fetch = git
+pkg_qdate_repo = https://github.com/choptastic/qdate
+pkg_qdate_commit = master
+
+PACKAGES += qrcode
+pkg_qrcode_name = qrcode
+pkg_qrcode_description = QR Code encoder in Erlang
+pkg_qrcode_homepage = https://github.com/komone/qrcode
+pkg_qrcode_fetch = git
+pkg_qrcode_repo = https://github.com/komone/qrcode
+pkg_qrcode_commit = master
+
+PACKAGES += quest
+pkg_quest_name = quest
+pkg_quest_description = Learn Erlang through this set of challenges. An interactive system for getting to know Erlang.
+pkg_quest_homepage = https://github.com/eriksoe/ErlangQuest
+pkg_quest_fetch = git
+pkg_quest_repo = https://github.com/eriksoe/ErlangQuest
+pkg_quest_commit = master
+
+PACKAGES += quickrand
+pkg_quickrand_name = quickrand
+pkg_quickrand_description = Quick Erlang Random Number Generation
+pkg_quickrand_homepage = https://github.com/okeuday/quickrand
+pkg_quickrand_fetch = git
+pkg_quickrand_repo = https://github.com/okeuday/quickrand
+pkg_quickrand_commit = master
+
+PACKAGES += rabbit
+pkg_rabbit_name = rabbit
+pkg_rabbit_description = RabbitMQ Server
+pkg_rabbit_homepage = https://www.rabbitmq.com/
+pkg_rabbit_fetch = git
+pkg_rabbit_repo = https://github.com/rabbitmq/rabbitmq-server.git
+pkg_rabbit_commit = master
+
+PACKAGES += rabbit_exchange_type_riak
+pkg_rabbit_exchange_type_riak_name = rabbit_exchange_type_riak
+pkg_rabbit_exchange_type_riak_description = Custom RabbitMQ exchange type for sticking messages in Riak
+pkg_rabbit_exchange_type_riak_homepage = https://github.com/jbrisbin/riak-exchange
+pkg_rabbit_exchange_type_riak_fetch = git
+pkg_rabbit_exchange_type_riak_repo = https://github.com/jbrisbin/riak-exchange
+pkg_rabbit_exchange_type_riak_commit = master
+
+PACKAGES += rack
+pkg_rack_name = rack
+pkg_rack_description = Rack handler for erlang
+pkg_rack_homepage = https://github.com/erlyvideo/rack
+pkg_rack_fetch = git
+pkg_rack_repo = https://github.com/erlyvideo/rack
+pkg_rack_commit = master
+
+PACKAGES += radierl
+pkg_radierl_name = radierl
+pkg_radierl_description = RADIUS protocol stack implemented in Erlang.
+pkg_radierl_homepage = https://github.com/vances/radierl
+pkg_radierl_fetch = git
+pkg_radierl_repo = https://github.com/vances/radierl
+pkg_radierl_commit = master
+
+PACKAGES += rafter
+pkg_rafter_name = rafter
+pkg_rafter_description = An Erlang library application which implements the Raft consensus protocol
+pkg_rafter_homepage = https://github.com/andrewjstone/rafter
+pkg_rafter_fetch = git
+pkg_rafter_repo = https://github.com/andrewjstone/rafter
+pkg_rafter_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 = 1.2.1
+
+PACKAGES += rbeacon
+pkg_rbeacon_name = rbeacon
+pkg_rbeacon_description = LAN discovery and presence in Erlang.
+pkg_rbeacon_homepage = https://github.com/refuge/rbeacon
+pkg_rbeacon_fetch = git
+pkg_rbeacon_repo = https://github.com/refuge/rbeacon
+pkg_rbeacon_commit = master
+
+PACKAGES += rebar
+pkg_rebar_name = rebar
+pkg_rebar_description = Erlang build tool that makes it easy to compile and test Erlang applications, port drivers and releases.
+pkg_rebar_homepage = http://www.rebar3.org
+pkg_rebar_fetch = git
+pkg_rebar_repo = https://github.com/rebar/rebar3
+pkg_rebar_commit = master
+
+PACKAGES += rebus
+pkg_rebus_name = rebus
+pkg_rebus_description = A stupid simple, internal, pub/sub event bus written in- and for Erlang.
+pkg_rebus_homepage = https://github.com/olle/rebus
+pkg_rebus_fetch = git
+pkg_rebus_repo = https://github.com/olle/rebus
+pkg_rebus_commit = master
+
+PACKAGES += rec2json
+pkg_rec2json_name = rec2json
+pkg_rec2json_description = Compile erlang record definitions into modules to convert them to/from json easily.
+pkg_rec2json_homepage = https://github.com/lordnull/rec2json
+pkg_rec2json_fetch = git
+pkg_rec2json_repo = https://github.com/lordnull/rec2json
+pkg_rec2json_commit = master
+
+PACKAGES += recon
+pkg_recon_name = recon
+pkg_recon_description = Collection of functions and scripts to debug Erlang in production.
+pkg_recon_homepage = https://github.com/ferd/recon
+pkg_recon_fetch = git
+pkg_recon_repo = https://github.com/ferd/recon
+pkg_recon_commit = master
+
+PACKAGES += record_info
+pkg_record_info_name = record_info
+pkg_record_info_description = Convert between record and proplist
+pkg_record_info_homepage = https://github.com/bipthelin/erlang-record_info
+pkg_record_info_fetch = git
+pkg_record_info_repo = https://github.com/bipthelin/erlang-record_info
+pkg_record_info_commit = master
+
+PACKAGES += redgrid
+pkg_redgrid_name = redgrid
+pkg_redgrid_description = automatic Erlang node discovery via redis
+pkg_redgrid_homepage = https://github.com/jkvor/redgrid
+pkg_redgrid_fetch = git
+pkg_redgrid_repo = https://github.com/jkvor/redgrid
+pkg_redgrid_commit = master
+
+PACKAGES += redo
+pkg_redo_name = redo
+pkg_redo_description = pipelined erlang redis client
+pkg_redo_homepage = https://github.com/jkvor/redo
+pkg_redo_fetch = git
+pkg_redo_repo = https://github.com/jkvor/redo
+pkg_redo_commit = master
+
+PACKAGES += reload_mk
+pkg_reload_mk_name = reload_mk
+pkg_reload_mk_description = Live reload plugin for erlang.mk.
+pkg_reload_mk_homepage = https://github.com/bullno1/reload.mk
+pkg_reload_mk_fetch = git
+pkg_reload_mk_repo = https://github.com/bullno1/reload.mk
+pkg_reload_mk_commit = master
+
+PACKAGES += reltool_util
+pkg_reltool_util_name = reltool_util
+pkg_reltool_util_description = Erlang reltool utility functionality application
+pkg_reltool_util_homepage = https://github.com/okeuday/reltool_util
+pkg_reltool_util_fetch = git
+pkg_reltool_util_repo = https://github.com/okeuday/reltool_util
+pkg_reltool_util_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 += resource_discovery
+pkg_resource_discovery_name = resource_discovery
+pkg_resource_discovery_description = An application used to dynamically discover resources present in an Erlang node cluster.
+pkg_resource_discovery_homepage = http://erlware.org/
+pkg_resource_discovery_fetch = git
+pkg_resource_discovery_repo = https://github.com/erlware/resource_discovery
+pkg_resource_discovery_commit = master
+
+PACKAGES += restc
+pkg_restc_name = restc
+pkg_restc_description = Erlang Rest Client
+pkg_restc_homepage = https://github.com/kivra/restclient
+pkg_restc_fetch = git
+pkg_restc_repo = https://github.com/kivra/restclient
+pkg_restc_commit = master
+
+PACKAGES += rfc4627_jsonrpc
+pkg_rfc4627_jsonrpc_name = rfc4627_jsonrpc
+pkg_rfc4627_jsonrpc_description = Erlang RFC4627 (JSON) codec and JSON-RPC server implementation.
+pkg_rfc4627_jsonrpc_homepage = https://github.com/tonyg/erlang-rfc4627
+pkg_rfc4627_jsonrpc_fetch = git
+pkg_rfc4627_jsonrpc_repo = https://github.com/tonyg/erlang-rfc4627
+pkg_rfc4627_jsonrpc_commit = master
+
+PACKAGES += riak_control
+pkg_riak_control_name = riak_control
+pkg_riak_control_description = Webmachine-based administration interface for Riak.
+pkg_riak_control_homepage = https://github.com/basho/riak_control
+pkg_riak_control_fetch = git
+pkg_riak_control_repo = https://github.com/basho/riak_control
+pkg_riak_control_commit = master
+
+PACKAGES += riak_core
+pkg_riak_core_name = riak_core
+pkg_riak_core_description = Distributed systems infrastructure used by Riak.
+pkg_riak_core_homepage = https://github.com/basho/riak_core
+pkg_riak_core_fetch = git
+pkg_riak_core_repo = https://github.com/basho/riak_core
+pkg_riak_core_commit = master
+
+PACKAGES += riak_dt
+pkg_riak_dt_name = riak_dt
+pkg_riak_dt_description = Convergent replicated datatypes in Erlang
+pkg_riak_dt_homepage = https://github.com/basho/riak_dt
+pkg_riak_dt_fetch = git
+pkg_riak_dt_repo = https://github.com/basho/riak_dt
+pkg_riak_dt_commit = master
+
+PACKAGES += riak_ensemble
+pkg_riak_ensemble_name = riak_ensemble
+pkg_riak_ensemble_description = Multi-Paxos framework in Erlang
+pkg_riak_ensemble_homepage = https://github.com/basho/riak_ensemble
+pkg_riak_ensemble_fetch = git
+pkg_riak_ensemble_repo = https://github.com/basho/riak_ensemble
+pkg_riak_ensemble_commit = master
+
+PACKAGES += riak_kv
+pkg_riak_kv_name = riak_kv
+pkg_riak_kv_description = Riak Key/Value Store
+pkg_riak_kv_homepage = https://github.com/basho/riak_kv
+pkg_riak_kv_fetch = git
+pkg_riak_kv_repo = https://github.com/basho/riak_kv
+pkg_riak_kv_commit = master
+
+PACKAGES += riak_pg
+pkg_riak_pg_name = riak_pg
+pkg_riak_pg_description = Distributed process groups with riak_core.
+pkg_riak_pg_homepage = https://github.com/cmeiklejohn/riak_pg
+pkg_riak_pg_fetch = git
+pkg_riak_pg_repo = https://github.com/cmeiklejohn/riak_pg
+pkg_riak_pg_commit = master
+
+PACKAGES += riak_pipe
+pkg_riak_pipe_name = riak_pipe
+pkg_riak_pipe_description = Riak Pipelines
+pkg_riak_pipe_homepage = https://github.com/basho/riak_pipe
+pkg_riak_pipe_fetch = git
+pkg_riak_pipe_repo = https://github.com/basho/riak_pipe
+pkg_riak_pipe_commit = master
+
+PACKAGES += riak_sysmon
+pkg_riak_sysmon_name = riak_sysmon
+pkg_riak_sysmon_description = Simple OTP app for managing Erlang VM system_monitor event messages
+pkg_riak_sysmon_homepage = https://github.com/basho/riak_sysmon
+pkg_riak_sysmon_fetch = git
+pkg_riak_sysmon_repo = https://github.com/basho/riak_sysmon
+pkg_riak_sysmon_commit = master
+
+PACKAGES += riak_test
+pkg_riak_test_name = riak_test
+pkg_riak_test_description = I'm in your cluster, testing your riaks
+pkg_riak_test_homepage = https://github.com/basho/riak_test
+pkg_riak_test_fetch = git
+pkg_riak_test_repo = https://github.com/basho/riak_test
+pkg_riak_test_commit = master
+
+PACKAGES += riakc
+pkg_riakc_name = riakc
+pkg_riakc_description = Erlang clients for Riak.
+pkg_riakc_homepage = https://github.com/basho/riak-erlang-client
+pkg_riakc_fetch = git
+pkg_riakc_repo = https://github.com/basho/riak-erlang-client
+pkg_riakc_commit = master
+
+PACKAGES += riakhttpc
+pkg_riakhttpc_name = riakhttpc
+pkg_riakhttpc_description = Riak Erlang client using the HTTP interface
+pkg_riakhttpc_homepage = https://github.com/basho/riak-erlang-http-client
+pkg_riakhttpc_fetch = git
+pkg_riakhttpc_repo = https://github.com/basho/riak-erlang-http-client
+pkg_riakhttpc_commit = master
+
+PACKAGES += riaknostic
+pkg_riaknostic_name = riaknostic
+pkg_riaknostic_description = A diagnostic tool for Riak installations, to find common errors asap
+pkg_riaknostic_homepage = https://github.com/basho/riaknostic
+pkg_riaknostic_fetch = git
+pkg_riaknostic_repo = https://github.com/basho/riaknostic
+pkg_riaknostic_commit = master
+
+PACKAGES += riakpool
+pkg_riakpool_name = riakpool
+pkg_riakpool_description = erlang riak client pool
+pkg_riakpool_homepage = https://github.com/dweldon/riakpool
+pkg_riakpool_fetch = git
+pkg_riakpool_repo = https://github.com/dweldon/riakpool
+pkg_riakpool_commit = master
+
+PACKAGES += rivus_cep
+pkg_rivus_cep_name = rivus_cep
+pkg_rivus_cep_description = Complex event processing in Erlang
+pkg_rivus_cep_homepage = https://github.com/vascokk/rivus_cep
+pkg_rivus_cep_fetch = git
+pkg_rivus_cep_repo = https://github.com/vascokk/rivus_cep
+pkg_rivus_cep_commit = master
+
+PACKAGES += rlimit
+pkg_rlimit_name = rlimit
+pkg_rlimit_description = Magnus Klaar's rate limiter code from etorrent
+pkg_rlimit_homepage = https://github.com/jlouis/rlimit
+pkg_rlimit_fetch = git
+pkg_rlimit_repo = https://github.com/jlouis/rlimit
+pkg_rlimit_commit = master
+
+PACKAGES += rust_mk
+pkg_rust_mk_name = rust_mk
+pkg_rust_mk_description = Build Rust crates in an Erlang application
+pkg_rust_mk_homepage = https://github.com/goertzenator/rust.mk
+pkg_rust_mk_fetch = git
+pkg_rust_mk_repo = https://github.com/goertzenator/rust.mk
+pkg_rust_mk_commit = master
+
+PACKAGES += safetyvalve
+pkg_safetyvalve_name = safetyvalve
+pkg_safetyvalve_description = A safety valve for your erlang node
+pkg_safetyvalve_homepage = https://github.com/jlouis/safetyvalve
+pkg_safetyvalve_fetch = git
+pkg_safetyvalve_repo = https://github.com/jlouis/safetyvalve
+pkg_safetyvalve_commit = master
+
+PACKAGES += seestar
+pkg_seestar_name = seestar
+pkg_seestar_description = The Erlang client for Cassandra 1.2+ binary protocol
+pkg_seestar_homepage = https://github.com/iamaleksey/seestar
+pkg_seestar_fetch = git
+pkg_seestar_repo = https://github.com/iamaleksey/seestar
+pkg_seestar_commit = master
+
+PACKAGES += service
+pkg_service_name = service
+pkg_service_description = A minimal Erlang behavior for creating CloudI internal services
+pkg_service_homepage = http://cloudi.org/
+pkg_service_fetch = git
+pkg_service_repo = https://github.com/CloudI/service
+pkg_service_commit = master
+
+PACKAGES += setup
+pkg_setup_name = setup
+pkg_setup_description = Generic setup utility for Erlang-based systems
+pkg_setup_homepage = https://github.com/uwiger/setup
+pkg_setup_fetch = git
+pkg_setup_repo = https://github.com/uwiger/setup
+pkg_setup_commit = master
+
+PACKAGES += sext
+pkg_sext_name = sext
+pkg_sext_description = Sortable Erlang Term Serialization
+pkg_sext_homepage = https://github.com/uwiger/sext
+pkg_sext_fetch = git
+pkg_sext_repo = https://github.com/uwiger/sext
+pkg_sext_commit = master
+
+PACKAGES += sfmt
+pkg_sfmt_name = sfmt
+pkg_sfmt_description = SFMT pseudo random number generator for Erlang.
+pkg_sfmt_homepage = https://github.com/jj1bdx/sfmt-erlang
+pkg_sfmt_fetch = git
+pkg_sfmt_repo = https://github.com/jj1bdx/sfmt-erlang
+pkg_sfmt_commit = master
+
+PACKAGES += sgte
+pkg_sgte_name = sgte
+pkg_sgte_description = A simple Erlang Template Engine
+pkg_sgte_homepage = https://github.com/filippo/sgte
+pkg_sgte_fetch = git
+pkg_sgte_repo = https://github.com/filippo/sgte
+pkg_sgte_commit = master
+
+PACKAGES += sheriff
+pkg_sheriff_name = sheriff
+pkg_sheriff_description = Parse transform for type based validation.
+pkg_sheriff_homepage = http://ninenines.eu
+pkg_sheriff_fetch = git
+pkg_sheriff_repo = https://github.com/extend/sheriff
+pkg_sheriff_commit = master
+
+PACKAGES += shotgun
+pkg_shotgun_name = shotgun
+pkg_shotgun_description = better than just a gun
+pkg_shotgun_homepage = https://github.com/inaka/shotgun
+pkg_shotgun_fetch = git
+pkg_shotgun_repo = https://github.com/inaka/shotgun
+pkg_shotgun_commit = master
+
+PACKAGES += sidejob
+pkg_sidejob_name = sidejob
+pkg_sidejob_description = Parallel worker and capacity limiting library for Erlang
+pkg_sidejob_homepage = https://github.com/basho/sidejob
+pkg_sidejob_fetch = git
+pkg_sidejob_repo = https://github.com/basho/sidejob
+pkg_sidejob_commit = master
+
+PACKAGES += sieve
+pkg_sieve_name = sieve
+pkg_sieve_description = sieve is a simple TCP routing proxy (layer 7) in erlang
+pkg_sieve_homepage = https://github.com/benoitc/sieve
+pkg_sieve_fetch = git
+pkg_sieve_repo = https://github.com/benoitc/sieve
+pkg_sieve_commit = master
+
+PACKAGES += sighandler
+pkg_sighandler_name = sighandler
+pkg_sighandler_description = Handle UNIX signals in Er lang
+pkg_sighandler_homepage = https://github.com/jkingsbery/sighandler
+pkg_sighandler_fetch = git
+pkg_sighandler_repo = https://github.com/jkingsbery/sighandler
+pkg_sighandler_commit = master
+
+PACKAGES += simhash
+pkg_simhash_name = simhash
+pkg_simhash_description = Simhashing for Erlang -- hashing algorithm to find near-duplicates in binary data.
+pkg_simhash_homepage = https://github.com/ferd/simhash
+pkg_simhash_fetch = git
+pkg_simhash_repo = https://github.com/ferd/simhash
+pkg_simhash_commit = master
+
+PACKAGES += simple_bridge
+pkg_simple_bridge_name = simple_bridge
+pkg_simple_bridge_description = A simple, standardized interface library to Erlang HTTP Servers.
+pkg_simple_bridge_homepage = https://github.com/nitrogen/simple_bridge
+pkg_simple_bridge_fetch = git
+pkg_simple_bridge_repo = https://github.com/nitrogen/simple_bridge
+pkg_simple_bridge_commit = master
+
+PACKAGES += simple_oauth2
+pkg_simple_oauth2_name = simple_oauth2
+pkg_simple_oauth2_description = Simple erlang OAuth2 client module for any http server framework (Google, Facebook, Yandex, Vkontakte are preconfigured)
+pkg_simple_oauth2_homepage = https://github.com/virtan/simple_oauth2
+pkg_simple_oauth2_fetch = git
+pkg_simple_oauth2_repo = https://github.com/virtan/simple_oauth2
+pkg_simple_oauth2_commit = master
+
+PACKAGES += skel
+pkg_skel_name = skel
+pkg_skel_description = A Streaming Process-based Skeleton Library for Erlang
+pkg_skel_homepage = https://github.com/ParaPhrase/skel
+pkg_skel_fetch = git
+pkg_skel_repo = https://github.com/ParaPhrase/skel
+pkg_skel_commit = master
+
+PACKAGES += slack
+pkg_slack_name = slack
+pkg_slack_description = Minimal slack notification OTP library.
+pkg_slack_homepage = https://github.com/DonBranson/slack
+pkg_slack_fetch = git
+pkg_slack_repo = https://github.com/DonBranson/slack.git
+pkg_slack_commit = master
+
+PACKAGES += smother
+pkg_smother_name = smother
+pkg_smother_description = Extended code coverage metrics for Erlang.
+pkg_smother_homepage = https://ramsay-t.github.io/Smother/
+pkg_smother_fetch = git
+pkg_smother_repo = https://github.com/ramsay-t/Smother
+pkg_smother_commit = master
+
+PACKAGES += snappyer
+pkg_snappyer_name = snappyer
+pkg_snappyer_description = Snappy as nif for Erlang
+pkg_snappyer_homepage = https://github.com/zmstone/snappyer
+pkg_snappyer_fetch = git
+pkg_snappyer_repo = https://github.com/zmstone/snappyer.git
+pkg_snappyer_commit = master
+
+PACKAGES += social
+pkg_social_name = social
+pkg_social_description = Cowboy handler for social login via OAuth2 providers
+pkg_social_homepage = https://github.com/dvv/social
+pkg_social_fetch = git
+pkg_social_repo = https://github.com/dvv/social
+pkg_social_commit = master
+
+PACKAGES += spapi_router
+pkg_spapi_router_name = spapi_router
+pkg_spapi_router_description = Partially-connected Erlang clustering
+pkg_spapi_router_homepage = https://github.com/spilgames/spapi-router
+pkg_spapi_router_fetch = git
+pkg_spapi_router_repo = https://github.com/spilgames/spapi-router
+pkg_spapi_router_commit = master
+
+PACKAGES += sqerl
+pkg_sqerl_name = sqerl
+pkg_sqerl_description = An Erlang-flavoured SQL DSL
+pkg_sqerl_homepage = https://github.com/hairyhum/sqerl
+pkg_sqerl_fetch = git
+pkg_sqerl_repo = https://github.com/hairyhum/sqerl
+pkg_sqerl_commit = master
+
+PACKAGES += srly
+pkg_srly_name = srly
+pkg_srly_description = Native Erlang Unix serial interface
+pkg_srly_homepage = https://github.com/msantos/srly
+pkg_srly_fetch = git
+pkg_srly_repo = https://github.com/msantos/srly
+pkg_srly_commit = master
+
+PACKAGES += sshrpc
+pkg_sshrpc_name = sshrpc
+pkg_sshrpc_description = Erlang SSH RPC module (experimental)
+pkg_sshrpc_homepage = https://github.com/jj1bdx/sshrpc
+pkg_sshrpc_fetch = git
+pkg_sshrpc_repo = https://github.com/jj1bdx/sshrpc
+pkg_sshrpc_commit = master
+
+PACKAGES += stable
+pkg_stable_name = stable
+pkg_stable_description = Library of assorted helpers for Cowboy web server.
+pkg_stable_homepage = https://github.com/dvv/stable
+pkg_stable_fetch = git
+pkg_stable_repo = https://github.com/dvv/stable
+pkg_stable_commit = master
+
+PACKAGES += statebox
+pkg_statebox_name = statebox
+pkg_statebox_description = Erlang state monad with merge/conflict-resolution capabilities. Useful for Riak.
+pkg_statebox_homepage = https://github.com/mochi/statebox
+pkg_statebox_fetch = git
+pkg_statebox_repo = https://github.com/mochi/statebox
+pkg_statebox_commit = master
+
+PACKAGES += statebox_riak
+pkg_statebox_riak_name = statebox_riak
+pkg_statebox_riak_description = Convenience library that makes it easier to use statebox with riak, extracted from best practices in our production code at Mochi Media.
+pkg_statebox_riak_homepage = https://github.com/mochi/statebox_riak
+pkg_statebox_riak_fetch = git
+pkg_statebox_riak_repo = https://github.com/mochi/statebox_riak
+pkg_statebox_riak_commit = master
+
+PACKAGES += statman
+pkg_statman_name = statman
+pkg_statman_description = Efficiently collect massive volumes of metrics inside the Erlang VM
+pkg_statman_homepage = https://github.com/knutin/statman
+pkg_statman_fetch = git
+pkg_statman_repo = https://github.com/knutin/statman
+pkg_statman_commit = master
+
+PACKAGES += statsderl
+pkg_statsderl_name = statsderl
+pkg_statsderl_description = StatsD client (erlang)
+pkg_statsderl_homepage = https://github.com/lpgauth/statsderl
+pkg_statsderl_fetch = git
+pkg_statsderl_repo = https://github.com/lpgauth/statsderl
+pkg_statsderl_commit = master
+
+PACKAGES += stdinout_pool
+pkg_stdinout_pool_name = stdinout_pool
+pkg_stdinout_pool_description = stdinout_pool : stuff goes in, stuff goes out. there's never any miscommunication.
+pkg_stdinout_pool_homepage = https://github.com/mattsta/erlang-stdinout-pool
+pkg_stdinout_pool_fetch = git
+pkg_stdinout_pool_repo = https://github.com/mattsta/erlang-stdinout-pool
+pkg_stdinout_pool_commit = master
+
+PACKAGES += stockdb
+pkg_stockdb_name = stockdb
+pkg_stockdb_description = Database for storing Stock Exchange quotes in erlang
+pkg_stockdb_homepage = https://github.com/maxlapshin/stockdb
+pkg_stockdb_fetch = git
+pkg_stockdb_repo = https://github.com/maxlapshin/stockdb
+pkg_stockdb_commit = master
+
+PACKAGES += stripe
+pkg_stripe_name = stripe
+pkg_stripe_description = Erlang interface to the stripe.com API
+pkg_stripe_homepage = https://github.com/mattsta/stripe-erlang
+pkg_stripe_fetch = git
+pkg_stripe_repo = https://github.com/mattsta/stripe-erlang
+pkg_stripe_commit = v1
+
+PACKAGES += subproc
+pkg_subproc_name = subproc
+pkg_subproc_description = unix subprocess manager with {active,once|false} modes
+pkg_subproc_homepage = http://dozzie.jarowit.net/trac/wiki/subproc
+pkg_subproc_fetch = git
+pkg_subproc_repo = https://github.com/dozzie/subproc
+pkg_subproc_commit = v0.1.0
+
+PACKAGES += supervisor3
+pkg_supervisor3_name = supervisor3
+pkg_supervisor3_description = OTP supervisor with additional strategies
+pkg_supervisor3_homepage = https://github.com/klarna/supervisor3
+pkg_supervisor3_fetch = git
+pkg_supervisor3_repo = https://github.com/klarna/supervisor3.git
+pkg_supervisor3_commit = master
+
+PACKAGES += surrogate
+pkg_surrogate_name = surrogate
+pkg_surrogate_description = Proxy server written in erlang. Supports reverse proxy load balancing and forward proxy with http (including CONNECT), socks4, socks5, and transparent proxy modes.
+pkg_surrogate_homepage = https://github.com/skruger/Surrogate
+pkg_surrogate_fetch = git
+pkg_surrogate_repo = https://github.com/skruger/Surrogate
+pkg_surrogate_commit = master
+
+PACKAGES += swab
+pkg_swab_name = swab
+pkg_swab_description = General purpose buffer handling module
+pkg_swab_homepage = https://github.com/crownedgrouse/swab
+pkg_swab_fetch = git
+pkg_swab_repo = https://github.com/crownedgrouse/swab
+pkg_swab_commit = master
+
+PACKAGES += swarm
+pkg_swarm_name = swarm
+pkg_swarm_description = Fast and simple acceptor pool for Erlang
+pkg_swarm_homepage = https://github.com/jeremey/swarm
+pkg_swarm_fetch = git
+pkg_swarm_repo = https://github.com/jeremey/swarm
+pkg_swarm_commit = master
+
+PACKAGES += switchboard
+pkg_switchboard_name = switchboard
+pkg_switchboard_description = A framework for processing email using worker plugins.
+pkg_switchboard_homepage = https://github.com/thusfresh/switchboard
+pkg_switchboard_fetch = git
+pkg_switchboard_repo = https://github.com/thusfresh/switchboard
+pkg_switchboard_commit = master
+
+PACKAGES += syn
+pkg_syn_name = syn
+pkg_syn_description = A global Process Registry and Process Group manager for Erlang.
+pkg_syn_homepage = https://github.com/ostinelli/syn
+pkg_syn_fetch = git
+pkg_syn_repo = https://github.com/ostinelli/syn
+pkg_syn_commit = master
+
+PACKAGES += sync
+pkg_sync_name = sync
+pkg_sync_description = On-the-fly recompiling and reloading in Erlang.
+pkg_sync_homepage = https://github.com/rustyio/sync
+pkg_sync_fetch = git
+pkg_sync_repo = https://github.com/rustyio/sync
+pkg_sync_commit = master
+
+PACKAGES += syntaxerl
+pkg_syntaxerl_name = syntaxerl
+pkg_syntaxerl_description = Syntax checker for Erlang
+pkg_syntaxerl_homepage = https://github.com/ten0s/syntaxerl
+pkg_syntaxerl_fetch = git
+pkg_syntaxerl_repo = https://github.com/ten0s/syntaxerl
+pkg_syntaxerl_commit = master
+
+PACKAGES += syslog
+pkg_syslog_name = syslog
+pkg_syslog_description = Erlang port driver for interacting with syslog via syslog(3)
+pkg_syslog_homepage = https://github.com/Vagabond/erlang-syslog
+pkg_syslog_fetch = git
+pkg_syslog_repo = https://github.com/Vagabond/erlang-syslog
+pkg_syslog_commit = master
+
+PACKAGES += taskforce
+pkg_taskforce_name = taskforce
+pkg_taskforce_description = Erlang worker pools for controlled parallelisation of arbitrary tasks.
+pkg_taskforce_homepage = https://github.com/g-andrade/taskforce
+pkg_taskforce_fetch = git
+pkg_taskforce_repo = https://github.com/g-andrade/taskforce
+pkg_taskforce_commit = master
+
+PACKAGES += tddreloader
+pkg_tddreloader_name = tddreloader
+pkg_tddreloader_description = Shell utility for recompiling, reloading, and testing code as it changes
+pkg_tddreloader_homepage = https://github.com/version2beta/tddreloader
+pkg_tddreloader_fetch = git
+pkg_tddreloader_repo = https://github.com/version2beta/tddreloader
+pkg_tddreloader_commit = master
+
+PACKAGES += tempo
+pkg_tempo_name = tempo
+pkg_tempo_description = NIF-based date and time parsing and formatting for Erlang.
+pkg_tempo_homepage = https://github.com/selectel/tempo
+pkg_tempo_fetch = git
+pkg_tempo_repo = https://github.com/selectel/tempo
+pkg_tempo_commit = master
+
+PACKAGES += ticktick
+pkg_ticktick_name = ticktick
+pkg_ticktick_description = Ticktick is an id generator for message service.
+pkg_ticktick_homepage = https://github.com/ericliang/ticktick
+pkg_ticktick_fetch = git
+pkg_ticktick_repo = https://github.com/ericliang/ticktick
+pkg_ticktick_commit = master
+
+PACKAGES += tinymq
+pkg_tinymq_name = tinymq
+pkg_tinymq_description = TinyMQ - a diminutive, in-memory message queue
+pkg_tinymq_homepage = https://github.com/ChicagoBoss/tinymq
+pkg_tinymq_fetch = git
+pkg_tinymq_repo = https://github.com/ChicagoBoss/tinymq
+pkg_tinymq_commit = master
+
+PACKAGES += tinymt
+pkg_tinymt_name = tinymt
+pkg_tinymt_description = TinyMT pseudo random number generator for Erlang.
+pkg_tinymt_homepage = https://github.com/jj1bdx/tinymt-erlang
+pkg_tinymt_fetch = git
+pkg_tinymt_repo = https://github.com/jj1bdx/tinymt-erlang
+pkg_tinymt_commit = master
+
+PACKAGES += tirerl
+pkg_tirerl_name = tirerl
+pkg_tirerl_description = Erlang interface to Elastic Search
+pkg_tirerl_homepage = https://github.com/inaka/tirerl
+pkg_tirerl_fetch = git
+pkg_tirerl_repo = https://github.com/inaka/tirerl
+pkg_tirerl_commit = master
+
+PACKAGES += toml
+pkg_toml_name = toml
+pkg_toml_description = TOML (0.4.0) config parser
+pkg_toml_homepage = http://dozzie.jarowit.net/trac/wiki/TOML
+pkg_toml_fetch = git
+pkg_toml_repo = https://github.com/dozzie/toml
+pkg_toml_commit = v0.2.0
+
+PACKAGES += traffic_tools
+pkg_traffic_tools_name = traffic_tools
+pkg_traffic_tools_description = Simple traffic limiting library
+pkg_traffic_tools_homepage = https://github.com/systra/traffic_tools
+pkg_traffic_tools_fetch = git
+pkg_traffic_tools_repo = https://github.com/systra/traffic_tools
+pkg_traffic_tools_commit = master
+
+PACKAGES += trails
+pkg_trails_name = trails
+pkg_trails_description = A couple of improvements over Cowboy Routes
+pkg_trails_homepage = http://inaka.github.io/cowboy-trails/
+pkg_trails_fetch = git
+pkg_trails_repo = https://github.com/inaka/cowboy-trails
+pkg_trails_commit = master
+
+PACKAGES += trane
+pkg_trane_name = trane
+pkg_trane_description = SAX style broken HTML parser in Erlang
+pkg_trane_homepage = https://github.com/massemanet/trane
+pkg_trane_fetch = git
+pkg_trane_repo = https://github.com/massemanet/trane
+pkg_trane_commit = master
+
+PACKAGES += transit
+pkg_transit_name = transit
+pkg_transit_description = transit format for erlang
+pkg_transit_homepage = https://github.com/isaiah/transit-erlang
+pkg_transit_fetch = git
+pkg_transit_repo = https://github.com/isaiah/transit-erlang
+pkg_transit_commit = master
+
+PACKAGES += trie
+pkg_trie_name = trie
+pkg_trie_description = Erlang Trie Implementation
+pkg_trie_homepage = https://github.com/okeuday/trie
+pkg_trie_fetch = git
+pkg_trie_repo = https://github.com/okeuday/trie
+pkg_trie_commit = master
+
+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
+
+PACKAGES += tunctl
+pkg_tunctl_name = tunctl
+pkg_tunctl_description = Erlang TUN/TAP interface
+pkg_tunctl_homepage = https://github.com/msantos/tunctl
+pkg_tunctl_fetch = git
+pkg_tunctl_repo = https://github.com/msantos/tunctl
+pkg_tunctl_commit = master
+
+PACKAGES += twerl
+pkg_twerl_name = twerl
+pkg_twerl_description = Erlang client for the Twitter Streaming API
+pkg_twerl_homepage = https://github.com/lucaspiller/twerl
+pkg_twerl_fetch = git
+pkg_twerl_repo = https://github.com/lucaspiller/twerl
+pkg_twerl_commit = oauth
+
+PACKAGES += twitter_erlang
+pkg_twitter_erlang_name = twitter_erlang
+pkg_twitter_erlang_description = An Erlang twitter client
+pkg_twitter_erlang_homepage = https://github.com/ngerakines/erlang_twitter
+pkg_twitter_erlang_fetch = git
+pkg_twitter_erlang_repo = https://github.com/ngerakines/erlang_twitter
+pkg_twitter_erlang_commit = master
+
+PACKAGES += ucol_nif
+pkg_ucol_nif_name = ucol_nif
+pkg_ucol_nif_description = ICU based collation Erlang module
+pkg_ucol_nif_homepage = https://github.com/refuge/ucol_nif
+pkg_ucol_nif_fetch = git
+pkg_ucol_nif_repo = https://github.com/refuge/ucol_nif
+pkg_ucol_nif_commit = master
+
+PACKAGES += unicorn
+pkg_unicorn_name = unicorn
+pkg_unicorn_description = Generic configuration server
+pkg_unicorn_homepage = https://github.com/shizzard/unicorn
+pkg_unicorn_fetch = git
+pkg_unicorn_repo = https://github.com/shizzard/unicorn
+pkg_unicorn_commit = master
+
+PACKAGES += unsplit
+pkg_unsplit_name = unsplit
+pkg_unsplit_description = Resolves conflicts in Mnesia after network splits
+pkg_unsplit_homepage = https://github.com/uwiger/unsplit
+pkg_unsplit_fetch = git
+pkg_unsplit_repo = https://github.com/uwiger/unsplit
+pkg_unsplit_commit = master
+
+PACKAGES += uuid
+pkg_uuid_name = uuid
+pkg_uuid_description = Erlang UUID Implementation
+pkg_uuid_homepage = https://github.com/okeuday/uuid
+pkg_uuid_fetch = git
+pkg_uuid_repo = https://github.com/okeuday/uuid
+pkg_uuid_commit = master
+
+PACKAGES += ux
+pkg_ux_name = ux
+pkg_ux_description = Unicode eXtention for Erlang (Strings, Collation)
+pkg_ux_homepage = https://github.com/erlang-unicode/ux
+pkg_ux_fetch = git
+pkg_ux_repo = https://github.com/erlang-unicode/ux
+pkg_ux_commit = master
+
+PACKAGES += vert
+pkg_vert_name = vert
+pkg_vert_description = Erlang binding to libvirt virtualization API
+pkg_vert_homepage = https://github.com/msantos/erlang-libvirt
+pkg_vert_fetch = git
+pkg_vert_repo = https://github.com/msantos/erlang-libvirt
+pkg_vert_commit = master
+
+PACKAGES += verx
+pkg_verx_name = verx
+pkg_verx_description = Erlang implementation of the libvirtd remote protocol
+pkg_verx_homepage = https://github.com/msantos/verx
+pkg_verx_fetch = git
+pkg_verx_repo = https://github.com/msantos/verx
+pkg_verx_commit = master
+
+PACKAGES += vmq_acl
+pkg_vmq_acl_name = vmq_acl
+pkg_vmq_acl_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_acl_homepage = https://verne.mq/
+pkg_vmq_acl_fetch = git
+pkg_vmq_acl_repo = https://github.com/erlio/vmq_acl
+pkg_vmq_acl_commit = master
+
+PACKAGES += vmq_bridge
+pkg_vmq_bridge_name = vmq_bridge
+pkg_vmq_bridge_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_bridge_homepage = https://verne.mq/
+pkg_vmq_bridge_fetch = git
+pkg_vmq_bridge_repo = https://github.com/erlio/vmq_bridge
+pkg_vmq_bridge_commit = master
+
+PACKAGES += vmq_graphite
+pkg_vmq_graphite_name = vmq_graphite
+pkg_vmq_graphite_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_graphite_homepage = https://verne.mq/
+pkg_vmq_graphite_fetch = git
+pkg_vmq_graphite_repo = https://github.com/erlio/vmq_graphite
+pkg_vmq_graphite_commit = master
+
+PACKAGES += vmq_passwd
+pkg_vmq_passwd_name = vmq_passwd
+pkg_vmq_passwd_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_passwd_homepage = https://verne.mq/
+pkg_vmq_passwd_fetch = git
+pkg_vmq_passwd_repo = https://github.com/erlio/vmq_passwd
+pkg_vmq_passwd_commit = master
+
+PACKAGES += vmq_server
+pkg_vmq_server_name = vmq_server
+pkg_vmq_server_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_server_homepage = https://verne.mq/
+pkg_vmq_server_fetch = git
+pkg_vmq_server_repo = https://github.com/erlio/vmq_server
+pkg_vmq_server_commit = master
+
+PACKAGES += vmq_snmp
+pkg_vmq_snmp_name = vmq_snmp
+pkg_vmq_snmp_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_snmp_homepage = https://verne.mq/
+pkg_vmq_snmp_fetch = git
+pkg_vmq_snmp_repo = https://github.com/erlio/vmq_snmp
+pkg_vmq_snmp_commit = master
+
+PACKAGES += vmq_systree
+pkg_vmq_systree_name = vmq_systree
+pkg_vmq_systree_description = Component of VerneMQ: A distributed MQTT message broker
+pkg_vmq_systree_homepage = https://verne.mq/
+pkg_vmq_systree_fetch = git
+pkg_vmq_systree_repo = https://github.com/erlio/vmq_systree
+pkg_vmq_systree_commit = master
+
+PACKAGES += vmstats
+pkg_vmstats_name = vmstats
+pkg_vmstats_description = tiny Erlang app that works in conjunction with statsderl in order to generate information on the Erlang VM for graphite logs.
+pkg_vmstats_homepage = https://github.com/ferd/vmstats
+pkg_vmstats_fetch = git
+pkg_vmstats_repo = https://github.com/ferd/vmstats
+pkg_vmstats_commit = master
+
+PACKAGES += walrus
+pkg_walrus_name = walrus
+pkg_walrus_description = Walrus - Mustache-like Templating
+pkg_walrus_homepage = https://github.com/devinus/walrus
+pkg_walrus_fetch = git
+pkg_walrus_repo = https://github.com/devinus/walrus
+pkg_walrus_commit = master
+
+PACKAGES += webmachine
+pkg_webmachine_name = webmachine
+pkg_webmachine_description = A REST-based system for building web applications.
+pkg_webmachine_homepage = https://github.com/basho/webmachine
+pkg_webmachine_fetch = git
+pkg_webmachine_repo = https://github.com/basho/webmachine
+pkg_webmachine_commit = master
+
+PACKAGES += websocket_client
+pkg_websocket_client_name = websocket_client
+pkg_websocket_client_description = Erlang websocket client (ws and wss supported)
+pkg_websocket_client_homepage = https://github.com/jeremyong/websocket_client
+pkg_websocket_client_fetch = git
+pkg_websocket_client_repo = https://github.com/jeremyong/websocket_client
+pkg_websocket_client_commit = master
+
+PACKAGES += worker_pool
+pkg_worker_pool_name = worker_pool
+pkg_worker_pool_description = a simple erlang worker pool
+pkg_worker_pool_homepage = https://github.com/inaka/worker_pool
+pkg_worker_pool_fetch = git
+pkg_worker_pool_repo = https://github.com/inaka/worker_pool
+pkg_worker_pool_commit = master
+
+PACKAGES += wrangler
+pkg_wrangler_name = wrangler
+pkg_wrangler_description = Import of the Wrangler svn repository.
+pkg_wrangler_homepage = http://www.cs.kent.ac.uk/projects/wrangler/Home.html
+pkg_wrangler_fetch = git
+pkg_wrangler_repo = https://github.com/RefactoringTools/wrangler
+pkg_wrangler_commit = master
+
+PACKAGES += wsock
+pkg_wsock_name = wsock
+pkg_wsock_description = Erlang library to build WebSocket clients and servers
+pkg_wsock_homepage = https://github.com/madtrick/wsock
+pkg_wsock_fetch = git
+pkg_wsock_repo = https://github.com/madtrick/wsock
+pkg_wsock_commit = master
+
+PACKAGES += xhttpc
+pkg_xhttpc_name = xhttpc
+pkg_xhttpc_description = Extensible HTTP Client for Erlang
+pkg_xhttpc_homepage = https://github.com/seriyps/xhttpc
+pkg_xhttpc_fetch = git
+pkg_xhttpc_repo = https://github.com/seriyps/xhttpc
+pkg_xhttpc_commit = master
+
+PACKAGES += xref_runner
+pkg_xref_runner_name = xref_runner
+pkg_xref_runner_description = Erlang Xref Runner (inspired in rebar xref)
+pkg_xref_runner_homepage = https://github.com/inaka/xref_runner
+pkg_xref_runner_fetch = git
+pkg_xref_runner_repo = https://github.com/inaka/xref_runner
+pkg_xref_runner_commit = master
+
+PACKAGES += yamerl
+pkg_yamerl_name = yamerl
+pkg_yamerl_description = YAML 1.2 parser in pure Erlang
+pkg_yamerl_homepage = https://github.com/yakaz/yamerl
+pkg_yamerl_fetch = git
+pkg_yamerl_repo = https://github.com/yakaz/yamerl
+pkg_yamerl_commit = master
+
+PACKAGES += yamler
+pkg_yamler_name = yamler
+pkg_yamler_description = libyaml-based yaml loader for Erlang
+pkg_yamler_homepage = https://github.com/goertzenator/yamler
+pkg_yamler_fetch = git
+pkg_yamler_repo = https://github.com/goertzenator/yamler
+pkg_yamler_commit = master
+
+PACKAGES += yaws
+pkg_yaws_name = yaws
+pkg_yaws_description = Yaws webserver
+pkg_yaws_homepage = http://yaws.hyber.org
+pkg_yaws_fetch = git
+pkg_yaws_repo = https://github.com/klacke/yaws
+pkg_yaws_commit = master
+
+PACKAGES += zab_engine
+pkg_zab_engine_name = zab_engine
+pkg_zab_engine_description = zab propotocol implement by erlang
+pkg_zab_engine_homepage = https://github.com/xinmingyao/zab_engine
+pkg_zab_engine_fetch = git
+pkg_zab_engine_repo = https://github.com/xinmingyao/zab_engine
+pkg_zab_engine_commit = master
+
+PACKAGES += zabbix_sender
+pkg_zabbix_sender_name = zabbix_sender
+pkg_zabbix_sender_description = Zabbix trapper for sending data to Zabbix in pure Erlang
+pkg_zabbix_sender_homepage = https://github.com/stalkermn/zabbix_sender
+pkg_zabbix_sender_fetch = git
+pkg_zabbix_sender_repo = https://github.com/stalkermn/zabbix_sender.git
+pkg_zabbix_sender_commit = master
+
+PACKAGES += zeta
+pkg_zeta_name = zeta
+pkg_zeta_description = HTTP access log parser in Erlang
+pkg_zeta_homepage = https://github.com/s1n4/zeta
+pkg_zeta_fetch = git
+pkg_zeta_repo = https://github.com/s1n4/zeta
+pkg_zeta_commit = master
+
+PACKAGES += zippers
+pkg_zippers_name = zippers
+pkg_zippers_description = A library for functional zipper data structures in Erlang. Read more on zippers
+pkg_zippers_homepage = https://github.com/ferd/zippers
+pkg_zippers_fetch = git
+pkg_zippers_repo = https://github.com/ferd/zippers
+pkg_zippers_commit = master
+
+PACKAGES += zlists
+pkg_zlists_name = zlists
+pkg_zlists_description = Erlang lazy lists library.
+pkg_zlists_homepage = https://github.com/vjache/erlang-zlists
+pkg_zlists_fetch = git
+pkg_zlists_repo = https://github.com/vjache/erlang-zlists
+pkg_zlists_commit = master
+
+PACKAGES += zraft_lib
+pkg_zraft_lib_name = zraft_lib
+pkg_zraft_lib_description = Erlang raft consensus protocol implementation
+pkg_zraft_lib_homepage = https://github.com/dreyk/zraft_lib
+pkg_zraft_lib_fetch = git
+pkg_zraft_lib_repo = https://github.com/dreyk/zraft_lib
+pkg_zraft_lib_commit = master
+
+PACKAGES += zucchini
+pkg_zucchini_name = zucchini
+pkg_zucchini_description = An Erlang INI parser
+pkg_zucchini_homepage = https://github.com/devinus/zucchini
+pkg_zucchini_fetch = git
+pkg_zucchini_repo = https://github.com/devinus/zucchini
+pkg_zucchini_commit = master
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# 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 <essen@ninenines.eu>
+# 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
+
+REBAR_GIT ?= https://github.com/rebar/rebar
+REBAR_COMMIT ?= 576e12171ab8d69b048b827b92aa65d067deea01
+
+# 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),$(if $(IS_DEP),legacy,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 dep_repo,$(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_repo_legacy = -
+
+query_version = $(call _qv,$(1),$(call query_fetch_method,$(1)))
+_qv = $(if $(query_version_$(2)),$(call query_version_$(2),$(1)),$(call dep_commit,$(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_git,$(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_version_legacy = -
+
+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_extra_legacy = -
+
+query_absolute_path = $(addprefix $(DEPS_DIR)/,$(call query_name,$(1)))
+
+# Deprecated legacy query functions.
+dep_fetch = $(call query_fetch_method,$(1))
+dep_name = $(call query_name,$(1))
+dep_repo = $(call query_repo_git,$(1))
+dep_commit = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(if $(filter hex,$(word 1,$(dep_$(1)))),$(word 2,$(dep_$(1))),$(word 3,$(dep_$(1)))),$(pkg_$(1)_commit)))
+
+LOCAL_DEPS_DIRS = $(foreach a,$(LOCAL_DEPS),$(if $(wildcard $(APPS_DIR)/$(a)),$(APPS_DIR)/$(a)))
+ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(BUILD_DEPS) $(DEPS)),$(call dep_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
+
+# Verbosity.
+
+dep_verbose_0 = @echo " DEP $1 ($(call dep_commit,$1))";
+dep_verbose_2 = set -x;
+dep_verbose = $(dep_verbose_$(V))
+
+# Optimization: don't recompile deps unless truly necessary.
+
+ifndef IS_DEP
+ifneq ($(MAKELEVEL),0)
+$(shell rm -f ebin/dep_built)
+endif
+endif
+
+# 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
+
+ifneq ($(SKIP_DEPS),)
+deps::
+else
+deps:: $(ALL_DEPS_DIRS) apps clean-tmp-deps.log | $(ERLANG_MK_TMP)
+ifneq ($(ALL_DEPS_DIRS),)
+ $(verbose) set -e; for dep in $(ALL_DEPS_DIRS); 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 $$dep/ebin/dep_built ]; then \
+ :; \
+ 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 $$dep/ebin/dep_built; fi; \
+ else \
+ echo "Error: No Makefile to build dependency $$dep." >&2; \
+ exit 2; \
+ fi \
+ fi \
+ done
+endif
+endif
+
+# Deps related targets.
+
+# @todo rename GNUmakefile and makefile into Makefile first, if they exist
+# While Makefile file could be GNUmakefile or makefile,
+# in practice only Makefile is needed so far.
+define dep_autopatch
+ if [ -f $(DEPS_DIR)/$(1)/erlang.mk ]; then \
+ rm -rf $(DEPS_DIR)/$1/ebin/; \
+ $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \
+ $(call dep_autopatch_erlang_mk,$(1)); \
+ elif [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \
+ if [ -f $(DEPS_DIR)/$1/rebar.lock ]; then \
+ $(call dep_autopatch2,$1); \
+ elif [ 0 != `grep -c "include ../\w*\.mk" $(DEPS_DIR)/$(1)/Makefile` ]; then \
+ $(call dep_autopatch2,$(1)); \
+ elif [ 0 != `grep -ci "^[^#].*rebar" $(DEPS_DIR)/$(1)/Makefile` ]; then \
+ $(call dep_autopatch2,$(1)); \
+ elif [ -n "`find $(DEPS_DIR)/$(1)/ -type f -name \*.mk -not -name erlang.mk -exec grep -i "^[^#].*rebar" '{}' \;`" ]; then \
+ $(call dep_autopatch2,$(1)); \
+ fi \
+ else \
+ if [ ! -d $(DEPS_DIR)/$(1)/src/ ]; then \
+ $(call dep_autopatch_noop,$(1)); \
+ else \
+ $(call dep_autopatch2,$(1)); \
+ fi \
+ fi
+endef
+
+define dep_autopatch2
+ ! 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_noop
+ printf "noop:\n" > $(DEPS_DIR)/$(1)/Makefile
+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
+ if command -v flock >/dev/null; then \
+ flock $(ERLANG_MK_TMP)/rebar.lock sh -c "$(call dep_autopatch_fetch_rebar2)"; \
+ elif command -v lockf >/dev/null; then \
+ lockf $(ERLANG_MK_TMP)/rebar.lock sh -c "$(call dep_autopatch_fetch_rebar2)"; \
+ else \
+ $(call dep_autopatch_fetch_rebar2); \
+ fi
+endef
+
+define dep_autopatch_fetch_rebar2
+ if [ ! -d $(ERLANG_MK_TMP)/rebar ]; then \
+ git clone -q -n -- $(REBAR_GIT) $(ERLANG_MK_TMP)/rebar; \
+ cd $(ERLANG_MK_TMP)/rebar; \
+ git checkout -q $(REBAR_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),
+ rmemo:start(),
+ 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\nexport ERLC_OPTS\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(),
+ GetHexVsn = fun(N, NP) ->
+ case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.lock)") of
+ {ok, Lock} ->
+ io:format("~p~n", [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) ->
+ io:format("~p~n", [LockPkgs]),
+ case lists:keyfind(atom_to_binary(N, latin1), 1, LockPkgs) of
+ {_, {pkg, _, Vsn}, _} ->
+ io:format("~p~n", [Vsn]),
+ {N, {hex, NP, binary_to_list(Vsn)}};
+ _ ->
+ false
+ end;
+ true ->
+ false
+ end;
+ _ ->
+ false
+ end
+ end,
+ SemVsn = fun
+ ("~>" ++ S0) ->
+ S = case S0 of
+ " " ++ S1 -> S1;
+ _ -> S0
+ end,
+ case length([ok || $$. <- S]) of
+ 0 -> S ++ ".0.0";
+ 1 -> S ++ ".0";
+ _ -> S
+ end;
+ (S) -> S
+ end,
+ fun() ->
+ File = case lists:keyfind(deps, 1, Conf) of
+ false -> [];
+ {_, Deps} ->
+ [begin case case Dep of
+ N when is_atom(N) -> GetHexVsn(N, N);
+ {N, S} when is_atom(N), is_list(S) -> {N, {hex, N, SemVsn(S)}};
+ {N, {pkg, NP}} when is_atom(N) -> GetHexVsn(N, NP);
+ {N, S, {pkg, NP}} -> {N, {hex, NP, S}};
+ {N, S} when is_tuple(S) -> {N, S};
+ {N, _, S} -> {N, S};
+ {N, _, S, _} -> {N, S};
+ _ -> false
+ end of
+ false -> ok;
+ {Name, Source} ->
+ {Method, Repo, Commit} = case Source of
+ {hex, NPV, V} -> {hex, V, NPV};
+ {git, R} -> {git, R, master};
+ {M, R, {branch, C}} -> {M, R, C};
+ {M, R, {ref, C}} -> {M, R, C};
+ {M, R, {tag, C}} -> {M, R, C};
+ {M, R, C} -> {M, R, C}
+ end,
+ Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, Commit]))
+ end end || Dep <- Deps]
+ end
+ end(),
+ fun() ->
+ case lists:keyfind(erl_first_files, 1, Conf) of
+ false -> ok;
+ {_, Files} ->
+ 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] || "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\n"),
+ Write("\npreprocess::\n"),
+ Write("\npre-deps::\n"),
+ Write("\npre-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");
+ {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(),
+ 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) $$\(EXE_LDFLAGS)",
+ case {filename:extension(Output), $(PLATFORM)} of
+ {[], _} -> "\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
+
+define dep_fetch_git
+ git clone -q -n -- $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); \
+ cd $(DEPS_DIR)/$(call dep_name,$(1)) && git checkout -q $(call dep_commit,$(1));
+endef
+
+define dep_fetch_git-subfolder
+ mkdir -p $(ERLANG_MK_TMP)/git-subfolder; \
+ git clone -q -n -- $(call dep_repo,$1) \
+ $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1); \
+ cd $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1) \
+ && git checkout -q $(call dep_commit,$1); \
+ ln -s $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1)/$(word 4,$(dep_$(1))) \
+ $(DEPS_DIR)/$(call dep_name,$1);
+endef
+
+define dep_fetch_git-submodule
+ git submodule update --init -- $(DEPS_DIR)/$1;
+endef
+
+define dep_fetch_hg
+ hg clone -q -U $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); \
+ cd $(DEPS_DIR)/$(call dep_name,$(1)) && hg update -q $(call dep_commit,$(1));
+endef
+
+define dep_fetch_svn
+ svn checkout -q $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1));
+endef
+
+define dep_fetch_cp
+ cp -R $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1));
+endef
+
+define dep_fetch_ln
+ ln -s $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1));
+endef
+
+# 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 core_http_get,$(ERLANG_MK_TMP)/hex/$1.tar,\
+ https://repo.hex.pm/tarballs/$(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1)-$(strip $(word 2,$(dep_$1))).tar); \
+ tar -xOf $(ERLANG_MK_TMP)/hex/$1.tar contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -;
+endef
+
+define dep_fetch_fail
+ echo "Error: Unknown or invalid dependency: $(1)." >&2; \
+ exit 78;
+endef
+
+# Kept for compatibility purposes with older Erlang.mk configuration.
+define dep_fetch_legacy
+ $(warning WARNING: '$(1)' dependency configuration uses deprecated format.) \
+ git clone -q -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1); \
+ cd $(DEPS_DIR)/$(1) && git checkout -q $(if $(word 2,$(dep_$(1))),$(word 2,$(dep_$(1))),master);
+endef
+
+define dep_target
+$(DEPS_DIR)/$(call dep_name,$1): | $(ERLANG_MK_TMP)
+ $(eval DEP_NAME := $(call dep_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 dep_fetch,$(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) $$(MAKE) --no-print-directory autopatch-$(DEP_NAME)
+endif
+
+.PHONY: autopatch-$(call dep_name,$1)
+
+autopatch-$(call dep_name,$1)::
+ $(verbose) if [ "$(1)" = "amqp_client" -a "$(RABBITMQ_CLIENT_PATCH)" ]; then \
+ if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \
+ echo " PATCH Downloading rabbitmq-codegen"; \
+ git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \
+ fi; \
+ if [ ! -d $(DEPS_DIR)/rabbitmq-server ]; then \
+ echo " PATCH Downloading rabbitmq-server"; \
+ git clone https://github.com/rabbitmq/rabbitmq-server.git $(DEPS_DIR)/rabbitmq-server; \
+ fi; \
+ ln -s $(DEPS_DIR)/amqp_client/deps/rabbit_common-0.0.0 $(DEPS_DIR)/rabbit_common; \
+ elif [ "$(1)" = "rabbit" -a "$(RABBITMQ_SERVER_PATCH)" ]; then \
+ if [ ! -d $(DEPS_DIR)/rabbitmq-codegen ]; then \
+ echo " PATCH Downloading rabbitmq-codegen"; \
+ git clone https://github.com/rabbitmq/rabbitmq-codegen.git $(DEPS_DIR)/rabbitmq-codegen; \
+ fi \
+ elif [ "$1" = "elixir" -a "$(ELIXIR_PATCH)" ]; then \
+ ln -s lib/elixir/ebin $(DEPS_DIR)/elixir/; \
+ else \
+ $$(call dep_autopatch,$(call dep_name,$1)) \
+ fi
+endef
+
+$(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
+
+# 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) 2013-2016, Loïc Hoguin <essen@ninenines.eu>
+# 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/),)
+
+# Targets.
+
+app:: $(if $(wildcard ebin/test),clean) deps
+ $(verbose) $(MAKE) --no-print-directory $(PROJECT).d
+ $(verbose) $(MAKE) --no-print-directory app-build
+
+ifeq ($(wildcard src/$(PROJECT_MOD).erl),)
+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, []},
+ {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(foreach dep,$(DEPS),$(call dep_name,$(dep))))]},
+ {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),)
+]}.
+endef
+else
+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, [$(call comma_list,$(PROJECT)_sup $(PROJECT_REGISTERED))]},
+ {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(foreach dep,$(DEPS),$(call dep_name,$(dep))))]},
+ {mod, {$(PROJECT_MOD), []}},
+ {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),)
+]}.
+endef
+endif
+
+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))
+
+# 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) ->
+ {ok, Filename} = file:pid2name(Fd),
+ 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) $(call core_find,include/,*.hrl) $(MAKEFILE_LIST)
+ $(makedep_verbose) $(call erlang,$(call makedep.erl,$@))
+endif
+
+ifeq ($(IS_APP)$(IS_DEP),)
+ifneq ($(words $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_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) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES); \
+ touch -c $(PROJECT).d; \
+ fi
+ $(verbose) touch $@
+
+$(ERL_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)
+ $(eval FILES_TO_COMPILE := $(filter-out src/$(PROJECT).app.src,$?))
+ $(if $(strip $(FILES_TO_COMPILE)),$(call compile_erl,$(FILES_TO_COMPILE)))
+# Older git versions do not have the --first-parent flag. Do without in that case.
+ $(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 := $(patsubst %,'%',$(sort $(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) 2016, Loïc Hoguin <essen@ninenines.eu>
+# Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
+# 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 <essen@ninenines.eu>
+# 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 <essen@ninenines.eu>
+# 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)
+ $(verbose) set -e; for dep in $(ALL_TEST_DEPS_DIRS) ; do \
+ if [ -z "$(strip $(FULL))" ] && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \
+ :; \
+ else \
+ $(MAKE) -C $$dep IS_DEP=1; \
+ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; 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)
+ $(eval FILES_TO_COMPILE := $(if $(filter $(MAKEFILE_LIST),$?),$(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 ebin/test),,clean)) $(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 ebin/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 ebin/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 <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: rebar.config
+
+# 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 dep_fetch,$d)),\
+ {$(call dep_name,$d)$(comma)"$(call dep_repo,$d)"},\
+ {$(call dep_name,$d)$(comma)".*"$(comma){git,"$(call dep_repo,$d)"$(comma)"$(call dep_commit,$d)"}})))
+]}.
+{erl_opts, $(call compat_erlc_opts_to_list,$(ERLC_OPTS))}.
+endef
+
+rebar.config:
+ $(gen_verbose) $(call core_render,compat_rebar_config,rebar.config)
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# 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) | sed 's/^./\U&\E/')
+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 ->
+ io:format("Exception ~p:~p~nStacktrace: ~p~n", [C, E, erlang:get_stacktrace()]),
+ 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 <essen@ninenines.eu>
+# 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"
+
+# Bootstrap templates.
+
+define bs_appsrc
+{application, $p, [
+ {description, ""},
+ {vsn, "0.1.0"},
+ {id, "git"},
+ {modules, []},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, {$p_app, []}},
+ {env, []}
+]}.
+endef
+
+define bs_appsrc_lib
+{application, $p, [
+ {description, ""},
+ {vsn, "0.1.0"},
+ {id, "git"},
+ {modules, []},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]}
+]}.
+endef
+
+# To prevent autocompletion issues with ZSH, we add "include erlang.mk"
+# separately during the actual bootstrap.
+define bs_Makefile
+PROJECT = $p
+PROJECT_DESCRIPTION = New project
+PROJECT_VERSION = 0.1.0
+$(if $(SP),
+# Whitespace to be used when creating files from templates.
+SP = $(SP)
+)
+endef
+
+define bs_apps_Makefile
+PROJECT = $p
+PROJECT_DESCRIPTION = New project
+PROJECT_VERSION = 0.1.0
+$(if $(SP),
+# Whitespace to be used when creating files from templates.
+SP = $(SP)
+)
+# Make sure we know where the applications are located.
+ROOT_DIR ?= $(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app)
+APPS_DIR ?= ..
+DEPS_DIR ?= $(call core_relpath,$(DEPS_DIR),$(APPS_DIR)/app)
+
+include $$(ROOT_DIR)/erlang.mk
+endef
+
+define bs_app
+-module($p_app).
+-behaviour(application).
+
+-export([start/2]).
+-export([stop/1]).
+
+start(_Type, _Args) ->
+ $p_sup:start_link().
+
+stop(_State) ->
+ ok.
+endef
+
+define bs_relx_config
+{release, {$p_release, "1"}, [$p, 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 bs_sys_config
+[
+].
+endef
+
+define bs_vm_args
+-name $p@127.0.0.1
+-setcookie $p
+-heart
+endef
+
+# Normal templates.
+
+define tpl_supervisor
+-module($(n)).
+-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_gen_server
+-module($(n)).
+-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_module
+-module($(n)).
+-export([]).
+endef
+
+define tpl_cowboy_http
+-module($(n)).
+-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_gen_fsm
+-module($(n)).
+-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_statem
+-module($(n)).
+-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_cowboy_loop
+-module($(n)).
+-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
+-module($(n)).
+
+-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) ->
+ {<<"<html><body>This is REST!</body></html>">>, Req, State}.
+endef
+
+define tpl_cowboy_ws
+-module($(n)).
+-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_ranch_protocol
+-module($(n)).
+-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
+
+# 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
+
+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,bs_Makefile,Makefile)
+ $(verbose) echo "include erlang.mk" >> Makefile
+ $(verbose) mkdir src/
+ifdef LEGACY
+ $(verbose) $(call core_render,bs_appsrc,src/$(PROJECT).app.src)
+endif
+ $(verbose) $(call core_render,bs_app,src/$(PROJECT)_app.erl)
+ $(verbose) $(call core_render,tpl_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,bs_Makefile,Makefile)
+ $(verbose) echo "include erlang.mk" >> Makefile
+ $(verbose) mkdir src/
+ifdef LEGACY
+ $(verbose) $(call core_render,bs_appsrc_lib,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,bs_relx_config,relx.config)
+ $(verbose) mkdir config/
+ $(verbose) $(call core_render,bs_sys_config,config/sys.config)
+ $(verbose) $(call core_render,bs_vm_args,config/vm.args)
+ $(verbose) awk '/^include erlang.mk/ && !ins {print "BUILD_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,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile)
+ifdef LEGACY
+ $(verbose) $(call core_render,bs_appsrc,$(APPS_DIR)/$p/src/$p.app.src)
+endif
+ $(verbose) $(call core_render,bs_app,$(APPS_DIR)/$p/src/$p_app.erl)
+ $(verbose) $(call core_render,tpl_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,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile)
+ifdef LEGACY
+ $(verbose) $(call core_render,bs_appsrc_lib,$(APPS_DIR)/$p/src/$p.app.src)
+endif
+
+new:
+ifeq ($(wildcard src/)$(in),)
+ $(error Error: src/ directory does not exist)
+endif
+ifndef t
+ $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP])
+endif
+ifndef n
+ $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP])
+endif
+ifdef in
+ $(verbose) $(call core_render,tpl_$(t),$(APPS_DIR)/$(in)/src/$(n).erl)
+else
+ $(verbose) $(call core_render,tpl_$(t),src/$(n).erl)
+endif
+
+list-templates:
+ $(verbose) @echo Available templates:
+ $(verbose) printf " %s\n" $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES))))
+
+# Copyright (c) 2014-2016, Loïc Hoguin <essen@ninenines.eu>
+# 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
+else
+ C_SRC_OUTPUT_EXECUTABLE_EXTENSION ?=
+ C_SRC_OUTPUT_SHARED_EXTENSION ?= .so
+endif
+
+ifeq ($(C_SRC_TYPE),shared)
+ C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_SHARED_EXTENSION)
+else
+ C_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_EXECUTABLE_EXTENSION)
+endif
+
+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
+
+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))
+
+# 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)
+
+$(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)
+
+$(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 <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: ci ci-prepare ci-setup
+
+CI_OTP ?=
+CI_HIPE ?=
+CI_ERLLVM ?=
+
+ifeq ($(CI_VM),native)
+ERLC_OPTS += +native
+TEST_ERLC_OPTS += +native
+else ifeq ($(CI_VM),erllvm)
+ERLC_OPTS += +native +'{hipe, [to_llvm]}'
+TEST_ERLC_OPTS += +native +'{hipe, [to_llvm]}'
+endif
+
+ifeq ($(strip $(CI_OTP) $(CI_HIPE) $(CI_ERLLVM)),)
+ci::
+else
+
+ci:: $(addprefix ci-,$(CI_OTP) $(addsuffix -native,$(CI_HIPE)) $(addsuffix -erllvm,$(CI_ERLLVM)))
+
+ci-prepare: $(addprefix $(KERL_INSTALL_DIR)/,$(CI_OTP) $(addsuffix -native,$(CI_HIPE)))
+
+ci-setup::
+ $(verbose) :
+
+ci-extra::
+ $(verbose) :
+
+ci_verbose_0 = @echo " CI " $(1);
+ci_verbose = $(ci_verbose_$(V))
+
+define ci_target
+ci-$1: $(KERL_INSTALL_DIR)/$2
+ $(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,$(CI_HIPE),$(eval $(call ci_target,$(otp)-native,$(otp)-native,native)))
+$(foreach otp,$(CI_ERLLVM),$(eval $(call ci_target,$(otp)-erllvm,$(otp)-native,erllvm)))
+
+$(foreach otp,$(filter-out $(ERLANG_OTP),$(CI_OTP)),$(eval $(call kerl_otp_target,$(otp))))
+$(foreach otp,$(filter-out $(ERLANG_HIPE),$(sort $(CI_HIPE) $(CI_ERLLLVM))),$(eval $(call kerl_hipe_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 <essen@ninenines.eu>
+# 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
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta charset="utf-8">
+<title>Concuerror HTML report</title>
+</head>
+<body>
+<h1>Concuerror HTML report</h1>
+<p>Generated on $(concuerror_date)</p>
+<ul>
+$(foreach t,$(concuerror_targets),<li><a href="$(t).txt">$(t)</a></li>)
+</ul>
+</body>
+</html>
+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 <essen@ninenines.eu>
+# 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 <essen@ninenines.eu>
+# 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 --no_native `$(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 <essen@ninenines.eu>
+# 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}") ++ 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 <essen@ninenines.eu>
+# 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 <essen@ninenines.eu>
+# Copyright (c) 2014, Dave Cottlehuber <dch@skunkwerks.at>
+# 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.
+
+escript-zip:: FULL=1
+escript-zip:: deps app
+ $(verbose) mkdir -p $(dir $(ESCRIPT_ZIP))
+ $(verbose) rm -f $(ESCRIPT_ZIP_FILE)
+ $(gen_verbose) cd .. && $(ESCRIPT_ZIP) $(ESCRIPT_ZIP_FILE) $(PROJECT)/ebin/*
+ifneq ($(DEPS),)
+ $(verbose) cd $(DEPS_DIR) && $(ESCRIPT_ZIP) $(ESCRIPT_ZIP_FILE) \
+ $(subst $(DEPS_DIR)/,,$(addsuffix /*,$(wildcard \
+ $(addsuffix /ebin,$(shell cat $(ERLANG_MK_TMP)/deps.log)))))
+endif
+
+escript:: escript-zip
+ $(gen_verbose) printf "%s\n" \
+ "#!$(ESCRIPT_SHEBANG)" \
+ "%% $(ESCRIPT_COMMENT)" \
+ "%%! $(ESCRIPT_EMU_ARGS)" > $(ESCRIPT_FILE)
+ $(verbose) cat $(ESCRIPT_ZIP_FILE) >> $(ESCRIPT_FILE)
+ $(verbose) chmod +x $(ESCRIPT_FILE)
+
+distclean-escript:
+ $(gen_verbose) rm -f $(ESCRIPT_FILE)
+
+# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>
+# Copyright (c) 2014, Enrique Fernandez <enrique.fernandez@erlang-solutions.com>
+# This file is contributed to erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: eunit apps-eunit
+
+# Configuration
+
+EUNIT_OPTS ?=
+EUNIT_ERL_OPTS ?=
+
+# 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($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
+
+# Copyright (c) 2020, Loïc Hoguin <essen@ninenines.eu>
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+HEX_CORE_GIT ?= https://github.com/hexpm/hex_core
+HEX_CORE_COMMIT ?= v0.7.0
+
+PACKAGES += hex_core
+pkg_hex_core_name = hex_core
+pkg_hex_core_description = Reference implementation of Hex specifications
+pkg_hex_core_homepage = $(HEX_CORE_GIT)
+pkg_hex_core_fetch = git
+pkg_hex_core_repo = $(HEX_CORE_GIT)
+pkg_hex_core_commit = $(HEX_CORE_COMMIT)
+
+# 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)))
+
+hex-core: $(DEPS_DIR)/hex_core
+ $(verbose) if [ ! -e $(DEPS_DIR)/hex_core/ebin/dep_built ]; then \
+ $(MAKE) -C $(DEPS_DIR)/hex_core IS_DEP=1; \
+ touch $(DEPS_DIR)/hex_core/ebin/dep_built; \
+ fi
+
+# @todo This must also apply to fetching.
+HEX_CONFIG ?=
+
+define hex_config.erl
+ begin
+ Config0 = hex_core:default_config(),
+ Config0$(HEX_CONFIG)
+ end
+endef
+
+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: 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: 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 $(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">> => <<"$(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): 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: hex-core $(HEX_TARBALL_OUTPUT_FILE)
+ $(verbose) $(call erlang,$(call hex_release_publish_summary.erl))
+
+hex-release-publish: 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: 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: 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: 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: 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): 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: 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: 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 <essen@ninenines.eu>
+# 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 ->
+ io:format("Undefined property or module?~n~p~n", [erlang:get_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 <essen@ninenines.eu>
+# 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, [
+ {include_as_lib, true},
+ {module_name_suffix, "_pb"},
+ {o_hrl, "./include"},
+ {o_erl, "./src"}])
+ 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 <essen@ninenines.eu>
+# 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_OUTPUT_DIR ?= _rel
+RELX_REL_EXT ?=
+RELX_TAR ?= 1
+
+ifdef SFX
+ RELX_TAR = 1
+endif
+
+# Core targets.
+
+ifeq ($(IS_DEP),)
+ifneq ($(wildcard $(RELX_CONFIG)),)
+rel:: relx-rel
+
+relup:: relx-relup
+endif
+endif
+
+distclean:: distclean-relx-rel
+
+# Plugin-specific targets.
+
+define relx_release.erl
+ {ok, Config} = file:consult("$(call core_native_path,$(RELX_CONFIG))"),
+ {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),
+ Vsn = case Vsn0 of
+ {cmd, Cmd} -> os:cmd(Cmd);
+ semver -> "";
+ {semver, _} -> "";
+ VsnStr -> Vsn0
+ end,
+ {ok, _} = relx:build_release(#{name => Name, vsn => Vsn}, Config),
+ halt(0).
+endef
+
+define relx_tar.erl
+ {ok, Config} = file:consult("$(call core_native_path,$(RELX_CONFIG))"),
+ {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),
+ Vsn = case Vsn0 of
+ {cmd, Cmd} -> os:cmd(Cmd);
+ semver -> "";
+ {semver, _} -> "";
+ VsnStr -> Vsn0
+ end,
+ {ok, _} = relx:build_tar(#{name => Name, vsn => Vsn}, Config),
+ halt(0).
+endef
+
+define relx_relup.erl
+ {ok, Config} = file:consult("$(call core_native_path,$(RELX_CONFIG))"),
+ {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),
+ Vsn = case Vsn0 of
+ {cmd, Cmd} -> os:cmd(Cmd);
+ semver -> "";
+ {semver, _} -> "";
+ 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
+ifeq ($(RELX_TAR),1)
+ $(call erlang,$(call relx_tar.erl),-pa ebin/)
+endif
+
+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/)
+ifeq ($(RELX_TAR),1)
+ $(call erlang,$(call relx_tar.erl),-pa ebin/)
+endif
+
+distclean-relx-rel:
+ $(gen_verbose) rm -rf $(RELX_OUTPUT_DIR)
+
+# Default hooks.
+relx-post-rel::
+ $(verbose) :
+
+# Run target.
+
+ifeq ($(wildcard $(RELX_CONFIG)),)
+run::
+else
+
+define get_relx_release.erl
+ {ok, Config} = file:consult("$(call core_native_path,$(RELX_CONFIG))"),
+ {release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),
+ Vsn = case Vsn0 of
+ {cmd, Cmd} -> os:cmd(Cmd);
+ semver -> "";
+ {semver, _} -> "";
+ 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:: 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 <essen@ninenines.eu>
+# Copyright (c) 2014, M Robert Martin <rob@version2beta.com>
+# 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)
+ $(verbose) set -e; for dep in $(ALL_SHELL_DEPS_DIRS) ; do \
+ if [ -z "$(strip $(FULL))" ] && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \
+ :; \
+ else \
+ $(MAKE) -C $$dep IS_DEP=1; \
+ if [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \
+ fi \
+ done
+endif
+
+shell:: build-shell-deps
+ $(gen_verbose) $(SHELL_ERL) -pa $(SHELL_PATHS) $(SHELL_OPTS)
+
+# Copyright 2017, Stanislaw Klekot <dozzie@jarowit.net>
+# 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 <jean-sebastien@rabbitmq.com>
+# 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 <essen@ninenines.eu>
+# 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 ->
+ io:format("Undefined property or module?~n~p~n", [erlang:get_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 <essen@ninenines.eu>
+# 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 <essen@ninenines.eu>
+# Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>
+# 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 ?=
+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)"}.' > $@
+
+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)")],
+ [begin
+ case filelib:is_dir(Dir) of
+ false -> false;
+ true ->
+ case cover:compile_beam_directory(Dir) of
+ {error, _} -> halt(1);
+ _ -> true
+ end
+ 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, "<!DOCTYPE html><html>~n"
+ "<head><meta charset=\"UTF-8\">~n"
+ "<title>Coverage report</title></head>~n"
+ "<body>~n", []),
+ io:format(F, "<h1>Coverage</h1>~n<p>Total: ~p%</p>~n", [TotalPerc]),
+ io:format(F, "<table><tr><th>Module</th><th>Coverage</th></tr>~n", []),
+ [io:format(F, "<tr><td><a href=\"~p.COVER.html\">~p</a></td>"
+ "<td>~p%</td></tr>~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, "</table>~n"
+ "<p>Generated using ~s and erlang.mk on ~s.</p>~n"
+ "</body></html>", [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 <essen@ninenines.eu>
+# 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 <essen@ninenines.eu>
+# 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 <essen@ninenines.eu>
+# Copyright (c) 2015-2016, Jean-Sébastien Pédron <jean-sebastien@rabbitmq.com>
+# 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) cmp -s $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@ \
+ || mv $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@
+ $(verbose) rm -f $(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)))
diff --git a/server/_build/default/lib/cowboy/hex_metadata.config b/server/_build/default/lib/cowboy/hex_metadata.config
new file mode 100644
index 0000000..4653299
--- /dev/null
+++ b/server/_build/default/lib/cowboy/hex_metadata.config
@@ -0,0 +1,36 @@
+{<<"app">>,<<"cowboy">>}.
+{<<"build_tools">>,[<<"make">>,<<"rebar3">>]}.
+{<<"description">>,<<"Small, fast, modern HTTP server.">>}.
+{<<"files">>,
+ [<<"ebin/cowboy.app">>,<<"erlang.mk">>,<<"LICENSE">>,<<"Makefile">>,
+ <<"plugins.mk">>,<<"README.asciidoc">>,<<"rebar.config">>,
+ <<"src/cowboy.erl">>,<<"src/cowboy_app.erl">>,<<"src/cowboy_bstr.erl">>,
+ <<"src/cowboy_children.erl">>,<<"src/cowboy_clear.erl">>,
+ <<"src/cowboy_clock.erl">>,<<"src/cowboy_compress_h.erl">>,
+ <<"src/cowboy_constraints.erl">>,<<"src/cowboy_handler.erl">>,
+ <<"src/cowboy_http.erl">>,<<"src/cowboy_http2.erl">>,
+ <<"src/cowboy_loop.erl">>,<<"src/cowboy_metrics_h.erl">>,
+ <<"src/cowboy_middleware.erl">>,<<"src/cowboy_req.erl">>,
+ <<"src/cowboy_rest.erl">>,<<"src/cowboy_router.erl">>,
+ <<"src/cowboy_static.erl">>,<<"src/cowboy_stream.erl">>,
+ <<"src/cowboy_stream_h.erl">>,<<"src/cowboy_sub_protocol.erl">>,
+ <<"src/cowboy_sup.erl">>,<<"src/cowboy_tls.erl">>,
+ <<"src/cowboy_tracer_h.erl">>,<<"src/cowboy_websocket.erl">>]}.
+{<<"licenses">>,[<<"ISC">>]}.
+{<<"links">>,
+ [{<<"Function reference">>,
+ <<"https://ninenines.eu/docs/en/cowboy/2.10/manual/">>},
+ {<<"GitHub">>,<<"https://github.com/ninenines/cowboy">>},
+ {<<"Sponsor">>,<<"https://github.com/sponsors/essen">>},
+ {<<"User guide">>,<<"https://ninenines.eu/docs/en/cowboy/2.10/guide/">>}]}.
+{<<"name">>,<<"cowboy">>}.
+{<<"requirements">>,
+ [{<<"cowlib">>,
+ [{<<"app">>,<<"cowlib">>},
+ {<<"optional">>,false},
+ {<<"requirement">>,<<"2.12.1">>}]},
+ {<<"ranch">>,
+ [{<<"app">>,<<"ranch">>},
+ {<<"optional">>,false},
+ {<<"requirement">>,<<"1.8.0">>}]}]}.
+{<<"version">>,<<"2.10.0">>}.
diff --git a/server/_build/default/lib/cowboy/plugins.mk b/server/_build/default/lib/cowboy/plugins.mk
new file mode 100644
index 0000000..3fb2f7e
--- /dev/null
+++ b/server/_build/default/lib/cowboy/plugins.mk
@@ -0,0 +1,75 @@
+# See LICENSE for licensing information.
+
+# Plain HTTP handlers.
+define tpl_cowboy.http
+-module($(n)).
+-behavior(cowboy_handler).
+
+-export([init/2]).
+
+init(Req, State) ->
+ {ok, Req, State}.
+endef
+
+# Loop handlers.
+define tpl_cowboy.loop
+-module($(n)).
+-behavior(cowboy_loop).
+
+-export([init/2]).
+-export([info/3]).
+
+init(Req, State) ->
+ {cowboy_loop, Req, State, hibernate}.
+
+info(_Info, Req, State) ->
+ {ok, Req, State, hibernate}.
+endef
+
+# REST handlers.
+define tpl_cowboy.rest
+-module($(n)).
+-behavior(cowboy_rest).
+
+-export([init/2]).
+-export([content_types_provided/2]).
+-export([to_html/2]).
+
+init(Req, State) ->
+ {cowboy_rest, Req, State}.
+
+content_types_provided(Req, State) ->
+ {[
+ {{<<"text">>, <<"html">>, '*'}, to_html}
+ ], Req, State}.
+
+to_html(Req, State) ->
+ {<<"<html><body>This is REST!</body></html>">>, Req, State}.
+endef
+
+# Websocket handlers.
+define tpl_cowboy.ws
+-module($(n)).
+-behavior(cowboy_websocket).
+
+-export([init/2]).
+-export([websocket_init/1]).
+-export([websocket_handle/2]).
+-export([websocket_info/2]).
+
+init(Req, State) ->
+ {cowboy_websocket, Req, State}.
+
+websocket_init(State) ->
+ {[], State}.
+
+websocket_handle({text, Data}, State) ->
+ {[{text, Data}], State};
+websocket_handle({binary, Data}, State) ->
+ {[{binary, Data}], State};
+websocket_handle(_Frame, State) ->
+ {[], State}.
+
+websocket_info(_Info, State) ->
+ {[], State}.
+endef
diff --git a/server/_build/default/lib/cowboy/rebar.config b/server/_build/default/lib/cowboy/rebar.config
new file mode 100644
index 0000000..08bb1ec
--- /dev/null
+++ b/server/_build/default/lib/cowboy/rebar.config
@@ -0,0 +1,4 @@
+{deps, [
+{cowlib,".*",{git,"https://github.com/ninenines/cowlib","2.12.1"}},{ranch,".*",{git,"https://github.com/ninenines/ranch","1.8.0"}}
+]}.
+{erl_opts, [debug_info,warn_export_vars,warn_shadow_vars,warn_obsolete_guard,warn_missing_spec,warn_untyped_record]}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy.erl b/server/_build/default/lib/cowboy/src/cowboy.erl
new file mode 100644
index 0000000..c4be25b
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy.erl
@@ -0,0 +1,105 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy).
+
+-export([start_clear/3]).
+-export([start_tls/3]).
+-export([stop_listener/1]).
+-export([set_env/3]).
+
+%% Internal.
+-export([log/2]).
+-export([log/4]).
+
+-type opts() :: cowboy_http:opts() | cowboy_http2:opts().
+-export_type([opts/0]).
+
+-type fields() :: [atom()
+ | {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()]}
+ | {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()], any()}].
+-export_type([fields/0]).
+
+-type http_headers() :: #{binary() => iodata()}.
+-export_type([http_headers/0]).
+
+-type http_status() :: non_neg_integer() | binary().
+-export_type([http_status/0]).
+
+-type http_version() :: 'HTTP/2' | 'HTTP/1.1' | 'HTTP/1.0'.
+-export_type([http_version/0]).
+
+-spec start_clear(ranch:ref(), ranch:opts(), opts())
+ -> {ok, pid()} | {error, any()}.
+start_clear(Ref, TransOpts0, ProtoOpts0) ->
+ TransOpts1 = ranch:normalize_opts(TransOpts0),
+ {TransOpts, ConnectionType} = ensure_connection_type(TransOpts1),
+ ProtoOpts = ProtoOpts0#{connection_type => ConnectionType},
+ ranch:start_listener(Ref, ranch_tcp, TransOpts, cowboy_clear, ProtoOpts).
+
+-spec start_tls(ranch:ref(), ranch:opts(), opts())
+ -> {ok, pid()} | {error, any()}.
+start_tls(Ref, TransOpts0, ProtoOpts0) ->
+ TransOpts1 = ranch:normalize_opts(TransOpts0),
+ SocketOpts = maps:get(socket_opts, TransOpts1, []),
+ TransOpts2 = TransOpts1#{socket_opts => [
+ {next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]},
+ {alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]}
+ |SocketOpts]},
+ {TransOpts, ConnectionType} = ensure_connection_type(TransOpts2),
+ ProtoOpts = ProtoOpts0#{connection_type => ConnectionType},
+ ranch:start_listener(Ref, ranch_ssl, TransOpts, cowboy_tls, ProtoOpts).
+
+ensure_connection_type(TransOpts=#{connection_type := ConnectionType}) ->
+ {TransOpts, ConnectionType};
+ensure_connection_type(TransOpts) ->
+ {TransOpts#{connection_type => supervisor}, supervisor}.
+
+-spec stop_listener(ranch:ref()) -> ok | {error, not_found}.
+stop_listener(Ref) ->
+ ranch:stop_listener(Ref).
+
+-spec set_env(ranch:ref(), atom(), any()) -> ok.
+set_env(Ref, Name, Value) ->
+ Opts = ranch:get_protocol_options(Ref),
+ Env = maps:get(env, Opts, #{}),
+ Opts2 = maps:put(env, maps:put(Name, Value, Env), Opts),
+ ok = ranch:set_protocol_options(Ref, Opts2).
+
+%% Internal.
+
+-spec log({log, logger:level(), io:format(), list()}, opts()) -> ok.
+log({log, Level, Format, Args}, Opts) ->
+ log(Level, Format, Args, Opts).
+
+-spec log(logger:level(), io:format(), list(), opts()) -> ok.
+log(Level, Format, Args, #{logger := Logger})
+ when Logger =/= error_logger ->
+ _ = Logger:Level(Format, Args),
+ ok;
+%% We use error_logger by default. Because error_logger does
+%% not have all the levels we accept we have to do some
+%% mapping to error_logger functions.
+log(Level, Format, Args, _) ->
+ Function = case Level of
+ emergency -> error_msg;
+ alert -> error_msg;
+ critical -> error_msg;
+ error -> error_msg;
+ warning -> warning_msg;
+ notice -> warning_msg;
+ info -> info_msg;
+ debug -> info_msg
+ end,
+ error_logger:Function(Format, Args).
diff --git a/server/_build/default/lib/cowboy/src/cowboy_app.erl b/server/_build/default/lib/cowboy/src/cowboy_app.erl
new file mode 100644
index 0000000..74cba41
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_app.erl
@@ -0,0 +1,27 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_app).
+-behaviour(application).
+
+-export([start/2]).
+-export([stop/1]).
+
+-spec start(_, _) -> {ok, pid()}.
+start(_, _) ->
+ cowboy_sup:start_link().
+
+-spec stop(_) -> ok.
+stop(_) ->
+ ok.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_bstr.erl b/server/_build/default/lib/cowboy/src/cowboy_bstr.erl
new file mode 100644
index 0000000..d8041e4
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_bstr.erl
@@ -0,0 +1,123 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_bstr).
+
+%% Binary strings.
+-export([capitalize_token/1]).
+-export([to_lower/1]).
+-export([to_upper/1]).
+
+%% Characters.
+-export([char_to_lower/1]).
+-export([char_to_upper/1]).
+
+%% The first letter and all letters after a dash are capitalized.
+%% This is the form seen for header names in the HTTP/1.1 RFC and
+%% others. Note that using this form isn't required, as header names
+%% are case insensitive, and it is only provided for use with eventual
+%% badly implemented clients.
+-spec capitalize_token(B) -> B when B::binary().
+capitalize_token(B) ->
+ capitalize_token(B, true, <<>>).
+capitalize_token(<<>>, _, Acc) ->
+ Acc;
+capitalize_token(<< $-, Rest/bits >>, _, Acc) ->
+ capitalize_token(Rest, true, << Acc/binary, $- >>);
+capitalize_token(<< C, Rest/bits >>, true, Acc) ->
+ capitalize_token(Rest, false, << Acc/binary, (char_to_upper(C)) >>);
+capitalize_token(<< C, Rest/bits >>, false, Acc) ->
+ capitalize_token(Rest, false, << Acc/binary, (char_to_lower(C)) >>).
+
+-spec to_lower(B) -> B when B::binary().
+to_lower(B) ->
+ << << (char_to_lower(C)) >> || << C >> <= B >>.
+
+-spec to_upper(B) -> B when B::binary().
+to_upper(B) ->
+ << << (char_to_upper(C)) >> || << C >> <= B >>.
+
+-spec char_to_lower(char()) -> char().
+char_to_lower($A) -> $a;
+char_to_lower($B) -> $b;
+char_to_lower($C) -> $c;
+char_to_lower($D) -> $d;
+char_to_lower($E) -> $e;
+char_to_lower($F) -> $f;
+char_to_lower($G) -> $g;
+char_to_lower($H) -> $h;
+char_to_lower($I) -> $i;
+char_to_lower($J) -> $j;
+char_to_lower($K) -> $k;
+char_to_lower($L) -> $l;
+char_to_lower($M) -> $m;
+char_to_lower($N) -> $n;
+char_to_lower($O) -> $o;
+char_to_lower($P) -> $p;
+char_to_lower($Q) -> $q;
+char_to_lower($R) -> $r;
+char_to_lower($S) -> $s;
+char_to_lower($T) -> $t;
+char_to_lower($U) -> $u;
+char_to_lower($V) -> $v;
+char_to_lower($W) -> $w;
+char_to_lower($X) -> $x;
+char_to_lower($Y) -> $y;
+char_to_lower($Z) -> $z;
+char_to_lower(Ch) -> Ch.
+
+-spec char_to_upper(char()) -> char().
+char_to_upper($a) -> $A;
+char_to_upper($b) -> $B;
+char_to_upper($c) -> $C;
+char_to_upper($d) -> $D;
+char_to_upper($e) -> $E;
+char_to_upper($f) -> $F;
+char_to_upper($g) -> $G;
+char_to_upper($h) -> $H;
+char_to_upper($i) -> $I;
+char_to_upper($j) -> $J;
+char_to_upper($k) -> $K;
+char_to_upper($l) -> $L;
+char_to_upper($m) -> $M;
+char_to_upper($n) -> $N;
+char_to_upper($o) -> $O;
+char_to_upper($p) -> $P;
+char_to_upper($q) -> $Q;
+char_to_upper($r) -> $R;
+char_to_upper($s) -> $S;
+char_to_upper($t) -> $T;
+char_to_upper($u) -> $U;
+char_to_upper($v) -> $V;
+char_to_upper($w) -> $W;
+char_to_upper($x) -> $X;
+char_to_upper($y) -> $Y;
+char_to_upper($z) -> $Z;
+char_to_upper(Ch) -> Ch.
+
+%% Tests.
+
+-ifdef(TEST).
+capitalize_token_test_() ->
+ Tests = [
+ {<<"heLLo-woRld">>, <<"Hello-World">>},
+ {<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>},
+ {<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>},
+ {<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>},
+ {<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>},
+ {<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--Version">>},
+ {<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>}
+ ],
+ [{H, fun() -> R = capitalize_token(H) end} || {H, R} <- Tests].
+-endif.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_children.erl b/server/_build/default/lib/cowboy/src/cowboy_children.erl
new file mode 100644
index 0000000..05d39fb
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_children.erl
@@ -0,0 +1,192 @@
+%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_children).
+
+-export([init/0]).
+-export([up/4]).
+-export([down/2]).
+-export([shutdown/2]).
+-export([shutdown_timeout/3]).
+-export([terminate/1]).
+-export([handle_supervisor_call/4]).
+
+-record(child, {
+ pid :: pid(),
+ streamid :: cowboy_stream:streamid() | undefined,
+ shutdown :: timeout(),
+ timer = undefined :: undefined | reference()
+}).
+
+-type children() :: [#child{}].
+-export_type([children/0]).
+
+-spec init() -> [].
+init() ->
+ [].
+
+-spec up(Children, pid(), cowboy_stream:streamid(), timeout())
+ -> Children when Children::children().
+up(Children, Pid, StreamID, Shutdown) ->
+ [#child{
+ pid=Pid,
+ streamid=StreamID,
+ shutdown=Shutdown
+ }|Children].
+
+-spec down(Children, pid())
+ -> {ok, cowboy_stream:streamid() | undefined, Children} | error
+ when Children::children().
+down(Children0, Pid) ->
+ case lists:keytake(Pid, #child.pid, Children0) of
+ {value, #child{streamid=StreamID, timer=Ref}, Children} ->
+ _ = case Ref of
+ undefined -> ok;
+ _ -> erlang:cancel_timer(Ref, [{async, true}, {info, false}])
+ end,
+ {ok, StreamID, Children};
+ false ->
+ error
+ end.
+
+%% We ask the processes to shutdown first. This gives
+%% a chance to processes that are trapping exits to
+%% shut down gracefully. Others will exit immediately.
+%%
+%% @todo We currently fire one timer per process being
+%% shut down. This is probably not the most efficient.
+%% A more efficient solution could be to maintain a
+%% single timer and decrease the shutdown time of all
+%% processes when it fires. This is however much more
+%% complex, and there aren't that many processes that
+%% will need to be shutdown through this function, so
+%% this is left for later.
+-spec shutdown(Children, cowboy_stream:streamid())
+ -> Children when Children::children().
+shutdown(Children0, StreamID) ->
+ [
+ case Child of
+ #child{pid=Pid, streamid=StreamID, shutdown=Shutdown} ->
+ exit(Pid, shutdown),
+ Ref = erlang:start_timer(Shutdown, self(), {shutdown, Pid}),
+ Child#child{streamid=undefined, timer=Ref};
+ _ ->
+ Child
+ end
+ || Child <- Children0].
+
+-spec shutdown_timeout(children(), reference(), pid()) -> ok.
+shutdown_timeout(Children, Ref, Pid) ->
+ case lists:keyfind(Pid, #child.pid, Children) of
+ #child{timer=Ref} ->
+ exit(Pid, kill),
+ ok;
+ _ ->
+ ok
+ end.
+
+-spec terminate(children()) -> ok.
+terminate(Children) ->
+ %% For each child, either ask for it to shut down,
+ %% or cancel its shutdown timer if it already is.
+ %%
+ %% We do not need to flush stray timeout messages out because
+ %% we are either terminating or switching protocols,
+ %% and in the latter case we flush all messages.
+ _ = [case TRef of
+ undefined -> exit(Pid, shutdown);
+ _ -> erlang:cancel_timer(TRef, [{async, true}, {info, false}])
+ end || #child{pid=Pid, timer=TRef} <- Children],
+ before_terminate_loop(Children).
+
+before_terminate_loop([]) ->
+ ok;
+before_terminate_loop(Children) ->
+ %% Find the longest shutdown time.
+ Time = longest_shutdown_time(Children, 0),
+ %% We delay the creation of the timer if one of the
+ %% processes has an infinity shutdown value.
+ TRef = case Time of
+ infinity -> undefined;
+ _ -> erlang:start_timer(Time, self(), terminate)
+ end,
+ %% Loop until that time or until all children are dead.
+ terminate_loop(Children, TRef).
+
+terminate_loop([], TRef) ->
+ %% Don't forget to cancel the timer, if any!
+ case TRef of
+ undefined ->
+ ok;
+ _ ->
+ _ = erlang:cancel_timer(TRef, [{async, true}, {info, false}]),
+ ok
+ end;
+terminate_loop(Children, TRef) ->
+ receive
+ {'EXIT', Pid, _} when TRef =:= undefined ->
+ {value, #child{shutdown=Shutdown}, Children1}
+ = lists:keytake(Pid, #child.pid, Children),
+ %% We delayed the creation of the timer. If a process with
+ %% infinity shutdown just ended, we might have to start that timer.
+ case Shutdown of
+ infinity -> before_terminate_loop(Children1);
+ _ -> terminate_loop(Children1, TRef)
+ end;
+ {'EXIT', Pid, _} ->
+ terminate_loop(lists:keydelete(Pid, #child.pid, Children), TRef);
+ {timeout, TRef, terminate} ->
+ %% Brutally kill any remaining children.
+ _ = [exit(Pid, kill) || #child{pid=Pid} <- Children],
+ ok
+ end.
+
+longest_shutdown_time([], Time) ->
+ Time;
+longest_shutdown_time([#child{shutdown=ChildTime}|Tail], Time) when ChildTime > Time ->
+ longest_shutdown_time(Tail, ChildTime);
+longest_shutdown_time([_|Tail], Time) ->
+ longest_shutdown_time(Tail, Time).
+
+-spec handle_supervisor_call(any(), {pid(), any()}, children(), module()) -> ok.
+handle_supervisor_call(which_children, {From, Tag}, Children, Module) ->
+ From ! {Tag, which_children(Children, Module)},
+ ok;
+handle_supervisor_call(count_children, {From, Tag}, Children, _) ->
+ From ! {Tag, count_children(Children)},
+ ok;
+%% We disable start_child since only incoming requests
+%% end up creating a new process.
+handle_supervisor_call({start_child, _}, {From, Tag}, _, _) ->
+ From ! {Tag, {error, start_child_disabled}},
+ ok;
+%% All other calls refer to children. We act in a similar way
+%% to a simple_one_for_one so we never find those.
+handle_supervisor_call(_, {From, Tag}, _, _) ->
+ From ! {Tag, {error, not_found}},
+ ok.
+
+-spec which_children(children(), module()) -> [{module(), pid(), worker, [module()]}].
+which_children(Children, Module) ->
+ [{Module, Pid, worker, [Module]} || #child{pid=Pid} <- Children].
+
+-spec count_children(children()) -> [{atom(), non_neg_integer()}].
+count_children(Children) ->
+ Count = length(Children),
+ [
+ {specs, 1},
+ {active, Count},
+ {supervisors, 0},
+ {workers, Count}
+ ].
diff --git a/server/_build/default/lib/cowboy/src/cowboy_clear.erl b/server/_build/default/lib/cowboy/src/cowboy_clear.erl
new file mode 100644
index 0000000..4f3a234
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_clear.erl
@@ -0,0 +1,60 @@
+%% Copyright (c) 2016-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_clear).
+-behavior(ranch_protocol).
+
+-export([start_link/3]).
+-export([start_link/4]).
+-export([connection_process/4]).
+
+%% Ranch 1.
+-spec start_link(ranch:ref(), inet:socket(), module(), cowboy:opts()) -> {ok, pid()}.
+start_link(Ref, _Socket, Transport, Opts) ->
+ start_link(Ref, Transport, Opts).
+
+%% Ranch 2.
+-spec start_link(ranch:ref(), module(), cowboy:opts()) -> {ok, pid()}.
+start_link(Ref, Transport, Opts) ->
+ Pid = proc_lib:spawn_link(?MODULE, connection_process,
+ [self(), Ref, Transport, Opts]),
+ {ok, Pid}.
+
+-spec connection_process(pid(), ranch:ref(), module(), cowboy:opts()) -> ok.
+connection_process(Parent, Ref, Transport, Opts) ->
+ ProxyInfo = case maps:get(proxy_header, Opts, false) of
+ true ->
+ {ok, ProxyInfo0} = ranch:recv_proxy_header(Ref, 1000),
+ ProxyInfo0;
+ false ->
+ undefined
+ end,
+ {ok, Socket} = ranch:handshake(Ref),
+ %% Use cowboy_http2 directly only when 'http' is missing.
+ %% Otherwise switch to cowboy_http2 from cowboy_http.
+ %%
+ %% @todo Extend this option to cowboy_tls and allow disabling
+ %% the switch to cowboy_http2 in cowboy_http. Also document it.
+ Protocol = case maps:get(protocols, Opts, [http2, http]) of
+ [http2] -> cowboy_http2;
+ [_|_] -> cowboy_http
+ end,
+ init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol).
+
+init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol) ->
+ _ = case maps:get(connection_type, Opts, supervisor) of
+ worker -> ok;
+ supervisor -> process_flag(trap_exit, true)
+ end,
+ Protocol:init(Parent, Ref, Socket, Transport, ProxyInfo, Opts).
diff --git a/server/_build/default/lib/cowboy/src/cowboy_clock.erl b/server/_build/default/lib/cowboy/src/cowboy_clock.erl
new file mode 100644
index 0000000..28f8a1b
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_clock.erl
@@ -0,0 +1,221 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% While a gen_server process runs in the background to update
+%% the cache of formatted dates every second, all API calls are
+%% local and directly read from the ETS cache table, providing
+%% fast time and date computations.
+-module(cowboy_clock).
+-behaviour(gen_server).
+
+%% API.
+-export([start_link/0]).
+-export([stop/0]).
+-export([rfc1123/0]).
+-export([rfc1123/1]).
+
+%% 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, {
+ universaltime = undefined :: undefined | calendar:datetime(),
+ rfc1123 = <<>> :: binary(),
+ tref = undefined :: undefined | reference()
+}).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+-spec stop() -> stopped.
+stop() ->
+ gen_server:call(?MODULE, stop).
+
+%% When the ets table doesn't exist, either because of a bug
+%% or because Cowboy is being restarted, we perform in a
+%% slightly degraded state and build a new timestamp for
+%% every request.
+-spec rfc1123() -> binary().
+rfc1123() ->
+ try
+ ets:lookup_element(?MODULE, rfc1123, 2)
+ catch error:badarg ->
+ rfc1123(erlang:universaltime())
+ end.
+
+-spec rfc1123(calendar:datetime()) -> binary().
+rfc1123(DateTime) ->
+ update_rfc1123(<<>>, undefined, DateTime).
+
+%% gen_server.
+
+-spec init([]) -> {ok, #state{}}.
+init([]) ->
+ ?MODULE = ets:new(?MODULE, [set, protected,
+ named_table, {read_concurrency, true}]),
+ T = erlang:universaltime(),
+ B = update_rfc1123(<<>>, undefined, T),
+ TRef = erlang:send_after(1000, self(), update),
+ ets:insert(?MODULE, {rfc1123, B}),
+ {ok, #state{universaltime=T, rfc1123=B, tref=TRef}}.
+
+-type from() :: {pid(), term()}.
+-spec handle_call
+ (stop, from(), State) -> {stop, normal, stopped, State}
+ when State::#state{}.
+handle_call(stop, _From, State) ->
+ {stop, normal, stopped, State};
+handle_call(_Request, _From, State) ->
+ {reply, ignored, State}.
+
+-spec handle_cast(_, State) -> {noreply, State} when State::#state{}.
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+-spec handle_info(any(), State) -> {noreply, State} when State::#state{}.
+handle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef0}) ->
+ %% Cancel the timer in case an external process sent an update message.
+ _ = erlang:cancel_timer(TRef0),
+ T = erlang:universaltime(),
+ B2 = update_rfc1123(B1, Prev, T),
+ ets:insert(?MODULE, {rfc1123, B2}),
+ TRef = erlang:send_after(1000, self(), update),
+ {noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+-spec terminate(_, _) -> ok.
+terminate(_Reason, _State) ->
+ ok.
+
+-spec code_change(_, State, _) -> {ok, State} when State::#state{}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% Internal.
+
+-spec update_rfc1123(binary(), undefined | calendar:datetime(),
+ calendar:datetime()) -> binary().
+update_rfc1123(Bin, Now, Now) ->
+ Bin;
+update_rfc1123(<< Keep:23/binary, _/bits >>,
+ {Date, {H, M, _}}, {Date, {H, M, S}}) ->
+ << Keep/binary, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(<< Keep:20/binary, _/bits >>,
+ {Date, {H, _, _}}, {Date, {H, M, S}}) ->
+ << Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) ->
+ << Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>,
+ {{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, Keep/binary,
+ (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>,
+ {{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
+ (month(Mo))/binary, Keep/binary,
+ (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>;
+update_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ", (pad_int(D))/binary, " ",
+ (month(Mo))/binary, " ", (integer_to_binary(Y))/binary,
+ " ", (pad_int(H))/binary, $:, (pad_int(M))/binary,
+ $:, (pad_int(S))/binary, " GMT" >>.
+
+%% Following suggestion by MononcQc on #erlounge.
+-spec pad_int(0..59) -> binary().
+pad_int(X) when X < 10 ->
+ << $0, ($0 + X) >>;
+pad_int(X) ->
+ integer_to_binary(X).
+
+-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">>.
+
+%% Tests.
+
+-ifdef(TEST).
+update_rfc1123_test_() ->
+ Tests = [
+ {<<"Sat, 14 May 2011 14:25:33 GMT">>, undefined,
+ {{2011, 5, 14}, {14, 25, 33}}, <<>>},
+ {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
+ {{2011, 5, 14}, {14, 25, 33}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
+ {<<"Sat, 14 May 2011 14:25:34 GMT">>, {{2011, 5, 14}, {14, 25, 33}},
+ {{2011, 5, 14}, {14, 25, 34}}, <<"Sat, 14 May 2011 14:25:33 GMT">>},
+ {<<"Sat, 14 May 2011 14:26:00 GMT">>, {{2011, 5, 14}, {14, 25, 59}},
+ {{2011, 5, 14}, {14, 26, 0}}, <<"Sat, 14 May 2011 14:25:59 GMT">>},
+ {<<"Sat, 14 May 2011 15:00:00 GMT">>, {{2011, 5, 14}, {14, 59, 59}},
+ {{2011, 5, 14}, {15, 0, 0}}, <<"Sat, 14 May 2011 14:59:59 GMT">>},
+ {<<"Sun, 15 May 2011 00:00:00 GMT">>, {{2011, 5, 14}, {23, 59, 59}},
+ {{2011, 5, 15}, { 0, 0, 0}}, <<"Sat, 14 May 2011 23:59:59 GMT">>},
+ {<<"Wed, 01 Jun 2011 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
+ {{2011, 6, 1}, { 0, 0, 0}}, <<"Tue, 31 May 2011 23:59:59 GMT">>},
+ {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2011, 5, 31}, {23, 59, 59}},
+ {{2012, 1, 1}, { 0, 0, 0}}, <<"Sat, 31 Dec 2011 23:59:59 GMT">>}
+ ],
+ [{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests].
+
+pad_int_test_() ->
+ Tests = [
+ { 0, <<"00">>}, { 1, <<"01">>}, { 2, <<"02">>}, { 3, <<"03">>},
+ { 4, <<"04">>}, { 5, <<"05">>}, { 6, <<"06">>}, { 7, <<"07">>},
+ { 8, <<"08">>}, { 9, <<"09">>}, {10, <<"10">>}, {11, <<"11">>},
+ {12, <<"12">>}, {13, <<"13">>}, {14, <<"14">>}, {15, <<"15">>},
+ {16, <<"16">>}, {17, <<"17">>}, {18, <<"18">>}, {19, <<"19">>},
+ {20, <<"20">>}, {21, <<"21">>}, {22, <<"22">>}, {23, <<"23">>},
+ {24, <<"24">>}, {25, <<"25">>}, {26, <<"26">>}, {27, <<"27">>},
+ {28, <<"28">>}, {29, <<"29">>}, {30, <<"30">>}, {31, <<"31">>},
+ {32, <<"32">>}, {33, <<"33">>}, {34, <<"34">>}, {35, <<"35">>},
+ {36, <<"36">>}, {37, <<"37">>}, {38, <<"38">>}, {39, <<"39">>},
+ {40, <<"40">>}, {41, <<"41">>}, {42, <<"42">>}, {43, <<"43">>},
+ {44, <<"44">>}, {45, <<"45">>}, {46, <<"46">>}, {47, <<"47">>},
+ {48, <<"48">>}, {49, <<"49">>}, {50, <<"50">>}, {51, <<"51">>},
+ {52, <<"52">>}, {53, <<"53">>}, {54, <<"54">>}, {55, <<"55">>},
+ {56, <<"56">>}, {57, <<"57">>}, {58, <<"58">>}, {59, <<"59">>}
+ ],
+ [{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests].
+-endif.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_compress_h.erl b/server/_build/default/lib/cowboy/src/cowboy_compress_h.erl
new file mode 100644
index 0000000..374cb6a
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_compress_h.erl
@@ -0,0 +1,249 @@
+%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_compress_h).
+-behavior(cowboy_stream).
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+
+-record(state, {
+ next :: any(),
+ threshold :: non_neg_integer() | undefined,
+ compress = undefined :: undefined | gzip,
+ deflate = undefined :: undefined | zlib:zstream(),
+ deflate_flush = sync :: none | sync
+}).
+
+-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
+ -> {cowboy_stream:commands(), #state{}}.
+init(StreamID, Req, Opts) ->
+ State0 = check_req(Req),
+ CompressThreshold = maps:get(compress_threshold, Opts, 300),
+ DeflateFlush = buffering_to_zflush(maps:get(compress_buffering, Opts, false)),
+ {Commands0, Next} = cowboy_stream:init(StreamID, Req, Opts),
+ fold(Commands0, State0#state{next=Next,
+ threshold=CompressThreshold,
+ deflate_flush=DeflateFlush}).
+
+-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+data(StreamID, IsFin, Data, State0=#state{next=Next0}) ->
+ {Commands0, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),
+ fold(Commands0, State0#state{next=Next}).
+
+-spec info(cowboy_stream:streamid(), any(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+info(StreamID, Info, State0=#state{next=Next0}) ->
+ {Commands0, Next} = cowboy_stream:info(StreamID, Info, Next0),
+ fold(Commands0, State0#state{next=Next}).
+
+-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> any().
+terminate(StreamID, Reason, #state{next=Next, deflate=Z}) ->
+ %% Clean the zlib:stream() in case something went wrong.
+ %% In the normal scenario the stream is already closed.
+ case Z of
+ undefined -> ok;
+ _ -> zlib:close(Z)
+ end,
+ cowboy_stream:terminate(StreamID, Reason, Next).
+
+-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
+ cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
+ when Resp::cowboy_stream:resp_command().
+early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
+ cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).
+
+%% Internal.
+
+%% Check if the client supports decoding of gzip responses.
+%%
+%% A malformed accept-encoding header is ignored (no compression).
+check_req(Req) ->
+ try cowboy_req:parse_header(<<"accept-encoding">>, Req) of
+ %% Client doesn't support any compression algorithm.
+ undefined ->
+ #state{compress=undefined};
+ Encodings ->
+ %% We only support gzip so look for it specifically.
+ %% @todo A recipient SHOULD consider "x-gzip" to be
+ %% equivalent to "gzip". (RFC7230 4.2.3)
+ case [E || E={<<"gzip">>, Q} <- Encodings, Q =/= 0] of
+ [] ->
+ #state{compress=undefined};
+ _ ->
+ #state{compress=gzip}
+ end
+ catch
+ _:_ ->
+ #state{compress=undefined}
+ end.
+
+%% Do not compress responses that contain the content-encoding header.
+check_resp_headers(#{<<"content-encoding">> := _}, State) ->
+ State#state{compress=undefined};
+check_resp_headers(_, State) ->
+ State.
+
+fold(Commands, State=#state{compress=undefined}) ->
+ {Commands, State};
+fold(Commands, State) ->
+ fold(Commands, State, []).
+
+fold([], State, Acc) ->
+ {lists:reverse(Acc), State};
+%% We do not compress full sendfile bodies.
+fold([Response={response, _, _, {sendfile, _, _, _}}|Tail], State, Acc) ->
+ fold(Tail, State, [Response|Acc]);
+%% We compress full responses directly, unless they are lower than
+%% the configured threshold or we find we are not able to by looking at the headers.
+fold([Response0={response, _, Headers, Body}|Tail],
+ State0=#state{threshold=CompressThreshold}, Acc) ->
+ case check_resp_headers(Headers, State0) of
+ State=#state{compress=undefined} ->
+ fold(Tail, State, [Response0|Acc]);
+ State1 ->
+ BodyLength = iolist_size(Body),
+ if
+ BodyLength =< CompressThreshold ->
+ fold(Tail, State1, [Response0|Acc]);
+ true ->
+ {Response, State} = gzip_response(Response0, State1),
+ fold(Tail, State, [Response|Acc])
+ end
+ end;
+%% Check headers and initiate compression...
+fold([Response0={headers, _, Headers}|Tail], State0, Acc) ->
+ case check_resp_headers(Headers, State0) of
+ State=#state{compress=undefined} ->
+ fold(Tail, State, [Response0|Acc]);
+ State1 ->
+ {Response, State} = gzip_headers(Response0, State1),
+ fold(Tail, State, [Response|Acc])
+ end;
+%% then compress each data commands individually.
+fold([Data0={data, _, _}|Tail], State0=#state{compress=gzip}, Acc) ->
+ {Data, State} = gzip_data(Data0, State0),
+ fold(Tail, State, [Data|Acc]);
+%% When trailers are sent we need to end the compression.
+%% This results in an extra data command being sent.
+fold([Trailers={trailers, _}|Tail], State0=#state{compress=gzip}, Acc) ->
+ {{data, fin, Data}, State} = gzip_data({data, fin, <<>>}, State0),
+ fold(Tail, State, [Trailers, {data, nofin, Data}|Acc]);
+%% All the options from this handler can be updated for the current stream.
+%% The set_options command must be propagated as-is regardless.
+fold([SetOptions={set_options, Opts}|Tail], State=#state{
+ threshold=CompressThreshold0, deflate_flush=DeflateFlush0}, Acc) ->
+ CompressThreshold = maps:get(compress_threshold, Opts, CompressThreshold0),
+ DeflateFlush = case Opts of
+ #{compress_buffering := CompressBuffering} ->
+ buffering_to_zflush(CompressBuffering);
+ _ ->
+ DeflateFlush0
+ end,
+ fold(Tail, State#state{threshold=CompressThreshold, deflate_flush=DeflateFlush},
+ [SetOptions|Acc]);
+%% Otherwise, we have an unrelated command or compression is disabled.
+fold([Command|Tail], State, Acc) ->
+ fold(Tail, State, [Command|Acc]).
+
+buffering_to_zflush(true) -> none;
+buffering_to_zflush(false) -> sync.
+
+gzip_response({response, Status, Headers, Body}, State) ->
+ %% We can't call zlib:gzip/1 because it does an
+ %% iolist_to_binary(GzBody) at the end to return
+ %% a binary(). Therefore the code here is largely
+ %% a duplicate of the code of that function.
+ Z = zlib:open(),
+ GzBody = try
+ %% 31 = 16+?MAX_WBITS from zlib.erl
+ %% @todo It might be good to allow them to be configured?
+ zlib:deflateInit(Z, default, deflated, 31, 8, default),
+ Gz = zlib:deflate(Z, Body, finish),
+ zlib:deflateEnd(Z),
+ Gz
+ after
+ zlib:close(Z)
+ end,
+ {{response, Status, vary(Headers#{
+ <<"content-length">> => integer_to_binary(iolist_size(GzBody)),
+ <<"content-encoding">> => <<"gzip">>
+ }), GzBody}, State}.
+
+gzip_headers({headers, Status, Headers0}, State) ->
+ Z = zlib:open(),
+ %% We use the same arguments as when compressing the body fully.
+ %% @todo It might be good to allow them to be configured?
+ zlib:deflateInit(Z, default, deflated, 31, 8, default),
+ Headers = maps:remove(<<"content-length">>, Headers0),
+ {{headers, Status, vary(Headers#{
+ <<"content-encoding">> => <<"gzip">>
+ })}, State#state{deflate=Z}}.
+
+%% We must add content-encoding to vary if it's not already there.
+vary(Headers=#{<<"vary">> := Vary}) ->
+ try cow_http_hd:parse_vary(iolist_to_binary(Vary)) of
+ '*' -> Headers;
+ List ->
+ case lists:member(<<"accept-encoding">>, List) of
+ true -> Headers;
+ false -> Headers#{<<"vary">> => [Vary, <<", accept-encoding">>]}
+ end
+ catch _:_ ->
+ %% The vary header is invalid. Probably empty. We replace it with ours.
+ Headers#{<<"vary">> => <<"accept-encoding">>}
+ end;
+vary(Headers) ->
+ Headers#{<<"vary">> => <<"accept-encoding">>}.
+
+%% It is not possible to combine zlib and the sendfile
+%% syscall as far as I can tell, because the zlib format
+%% includes a checksum at the end of the stream. We have
+%% to read the file in memory, making this not suitable for
+%% large files.
+gzip_data({data, nofin, Sendfile={sendfile, _, _, _}},
+ State=#state{deflate=Z, deflate_flush=Flush}) ->
+ {ok, Data0} = read_file(Sendfile),
+ Data = zlib:deflate(Z, Data0, Flush),
+ {{data, nofin, Data}, State};
+gzip_data({data, fin, Sendfile={sendfile, _, _, _}}, State=#state{deflate=Z}) ->
+ {ok, Data0} = read_file(Sendfile),
+ Data = zlib:deflate(Z, Data0, finish),
+ zlib:deflateEnd(Z),
+ zlib:close(Z),
+ {{data, fin, Data}, State#state{deflate=undefined}};
+gzip_data({data, nofin, Data0}, State=#state{deflate=Z, deflate_flush=Flush}) ->
+ Data = zlib:deflate(Z, Data0, Flush),
+ {{data, nofin, Data}, State};
+gzip_data({data, fin, Data0}, State=#state{deflate=Z}) ->
+ Data = zlib:deflate(Z, Data0, finish),
+ zlib:deflateEnd(Z),
+ zlib:close(Z),
+ {{data, fin, Data}, State#state{deflate=undefined}}.
+
+read_file({sendfile, Offset, Bytes, Path}) ->
+ {ok, IoDevice} = file:open(Path, [read, raw, binary]),
+ try
+ _ = case Offset of
+ 0 -> ok;
+ _ -> file:position(IoDevice, {bof, Offset})
+ end,
+ file:read(IoDevice, Bytes)
+ after
+ file:close(IoDevice)
+ end.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_constraints.erl b/server/_build/default/lib/cowboy/src/cowboy_constraints.erl
new file mode 100644
index 0000000..6509c4b
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_constraints.erl
@@ -0,0 +1,174 @@
+%% Copyright (c) 2014-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_constraints).
+
+-export([validate/2]).
+-export([reverse/2]).
+-export([format_error/1]).
+
+-type constraint() :: int | nonempty | fun().
+-export_type([constraint/0]).
+
+-type reason() :: {constraint(), any(), any()}.
+-export_type([reason/0]).
+
+-spec validate(binary(), constraint() | [constraint()])
+ -> {ok, any()} | {error, reason()}.
+validate(Value, Constraints) when is_list(Constraints) ->
+ apply_list(forward, Value, Constraints);
+validate(Value, Constraint) ->
+ apply_list(forward, Value, [Constraint]).
+
+-spec reverse(any(), constraint() | [constraint()])
+ -> {ok, binary()} | {error, reason()}.
+reverse(Value, Constraints) when is_list(Constraints) ->
+ apply_list(reverse, Value, Constraints);
+reverse(Value, Constraint) ->
+ apply_list(reverse, Value, [Constraint]).
+
+-spec format_error(reason()) -> iodata().
+format_error({Constraint, Reason, Value}) ->
+ apply_constraint(format_error, {Reason, Value}, Constraint).
+
+apply_list(_, Value, []) ->
+ {ok, Value};
+apply_list(Type, Value0, [Constraint|Tail]) ->
+ case apply_constraint(Type, Value0, Constraint) of
+ {ok, Value} ->
+ apply_list(Type, Value, Tail);
+ {error, Reason} ->
+ {error, {Constraint, Reason, Value0}}
+ end.
+
+%% @todo {int, From, To}, etc.
+apply_constraint(Type, Value, int) ->
+ int(Type, Value);
+apply_constraint(Type, Value, nonempty) ->
+ nonempty(Type, Value);
+apply_constraint(Type, Value, F) when is_function(F) ->
+ F(Type, Value).
+
+%% Constraint functions.
+
+int(forward, Value) ->
+ try
+ {ok, binary_to_integer(Value)}
+ catch _:_ ->
+ {error, not_an_integer}
+ end;
+int(reverse, Value) ->
+ try
+ {ok, integer_to_binary(Value)}
+ catch _:_ ->
+ {error, not_an_integer}
+ end;
+int(format_error, {not_an_integer, Value}) ->
+ io_lib:format("The value ~p is not an integer.", [Value]).
+
+nonempty(Type, <<>>) when Type =/= format_error ->
+ {error, empty};
+nonempty(Type, Value) when Type =/= format_error, is_binary(Value) ->
+ {ok, Value};
+nonempty(format_error, {empty, Value}) ->
+ io_lib:format("The value ~p is empty.", [Value]).
+
+-ifdef(TEST).
+
+validate_test() ->
+ F = fun(_, Value) ->
+ try
+ {ok, binary_to_atom(Value, latin1)}
+ catch _:_ ->
+ {error, not_a_binary}
+ end
+ end,
+ %% Value, Constraints, Result.
+ Tests = [
+ {<<>>, [], <<>>},
+ {<<"123">>, int, 123},
+ {<<"123">>, [int], 123},
+ {<<"123">>, [nonempty, int], 123},
+ {<<"123">>, [int, nonempty], 123},
+ {<<>>, nonempty, error},
+ {<<>>, [nonempty], error},
+ {<<"hello">>, F, hello},
+ {<<"hello">>, [F], hello},
+ {<<"123">>, [F, int], error},
+ {<<"123">>, [int, F], error},
+ {<<"hello">>, [nonempty, F], hello},
+ {<<"hello">>, [F, nonempty], hello}
+ ],
+ [{lists:flatten(io_lib:format("~p, ~p", [V, C])), fun() ->
+ case R of
+ error -> {error, _} = validate(V, C);
+ _ -> {ok, R} = validate(V, C)
+ end
+ end} || {V, C, R} <- Tests].
+
+reverse_test() ->
+ F = fun(_, Value) ->
+ try
+ {ok, atom_to_binary(Value, latin1)}
+ catch _:_ ->
+ {error, not_an_atom}
+ end
+ end,
+ %% Value, Constraints, Result.
+ Tests = [
+ {<<>>, [], <<>>},
+ {123, int, <<"123">>},
+ {123, [int], <<"123">>},
+ {123, [nonempty, int], <<"123">>},
+ {123, [int, nonempty], <<"123">>},
+ {<<>>, nonempty, error},
+ {<<>>, [nonempty], error},
+ {hello, F, <<"hello">>},
+ {hello, [F], <<"hello">>},
+ {123, [F, int], error},
+ {123, [int, F], error},
+ {hello, [nonempty, F], <<"hello">>},
+ {hello, [F, nonempty], <<"hello">>}
+ ],
+ [{lists:flatten(io_lib:format("~p, ~p", [V, C])), fun() ->
+ case R of
+ error -> {error, _} = reverse(V, C);
+ _ -> {ok, R} = reverse(V, C)
+ end
+ end} || {V, C, R} <- Tests].
+
+int_format_error_test() ->
+ {error, Reason} = validate(<<"string">>, int),
+ Bin = iolist_to_binary(format_error(Reason)),
+ true = is_binary(Bin),
+ ok.
+
+nonempty_format_error_test() ->
+ {error, Reason} = validate(<<>>, nonempty),
+ Bin = iolist_to_binary(format_error(Reason)),
+ true = is_binary(Bin),
+ ok.
+
+fun_format_error_test() ->
+ F = fun
+ (format_error, {test, <<"value">>}) ->
+ formatted;
+ (_, _) ->
+ {error, test}
+ end,
+ {error, Reason} = validate(<<"value">>, F),
+ formatted = format_error(Reason),
+ ok.
+
+-endif.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_handler.erl b/server/_build/default/lib/cowboy/src/cowboy_handler.erl
new file mode 100644
index 0000000..c0f7ff7
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_handler.erl
@@ -0,0 +1,57 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% Handler middleware.
+%%
+%% Execute the handler given by the <em>handler</em> and <em>handler_opts</em>
+%% environment values. The result of this execution is added to the
+%% environment under the <em>result</em> value.
+-module(cowboy_handler).
+-behaviour(cowboy_middleware).
+
+-export([execute/2]).
+-export([terminate/4]).
+
+-callback init(Req, any())
+ -> {ok | module(), Req, any()}
+ | {module(), Req, any(), any()}
+ when Req::cowboy_req:req().
+
+-callback terminate(any(), map(), any()) -> ok.
+-optional_callbacks([terminate/3]).
+
+-spec execute(Req, Env) -> {ok, Req, Env}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) ->
+ try Handler:init(Req, HandlerOpts) of
+ {ok, Req2, State} ->
+ Result = terminate(normal, Req2, State, Handler),
+ {ok, Req2, Env#{result => Result}};
+ {Mod, Req2, State} ->
+ Mod:upgrade(Req2, Env, Handler, State);
+ {Mod, Req2, State, Opts} ->
+ Mod:upgrade(Req2, Env, Handler, State, Opts)
+ catch Class:Reason:Stacktrace ->
+ terminate({crash, Class, Reason}, Req, HandlerOpts, Handler),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+-spec terminate(any(), Req | undefined, any(), module()) -> ok when Req::cowboy_req:req().
+terminate(Reason, Req, State, Handler) ->
+ case erlang:function_exported(Handler, terminate, 3) of
+ true ->
+ Handler:terminate(Reason, Req, State);
+ false ->
+ ok
+ end.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_http.erl b/server/_build/default/lib/cowboy/src/cowboy_http.erl
new file mode 100644
index 0000000..c9bceed
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_http.erl
@@ -0,0 +1,1523 @@
+%% Copyright (c) 2016-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_http).
+
+-export([init/6]).
+
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-type opts() :: #{
+ active_n => pos_integer(),
+ chunked => boolean(),
+ compress_buffering => boolean(),
+ compress_threshold => non_neg_integer(),
+ connection_type => worker | supervisor,
+ env => cowboy_middleware:env(),
+ http10_keepalive => boolean(),
+ idle_timeout => timeout(),
+ inactivity_timeout => timeout(),
+ initial_stream_flow_size => non_neg_integer(),
+ linger_timeout => timeout(),
+ logger => module(),
+ max_authority_length => non_neg_integer(),
+ max_empty_lines => non_neg_integer(),
+ max_header_name_length => non_neg_integer(),
+ max_header_value_length => non_neg_integer(),
+ max_headers => non_neg_integer(),
+ max_keepalive => non_neg_integer(),
+ max_method_length => non_neg_integer(),
+ max_request_line_length => non_neg_integer(),
+ metrics_callback => cowboy_metrics_h:metrics_callback(),
+ metrics_req_filter => fun((cowboy_req:req()) -> map()),
+ metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),
+ middlewares => [module()],
+ proxy_header => boolean(),
+ request_timeout => timeout(),
+ sendfile => boolean(),
+ shutdown_timeout => timeout(),
+ stream_handlers => [module()],
+ tracer_callback => cowboy_tracer_h:tracer_callback(),
+ tracer_flags => [atom()],
+ tracer_match_specs => cowboy_tracer_h:tracer_match_specs(),
+ %% Open ended because configured stream handlers might add options.
+ _ => _
+}.
+-export_type([opts/0]).
+
+-record(ps_request_line, {
+ empty_lines = 0 :: non_neg_integer()
+}).
+
+-record(ps_header, {
+ method = undefined :: binary(),
+ authority = undefined :: binary() | undefined,
+ path = undefined :: binary(),
+ qs = undefined :: binary(),
+ version = undefined :: cowboy:http_version(),
+ headers = undefined :: cowboy:http_headers() | undefined,
+ name = undefined :: binary() | undefined
+}).
+
+-record(ps_body, {
+ length :: non_neg_integer() | undefined,
+ received = 0 :: non_neg_integer(),
+ transfer_decode_fun :: fun((binary(), cow_http_te:state()) -> cow_http_te:decode_ret()),
+ transfer_decode_state :: cow_http_te:state()
+}).
+
+-record(stream, {
+ id = undefined :: cowboy_stream:streamid(),
+ %% Stream handlers and their state.
+ state = undefined :: {module(), any()},
+ %% Request method.
+ method = undefined :: binary(),
+ %% Client HTTP version for this stream.
+ version = undefined :: cowboy:http_version(),
+ %% Unparsed te header. Used to know if we can send trailers.
+ te :: undefined | binary(),
+ %% Expected body size.
+ local_expected_size = undefined :: undefined | non_neg_integer(),
+ %% Sent body size.
+ local_sent_size = 0 :: non_neg_integer(),
+ %% Commands queued.
+ queue = [] :: cowboy_stream:commands()
+}).
+
+-type stream() :: #stream{}.
+
+-record(state, {
+ parent :: pid(),
+ ref :: ranch:ref(),
+ socket :: inet:socket(),
+ transport :: module(),
+ proxy_header :: undefined | ranch_proxy_header:proxy_info(),
+ opts = #{} :: cowboy:opts(),
+ buffer = <<>> :: binary(),
+
+ %% Some options may be overriden for the current stream.
+ overriden_opts = #{} :: cowboy:opts(),
+
+ %% Remote address and port for the connection.
+ peer = undefined :: {inet:ip_address(), inet:port_number()},
+
+ %% Local address and port for the connection.
+ sock = undefined :: {inet:ip_address(), inet:port_number()},
+
+ %% Client certificate (TLS only).
+ cert :: undefined | binary(),
+
+ timer = undefined :: undefined | reference(),
+
+ %% Whether we are currently receiving data from the socket.
+ active = true :: boolean(),
+
+ %% Identifier for the stream currently being read (or waiting to be received).
+ in_streamid = 1 :: pos_integer(),
+
+ %% Parsing state for the current stream or stream-to-be.
+ in_state = #ps_request_line{} :: #ps_request_line{} | #ps_header{} | #ps_body{},
+
+ %% Flow requested for the current stream.
+ flow = infinity :: non_neg_integer() | infinity,
+
+ %% Identifier for the stream currently being written.
+ %% Note that out_streamid =< in_streamid.
+ out_streamid = 1 :: pos_integer(),
+
+ %% Whether we finished writing data for the current stream.
+ out_state = wait :: wait | chunked | streaming | done,
+
+ %% The connection will be closed after this stream.
+ last_streamid = undefined :: pos_integer(),
+
+ %% Currently active HTTP/1.1 streams.
+ streams = [] :: [stream()],
+
+ %% Children processes created by streams.
+ children = cowboy_children:init() :: cowboy_children:children()
+}).
+
+-include_lib("cowlib/include/cow_inline.hrl").
+-include_lib("cowlib/include/cow_parse.hrl").
+
+-spec init(pid(), ranch:ref(), inet:socket(), module(),
+ ranch_proxy_header:proxy_info(), cowboy:opts()) -> ok.
+init(Parent, Ref, Socket, Transport, ProxyHeader, Opts) ->
+ Peer0 = Transport:peername(Socket),
+ Sock0 = Transport:sockname(Socket),
+ Cert1 = case Transport:name() of
+ ssl ->
+ case ssl:peercert(Socket) of
+ {error, no_peercert} ->
+ {ok, undefined};
+ Cert0 ->
+ Cert0
+ end;
+ _ ->
+ {ok, undefined}
+ end,
+ case {Peer0, Sock0, Cert1} of
+ {{ok, Peer}, {ok, Sock}, {ok, Cert}} ->
+ State = #state{
+ parent=Parent, ref=Ref, socket=Socket,
+ transport=Transport, proxy_header=ProxyHeader, opts=Opts,
+ peer=Peer, sock=Sock, cert=Cert,
+ last_streamid=maps:get(max_keepalive, Opts, 1000)},
+ setopts_active(State),
+ loop(set_timeout(State, request_timeout));
+ {{error, Reason}, _, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the peer name.'});
+ {_, {error, Reason}, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the sock name.'});
+ {_, _, {error, Reason}} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the client TLS certificate.'})
+ end.
+
+setopts_active(#state{socket=Socket, transport=Transport, opts=Opts}) ->
+ N = maps:get(active_n, Opts, 100),
+ Transport:setopts(Socket, [{active, N}]).
+
+active(State) ->
+ setopts_active(State),
+ State#state{active=true}.
+
+passive(State=#state{socket=Socket, transport=Transport}) ->
+ Transport:setopts(Socket, [{active, false}]),
+ Messages = Transport:messages(),
+ flush_passive(Socket, Messages),
+ State#state{active=false}.
+
+flush_passive(Socket, Messages) ->
+ receive
+ {Passive, Socket} when Passive =:= element(4, Messages);
+ %% Hardcoded for compatibility with Ranch 1.x.
+ Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ flush_passive(Socket, Messages)
+ after 0 ->
+ ok
+ end.
+
+loop(State=#state{parent=Parent, socket=Socket, transport=Transport, opts=Opts,
+ buffer=Buffer, timer=TimerRef, children=Children, in_streamid=InStreamID,
+ last_streamid=LastStreamID}) ->
+ Messages = Transport:messages(),
+ InactivityTimeout = maps:get(inactivity_timeout, Opts, 300000),
+ receive
+ %% Discard data coming in after the last request
+ %% we want to process was received fully.
+ {OK, Socket, _} when OK =:= element(1, Messages), InStreamID > LastStreamID ->
+ loop(State);
+ %% Socket messages.
+ {OK, Socket, Data} when OK =:= element(1, Messages) ->
+ parse(<< Buffer/binary, Data/binary >>, State);
+ {Closed, Socket} when Closed =:= element(2, Messages) ->
+ terminate(State, {socket_error, closed, 'The socket has been closed.'});
+ {Error, Socket, Reason} when Error =:= element(3, Messages) ->
+ terminate(State, {socket_error, Reason, 'An error has occurred on the socket.'});
+ {Passive, Socket} when Passive =:= element(4, Messages);
+ %% Hardcoded for compatibility with Ranch 1.x.
+ Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ setopts_active(State),
+ loop(State);
+ %% Timeouts.
+ {timeout, Ref, {shutdown, Pid}} ->
+ cowboy_children:shutdown_timeout(Children, Ref, Pid),
+ loop(State);
+ {timeout, TimerRef, Reason} ->
+ timeout(State, Reason);
+ {timeout, _, _} ->
+ loop(State);
+ %% System messages.
+ {'EXIT', Parent, shutdown} ->
+ Reason = {stop, {exit, shutdown}, 'Parent process requested shutdown.'},
+ loop(initiate_closing(State, Reason));
+ {'EXIT', Parent, Reason} ->
+ terminate(State, {stop, {exit, Reason}, 'Parent process terminated.'});
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [], State);
+ %% Messages pertaining to a stream.
+ {{Pid, StreamID}, Msg} when Pid =:= self() ->
+ loop(info(State, StreamID, Msg));
+ %% Exit signal from children.
+ Msg = {'EXIT', Pid, _} ->
+ loop(down(State, Pid, Msg));
+ %% Calls from supervisor module.
+ {'$gen_call', From, Call} ->
+ cowboy_children:handle_supervisor_call(Call, From, Children, ?MODULE),
+ loop(State);
+ %% Unknown messages.
+ Msg ->
+ cowboy:log(warning, "Received stray message ~p.~n", [Msg], Opts),
+ loop(State)
+ after InactivityTimeout ->
+ terminate(State, {internal_error, timeout, 'No message or data received before timeout.'})
+ end.
+
+%% We do not set request_timeout if there are active streams.
+set_timeout(State=#state{streams=[_|_]}, request_timeout) ->
+ State;
+%% We do not set request_timeout if we are skipping a body.
+set_timeout(State=#state{in_state=#ps_body{}}, request_timeout) ->
+ State;
+%% We do not set idle_timeout if there are no active streams,
+%% unless when we are skipping a body.
+set_timeout(State=#state{streams=[], in_state=InState}, idle_timeout)
+ when element(1, InState) =/= ps_body ->
+ State;
+%% Otherwise we can set the timeout.
+set_timeout(State0=#state{opts=Opts, overriden_opts=Override}, Name) ->
+ State = cancel_timeout(State0),
+ Default = case Name of
+ request_timeout -> 5000;
+ idle_timeout -> 60000
+ end,
+ Timeout = case Override of
+ %% The timeout may have been overriden for the current stream.
+ #{Name := Timeout0} -> Timeout0;
+ _ -> maps:get(Name, Opts, Default)
+ end,
+ TimerRef = case Timeout of
+ infinity -> undefined;
+ Timeout -> erlang:start_timer(Timeout, self(), Name)
+ end,
+ State#state{timer=TimerRef}.
+
+cancel_timeout(State=#state{timer=TimerRef}) ->
+ ok = case TimerRef of
+ undefined ->
+ ok;
+ _ ->
+ %% Do a synchronous cancel and remove the message if any
+ %% to avoid receiving stray messages.
+ _ = erlang:cancel_timer(TimerRef),
+ receive
+ {timeout, TimerRef, _} -> ok
+ after 0 ->
+ ok
+ end
+ end,
+ State#state{timer=undefined}.
+
+-spec timeout(_, _) -> no_return().
+timeout(State=#state{in_state=#ps_request_line{}}, request_timeout) ->
+ terminate(State, {connection_error, timeout,
+ 'No request-line received before timeout.'});
+timeout(State=#state{in_state=#ps_header{}}, request_timeout) ->
+ error_terminate(408, State, {connection_error, timeout,
+ 'Request headers not received before timeout.'});
+timeout(State, idle_timeout) ->
+ terminate(State, {connection_error, timeout,
+ 'Connection idle longer than configuration allows.'}).
+
+parse(<<>>, State) ->
+ loop(State#state{buffer= <<>>});
+%% Do not process requests that come in after the last request
+%% and discard the buffer if any to save memory.
+parse(_, State=#state{in_streamid=InStreamID, in_state=#ps_request_line{},
+ last_streamid=LastStreamID}) when InStreamID > LastStreamID ->
+ loop(State#state{buffer= <<>>});
+parse(Buffer, State=#state{in_state=#ps_request_line{empty_lines=EmptyLines}}) ->
+ after_parse(parse_request(Buffer, State, EmptyLines));
+parse(Buffer, State=#state{in_state=PS=#ps_header{headers=Headers, name=undefined}}) ->
+ after_parse(parse_header(Buffer,
+ State#state{in_state=PS#ps_header{headers=undefined}},
+ Headers));
+parse(Buffer, State=#state{in_state=PS=#ps_header{headers=Headers, name=Name}}) ->
+ after_parse(parse_hd_before_value(Buffer,
+ State#state{in_state=PS#ps_header{headers=undefined, name=undefined}},
+ Headers, Name));
+parse(Buffer, State=#state{in_state=#ps_body{}}) ->
+ after_parse(parse_body(Buffer, State)).
+
+after_parse({request, Req=#{streamid := StreamID, method := Method,
+ headers := Headers, version := Version},
+ State0=#state{opts=Opts, buffer=Buffer, streams=Streams0}}) ->
+ try cowboy_stream:init(StreamID, Req, Opts) of
+ {Commands, StreamState} ->
+ Flow = maps:get(initial_stream_flow_size, Opts, 65535),
+ TE = maps:get(<<"te">>, Headers, undefined),
+ Streams = [#stream{id=StreamID, state=StreamState,
+ method=Method, version=Version, te=TE}|Streams0],
+ State1 = case maybe_req_close(State0, Headers, Version) of
+ close -> State0#state{streams=Streams, last_streamid=StreamID, flow=Flow};
+ keepalive -> State0#state{streams=Streams, flow=Flow}
+ end,
+ State = set_timeout(State1, idle_timeout),
+ parse(Buffer, commands(State, StreamID, Commands))
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(init,
+ [StreamID, Req, Opts],
+ Class, Exception, Stacktrace), Opts),
+ early_error(500, State0, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:init/3.'}, Req),
+ parse(Buffer, State0)
+ end;
+%% Streams are sequential so the body is always about the last stream created
+%% unless that stream has terminated.
+after_parse({data, StreamID, IsFin, Data, State0=#state{opts=Opts, buffer=Buffer,
+ streams=Streams0=[Stream=#stream{id=StreamID, state=StreamState0}|_]}}) ->
+ try cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of
+ {Commands, StreamState} ->
+ Streams = lists:keyreplace(StreamID, #stream.id, Streams0,
+ Stream#stream{state=StreamState}),
+ State1 = set_timeout(State0, case IsFin of
+ fin -> request_timeout;
+ nofin -> idle_timeout
+ end),
+ State = update_flow(IsFin, Data, State1#state{streams=Streams}),
+ parse(Buffer, commands(State, StreamID, Commands))
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(data,
+ [StreamID, IsFin, Data, StreamState0],
+ Class, Exception, Stacktrace), Opts),
+ %% @todo Should call parse after this.
+ stream_terminate(State0, StreamID, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:data/4.'})
+ end;
+%% No corresponding stream. We must skip the body of the previous request
+%% in order to process the next one.
+after_parse({data, _, IsFin, _, State}) ->
+ loop(set_timeout(State, case IsFin of
+ fin -> request_timeout;
+ nofin -> idle_timeout
+ end));
+after_parse({more, State}) ->
+ loop(set_timeout(State, idle_timeout)).
+
+update_flow(fin, _, State) ->
+ %% This function is only called after parsing, therefore we
+ %% are expecting to be in active mode already.
+ State#state{flow=infinity};
+update_flow(nofin, Data, State0=#state{flow=Flow0}) ->
+ Flow = Flow0 - byte_size(Data),
+ State = State0#state{flow=Flow},
+ if
+ Flow0 > 0, Flow =< 0 ->
+ passive(State);
+ true ->
+ State
+ end.
+
+%% Request-line.
+
+-spec parse_request(Buffer, State, non_neg_integer())
+ -> {request, cowboy_req:req(), State}
+ | {data, cowboy_stream:streamid(), cowboy_stream:fin(), binary(), State}
+ | {more, State}
+ when Buffer::binary(), State::#state{}.
+%% Empty lines must be using \r\n.
+parse_request(<< $\n, _/bits >>, State, _) ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Empty lines between requests must use the CRLF line terminator. (RFC7230 3.5)'});
+parse_request(<< $\s, _/bits >>, State, _) ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'The request-line must not begin with a space. (RFC7230 3.1.1, RFC7230 3.5)'});
+%% We limit the length of the Request-line to MaxLength to avoid endlessly
+%% reading from the socket and eventually crashing.
+parse_request(Buffer, State=#state{opts=Opts, in_streamid=InStreamID}, EmptyLines) ->
+ MaxLength = maps:get(max_request_line_length, Opts, 8000),
+ MaxEmptyLines = maps:get(max_empty_lines, Opts, 5),
+ case match_eol(Buffer, 0) of
+ nomatch when byte_size(Buffer) > MaxLength ->
+ error_terminate(414, State, {connection_error, limit_reached,
+ 'The request-line length is larger than configuration allows. (RFC7230 3.1.1)'});
+ nomatch ->
+ {more, State#state{buffer=Buffer, in_state=#ps_request_line{empty_lines=EmptyLines}}};
+ 1 when EmptyLines =:= MaxEmptyLines ->
+ error_terminate(400, State, {connection_error, limit_reached,
+ 'More empty lines were received than configuration allows. (RFC7230 3.5)'});
+ 1 ->
+ << _:16, Rest/bits >> = Buffer,
+ parse_request(Rest, State, EmptyLines + 1);
+ _ ->
+ case Buffer of
+ %% @todo * is only for server-wide OPTIONS request (RFC7230 5.3.4); tests
+ << "OPTIONS * ", Rest/bits >> ->
+ parse_version(Rest, State, <<"OPTIONS">>, undefined, <<"*">>, <<>>);
+ <<"CONNECT ", _/bits>> ->
+ error_terminate(501, State, {connection_error, no_error,
+ 'The CONNECT method is currently not implemented. (RFC7231 4.3.6)'});
+ <<"TRACE ", _/bits>> ->
+ error_terminate(501, State, {connection_error, no_error,
+ 'The TRACE method is currently not implemented. (RFC7231 4.3.8)'});
+ %% Accept direct HTTP/2 only at the beginning of the connection.
+ << "PRI * HTTP/2.0\r\n", _/bits >> when InStreamID =:= 1 ->
+ %% @todo Might be worth throwing to get a clean stacktrace.
+ http2_upgrade(State, Buffer);
+ _ ->
+ parse_method(Buffer, State, <<>>,
+ maps:get(max_method_length, Opts, 32))
+ end
+ end.
+
+match_eol(<< $\n, _/bits >>, N) ->
+ N;
+match_eol(<< _, Rest/bits >>, N) ->
+ match_eol(Rest, N + 1);
+match_eol(_, _) ->
+ nomatch.
+
+parse_method(_, State, _, 0) ->
+ error_terminate(501, State, {connection_error, limit_reached,
+ 'The method name is longer than configuration allows. (RFC7230 3.1.1)'});
+parse_method(<< C, Rest/bits >>, State, SoFar, Remaining) ->
+ case C of
+ $\r -> error_terminate(400, State, {connection_error, protocol_error,
+ 'The method name must not be followed with a line break. (RFC7230 3.1.1)'});
+ $\s -> parse_uri(Rest, State, SoFar);
+ _ when ?IS_TOKEN(C) -> parse_method(Rest, State, << SoFar/binary, C >>, Remaining - 1);
+ _ -> error_terminate(400, State, {connection_error, protocol_error,
+ 'The method name must contain only valid token characters. (RFC7230 3.1.1)'})
+ end.
+
+parse_uri(<< H, T, T, P, "://", Rest/bits >>, State, Method)
+ when H =:= $h orelse H =:= $H, T =:= $t orelse T =:= $T;
+ P =:= $p orelse P =:= $P ->
+ parse_uri_authority(Rest, State, Method);
+parse_uri(<< H, T, T, P, S, "://", Rest/bits >>, State, Method)
+ when H =:= $h orelse H =:= $H, T =:= $t orelse T =:= $T;
+ P =:= $p orelse P =:= $P; S =:= $s orelse S =:= $S ->
+ parse_uri_authority(Rest, State, Method);
+parse_uri(<< $/, Rest/bits >>, State, Method) ->
+ parse_uri_path(Rest, State, Method, undefined, <<$/>>);
+parse_uri(_, State, _) ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Invalid request-line or request-target. (RFC7230 3.1.1, RFC7230 5.3)'}).
+
+%% @todo We probably want to apply max_authority_length also
+%% to the host header and to document this option. It might
+%% also be useful for HTTP/2 requests.
+parse_uri_authority(Rest, State=#state{opts=Opts}, Method) ->
+ parse_uri_authority(Rest, State, Method, <<>>,
+ maps:get(max_authority_length, Opts, 255)).
+
+parse_uri_authority(_, State, _, _, 0) ->
+ error_terminate(414, State, {connection_error, limit_reached,
+ 'The authority component of the absolute URI is longer than configuration allows. (RFC7230 2.7.1)'});
+parse_uri_authority(<<C, Rest/bits>>, State, Method, SoFar, Remaining) ->
+ case C of
+ $\r ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});
+ $@ ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Absolute URIs must not include a userinfo component. (RFC7230 2.7.1)'});
+ C when SoFar =:= <<>> andalso
+ ((C =:= $/) orelse (C =:= $\s) orelse (C =:= $?) orelse (C =:= $#)) ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Absolute URIs must include a non-empty host component. (RFC7230 2.7.1)'});
+ $: when SoFar =:= <<>> ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Absolute URIs must include a non-empty host component. (RFC7230 2.7.1)'});
+ $/ -> parse_uri_path(Rest, State, Method, SoFar, <<"/">>);
+ $\s -> parse_version(Rest, State, Method, SoFar, <<"/">>, <<>>);
+ $? -> parse_uri_query(Rest, State, Method, SoFar, <<"/">>, <<>>);
+ $# -> skip_uri_fragment(Rest, State, Method, SoFar, <<"/">>, <<>>);
+ C -> parse_uri_authority(Rest, State, Method, <<SoFar/binary, C>>, Remaining - 1)
+ end.
+
+parse_uri_path(<<C, Rest/bits>>, State, Method, Authority, SoFar) ->
+ case C of
+ $\r -> error_terminate(400, State, {connection_error, protocol_error,
+ 'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});
+ $\s -> parse_version(Rest, State, Method, Authority, SoFar, <<>>);
+ $? -> parse_uri_query(Rest, State, Method, Authority, SoFar, <<>>);
+ $# -> skip_uri_fragment(Rest, State, Method, Authority, SoFar, <<>>);
+ _ -> parse_uri_path(Rest, State, Method, Authority, <<SoFar/binary, C>>)
+ end.
+
+parse_uri_query(<<C, Rest/bits>>, State, M, A, P, SoFar) ->
+ case C of
+ $\r -> error_terminate(400, State, {connection_error, protocol_error,
+ 'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});
+ $\s -> parse_version(Rest, State, M, A, P, SoFar);
+ $# -> skip_uri_fragment(Rest, State, M, A, P, SoFar);
+ _ -> parse_uri_query(Rest, State, M, A, P, <<SoFar/binary, C>>)
+ end.
+
+skip_uri_fragment(<<C, Rest/bits>>, State, M, A, P, Q) ->
+ case C of
+ $\r -> error_terminate(400, State, {connection_error, protocol_error,
+ 'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});
+ $\s -> parse_version(Rest, State, M, A, P, Q);
+ _ -> skip_uri_fragment(Rest, State, M, A, P, Q)
+ end.
+
+parse_version(<< "HTTP/1.1\r\n", Rest/bits >>, State, M, A, P, Q) ->
+ before_parse_headers(Rest, State, M, A, P, Q, 'HTTP/1.1');
+parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, State, M, A, P, Q) ->
+ before_parse_headers(Rest, State, M, A, P, Q, 'HTTP/1.0');
+parse_version(<< "HTTP/1.", _, C, _/bits >>, State, _, _, _, _) when C =:= $\s; C =:= $\t ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Whitespace is not allowed after the HTTP version. (RFC7230 3.1.1)'});
+parse_version(<< C, _/bits >>, State, _, _, _, _) when C =:= $\s; C =:= $\t ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'The separator between request target and version must be a single SP. (RFC7230 3.1.1)'});
+parse_version(_, State, _, _, _, _) ->
+ error_terminate(505, State, {connection_error, protocol_error,
+ 'Unsupported HTTP version. (RFC7230 2.6)'}).
+
+before_parse_headers(Rest, State, M, A, P, Q, V) ->
+ parse_header(Rest, State#state{in_state=#ps_header{
+ method=M, authority=A, path=P, qs=Q, version=V}}, #{}).
+
+%% Headers.
+
+%% We need two or more bytes in the buffer to continue.
+parse_header(Rest, State=#state{in_state=PS}, Headers) when byte_size(Rest) < 2 ->
+ {more, State#state{buffer=Rest, in_state=PS#ps_header{headers=Headers}}};
+parse_header(<< $\r, $\n, Rest/bits >>, S, Headers) ->
+ request(Rest, S, Headers);
+parse_header(Buffer, State=#state{opts=Opts, in_state=PS}, Headers) ->
+ MaxHeaders = maps:get(max_headers, Opts, 100),
+ NumHeaders = maps:size(Headers),
+ if
+ NumHeaders >= MaxHeaders ->
+ error_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},
+ {connection_error, limit_reached,
+ 'The number of headers is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});
+ true ->
+ parse_header_colon(Buffer, State, Headers)
+ end.
+
+parse_header_colon(Buffer, State=#state{opts=Opts, in_state=PS}, Headers) ->
+ MaxLength = maps:get(max_header_name_length, Opts, 64),
+ case match_colon(Buffer, 0) of
+ nomatch when byte_size(Buffer) > MaxLength ->
+ error_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},
+ {connection_error, limit_reached,
+ 'A header name is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});
+ nomatch ->
+ %% We don't have a colon but we might have an invalid header line,
+ %% so check if we have an LF and abort with an error if we do.
+ case match_eol(Buffer, 0) of
+ nomatch ->
+ {more, State#state{buffer=Buffer, in_state=PS#ps_header{headers=Headers}}};
+ _ ->
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},
+ {connection_error, protocol_error,
+ 'A header line is missing a colon separator. (RFC7230 3.2.4)'})
+ end;
+ _ ->
+ parse_hd_name(Buffer, State, Headers, <<>>)
+ end.
+
+match_colon(<< $:, _/bits >>, N) ->
+ N;
+match_colon(<< _, Rest/bits >>, N) ->
+ match_colon(Rest, N + 1);
+match_colon(_, _) ->
+ nomatch.
+
+parse_hd_name(<< $:, Rest/bits >>, State, H, SoFar) ->
+ parse_hd_before_value(Rest, State, H, SoFar);
+parse_hd_name(<< C, _/bits >>, State=#state{in_state=PS}, H, <<>>) when ?IS_WS(C) ->
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=H}},
+ {connection_error, protocol_error,
+ 'Whitespace is not allowed before the header name. (RFC7230 3.2)'});
+parse_hd_name(<< C, _/bits >>, State=#state{in_state=PS}, H, _) when ?IS_WS(C) ->
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=H}},
+ {connection_error, protocol_error,
+ 'Whitespace is not allowed between the header name and the colon. (RFC7230 3.2.4)'});
+parse_hd_name(<< C, Rest/bits >>, State, H, SoFar) ->
+ ?LOWER(parse_hd_name, Rest, State, H, SoFar).
+
+parse_hd_before_value(<< $\s, Rest/bits >>, S, H, N) ->
+ parse_hd_before_value(Rest, S, H, N);
+parse_hd_before_value(<< $\t, Rest/bits >>, S, H, N) ->
+ parse_hd_before_value(Rest, S, H, N);
+parse_hd_before_value(Buffer, State=#state{opts=Opts, in_state=PS}, H, N) ->
+ MaxLength = maps:get(max_header_value_length, Opts, 4096),
+ case match_eol(Buffer, 0) of
+ nomatch when byte_size(Buffer) > MaxLength ->
+ error_terminate(431, State#state{in_state=PS#ps_header{headers=H}},
+ {connection_error, limit_reached,
+ 'A header value is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});
+ nomatch ->
+ {more, State#state{buffer=Buffer, in_state=PS#ps_header{headers=H, name=N}}};
+ _ ->
+ parse_hd_value(Buffer, State, H, N, <<>>)
+ end.
+
+parse_hd_value(<< $\r, $\n, Rest/bits >>, S, Headers0, Name, SoFar) ->
+ Value = clean_value_ws_end(SoFar, byte_size(SoFar) - 1),
+ Headers = case maps:get(Name, Headers0, undefined) of
+ undefined -> Headers0#{Name => Value};
+ %% The cookie header does not use proper HTTP header lists.
+ Value0 when Name =:= <<"cookie">> -> Headers0#{Name => << Value0/binary, "; ", Value/binary >>};
+ Value0 -> Headers0#{Name => << Value0/binary, ", ", Value/binary >>}
+ end,
+ parse_header(Rest, S, Headers);
+parse_hd_value(<< C, Rest/bits >>, S, H, N, SoFar) ->
+ parse_hd_value(Rest, S, H, N, << SoFar/binary, C >>).
+
+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).
+clean_value_ws_end_test_() ->
+ Tests = [
+ {<<>>, <<>>},
+ {<<" ">>, <<>>},
+ {<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5 \t \t ">>,
+ <<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5">>}
+ ],
+ [{V, fun() -> R = clean_value_ws_end(V, byte_size(V) - 1) end} || {V, R} <- Tests].
+
+horse_clean_value_ws_end() ->
+ horse:repeat(200000,
+ clean_value_ws_end(
+ <<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5 ">>,
+ byte_size(<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5 ">>) - 1)
+ ).
+-endif.
+
+request(Buffer, State=#state{transport=Transport,
+ in_state=PS=#ps_header{authority=Authority, version=Version}}, Headers) ->
+ case maps:get(<<"host">>, Headers, undefined) of
+ undefined when Version =:= 'HTTP/1.1' ->
+ %% @todo Might want to not close the connection on this and next one.
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},
+ {stream_error, protocol_error,
+ 'HTTP/1.1 requests must include a host header. (RFC7230 5.4)'});
+ undefined ->
+ request(Buffer, State, Headers, <<>>, default_port(Transport:secure()));
+ %% @todo When CONNECT requests come in we need to ignore the RawHost
+ %% and instead use the Authority as the source of host.
+ RawHost when Authority =:= undefined; Authority =:= RawHost ->
+ request_parse_host(Buffer, State, Headers, RawHost);
+ %% RFC7230 does not explicitly ask us to reject requests
+ %% that have a different authority component and host header.
+ %% However it DOES ask clients to set them to the same value,
+ %% so we enforce that.
+ _ ->
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},
+ {stream_error, protocol_error,
+ 'The host header is different than the absolute-form authority component. (RFC7230 5.4)'})
+ end.
+
+request_parse_host(Buffer, State=#state{transport=Transport, in_state=PS}, Headers, RawHost) ->
+ try cow_http_hd:parse_host(RawHost) of
+ {Host, undefined} ->
+ request(Buffer, State, Headers, Host, default_port(Transport:secure()));
+ {Host, Port} when Port > 0, Port =< 65535 ->
+ request(Buffer, State, Headers, Host, Port);
+ _ ->
+ error_terminate(400, State, {stream_error, protocol_error,
+ 'The port component of the absolute-form is not in the range 0..65535. (RFC7230 2.7.1)'})
+ catch _:_ ->
+ error_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},
+ {stream_error, protocol_error,
+ 'The host header is invalid. (RFC7230 5.4)'})
+ end.
+
+-spec default_port(boolean()) -> 80 | 443.
+default_port(true) -> 443;
+default_port(_) -> 80.
+
+%% End of request parsing.
+
+request(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock, cert=Cert,
+ proxy_header=ProxyHeader, in_streamid=StreamID, in_state=
+ PS=#ps_header{method=Method, path=Path, qs=Qs, version=Version}},
+ Headers0, Host, Port) ->
+ Scheme = case Transport:secure() of
+ true -> <<"https">>;
+ false -> <<"http">>
+ end,
+ {Headers, HasBody, BodyLength, TDecodeFun, TDecodeState} = case Headers0 of
+ #{<<"transfer-encoding">> := TransferEncoding0} ->
+ try cow_http_hd:parse_transfer_encoding(TransferEncoding0) of
+ [<<"chunked">>] ->
+ {maps:remove(<<"content-length">>, Headers0),
+ true, undefined, fun cow_http_te:stream_chunked/2, {0, 0}};
+ _ ->
+ error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers0}},
+ {stream_error, protocol_error,
+ 'Cowboy only supports transfer-encoding: chunked. (RFC7230 3.3.1)'})
+ catch _:_ ->
+ error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers0}},
+ {stream_error, protocol_error,
+ 'The transfer-encoding header is invalid. (RFC7230 3.3.1)'})
+ end;
+ #{<<"content-length">> := <<"0">>} ->
+ {Headers0, false, 0, undefined, undefined};
+ #{<<"content-length">> := BinLength} ->
+ Length = try
+ cow_http_hd:parse_content_length(BinLength)
+ catch _:_ ->
+ error_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers0}},
+ {stream_error, protocol_error,
+ 'The content-length header is invalid. (RFC7230 3.3.2)'})
+ end,
+ {Headers0, true, Length, fun cow_http_te:stream_identity/2, {0, Length}};
+ _ ->
+ {Headers0, false, 0, undefined, undefined}
+ end,
+ Req0 = #{
+ ref => Ref,
+ pid => self(),
+ streamid => StreamID,
+ peer => Peer,
+ sock => Sock,
+ cert => Cert,
+ method => Method,
+ scheme => Scheme,
+ host => Host,
+ port => Port,
+ path => Path,
+ qs => Qs,
+ version => Version,
+ %% We are transparently taking care of transfer-encodings so
+ %% the user code has no need to know about it.
+ headers => maps:remove(<<"transfer-encoding">>, Headers),
+ has_body => HasBody,
+ body_length => BodyLength
+ },
+ %% We add the PROXY header information if any.
+ Req = case ProxyHeader of
+ undefined -> Req0;
+ _ -> Req0#{proxy_header => ProxyHeader}
+ end,
+ case is_http2_upgrade(Headers, Version) of
+ false ->
+ State = case HasBody of
+ true ->
+ State0#state{in_state=#ps_body{
+ length = BodyLength,
+ transfer_decode_fun = TDecodeFun,
+ transfer_decode_state = TDecodeState
+ }};
+ false ->
+ State0#state{in_streamid=StreamID + 1, in_state=#ps_request_line{}}
+ end,
+ {request, Req, State#state{buffer=Buffer}};
+ {true, HTTP2Settings} ->
+ %% We save the headers in case the upgrade will fail
+ %% and we need to pass them to cowboy_stream:early_error.
+ http2_upgrade(State0#state{in_state=PS#ps_header{headers=Headers}},
+ Buffer, HTTP2Settings, Req)
+ end.
+
+%% HTTP/2 upgrade.
+
+%% @todo We must not upgrade to h2c over a TLS connection.
+is_http2_upgrade(#{<<"connection">> := Conn, <<"upgrade">> := Upgrade,
+ <<"http2-settings">> := HTTP2Settings}, 'HTTP/1.1') ->
+ Conns = cow_http_hd:parse_connection(Conn),
+ case {lists:member(<<"upgrade">>, Conns), lists:member(<<"http2-settings">>, Conns)} of
+ {true, true} ->
+ Protocols = cow_http_hd:parse_upgrade(Upgrade),
+ case lists:member(<<"h2c">>, Protocols) of
+ true ->
+ {true, HTTP2Settings};
+ false ->
+ false
+ end;
+ _ ->
+ false
+ end;
+is_http2_upgrade(_, _) ->
+ false.
+
+%% Prior knowledge upgrade, without an HTTP/1.1 request.
+http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
+ proxy_header=ProxyHeader, opts=Opts, peer=Peer, sock=Sock, cert=Cert}, Buffer) ->
+ case Transport:secure() of
+ false ->
+ _ = cancel_timeout(State),
+ cowboy_http2:init(Parent, Ref, Socket, Transport,
+ ProxyHeader, Opts, Peer, Sock, Cert, Buffer);
+ true ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'Clients that support HTTP/2 over TLS MUST use ALPN. (RFC7540 3.4)'})
+ end.
+
+%% Upgrade via an HTTP/1.1 request.
+http2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,
+ proxy_header=ProxyHeader, opts=Opts, peer=Peer, sock=Sock, cert=Cert},
+ Buffer, HTTP2Settings, Req) ->
+ %% @todo
+ %% However if the client sent a body, we need to read the body in full
+ %% and if we can't do that, return a 413 response. Some options are in order.
+ %% Always half-closed stream coming from this side.
+ try cow_http_hd:parse_http2_settings(HTTP2Settings) of
+ Settings ->
+ _ = cancel_timeout(State),
+ cowboy_http2:init(Parent, Ref, Socket, Transport,
+ ProxyHeader, Opts, Peer, Sock, Cert, Buffer, Settings, Req)
+ catch _:_ ->
+ error_terminate(400, State, {connection_error, protocol_error,
+ 'The HTTP2-Settings header must contain a base64 SETTINGS payload. (RFC7540 3.2, RFC7540 3.2.1)'})
+ end.
+
+%% Request body parsing.
+
+parse_body(Buffer, State=#state{in_streamid=StreamID, in_state=
+ PS=#ps_body{received=Received, transfer_decode_fun=TDecode,
+ transfer_decode_state=TState0}}) ->
+ %% @todo Proper trailers.
+ try TDecode(Buffer, TState0) of
+ more ->
+ {more, State#state{buffer=Buffer}};
+ {more, Data, TState} ->
+ {data, StreamID, nofin, Data, State#state{buffer= <<>>,
+ in_state=PS#ps_body{received=Received + byte_size(Data),
+ transfer_decode_state=TState}}};
+ {more, Data, _Length, TState} when is_integer(_Length) ->
+ {data, StreamID, nofin, Data, State#state{buffer= <<>>,
+ in_state=PS#ps_body{received=Received + byte_size(Data),
+ transfer_decode_state=TState}}};
+ {more, Data, Rest, TState} ->
+ {data, StreamID, nofin, Data, State#state{buffer=Rest,
+ in_state=PS#ps_body{received=Received + byte_size(Data),
+ transfer_decode_state=TState}}};
+ {done, _HasTrailers, Rest} ->
+ {data, StreamID, fin, <<>>,
+ State#state{buffer=Rest, in_streamid=StreamID + 1, in_state=#ps_request_line{}}};
+ {done, Data, _HasTrailers, Rest} ->
+ {data, StreamID, fin, Data,
+ State#state{buffer=Rest, in_streamid=StreamID + 1, in_state=#ps_request_line{}}}
+ catch _:_ ->
+ Reason = {connection_error, protocol_error,
+ 'Failure to decode the content. (RFC7230 4)'},
+ terminate(stream_terminate(State, StreamID, Reason), Reason)
+ end.
+
+%% Message handling.
+
+down(State=#state{opts=Opts, children=Children0}, Pid, Msg) ->
+ case cowboy_children:down(Children0, Pid) of
+ %% The stream was terminated already.
+ {ok, undefined, Children} ->
+ State#state{children=Children};
+ %% The stream is still running.
+ {ok, StreamID, Children} ->
+ info(State#state{children=Children}, StreamID, Msg);
+ %% The process was unknown.
+ error ->
+ cowboy:log(warning, "Received EXIT signal ~p for unknown process ~p.~n",
+ [Msg, Pid], Opts),
+ State
+ end.
+
+info(State=#state{opts=Opts, streams=Streams0}, StreamID, Msg) ->
+ case lists:keyfind(StreamID, #stream.id, Streams0) of
+ Stream = #stream{state=StreamState0} ->
+ try cowboy_stream:info(StreamID, Msg, StreamState0) of
+ {Commands, StreamState} ->
+ Streams = lists:keyreplace(StreamID, #stream.id, Streams0,
+ Stream#stream{state=StreamState}),
+ commands(State#state{streams=Streams}, StreamID, Commands)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(info,
+ [StreamID, Msg, StreamState0],
+ Class, Exception, Stacktrace), Opts),
+ stream_terminate(State, StreamID, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:info/3.'})
+ end;
+ false ->
+ cowboy:log(warning, "Received message ~p for unknown stream ~p.~n",
+ [Msg, StreamID], Opts),
+ State
+ end.
+
+%% Commands.
+
+commands(State, _, []) ->
+ State;
+%% Supervise a child process.
+commands(State=#state{children=Children}, StreamID, [{spawn, Pid, Shutdown}|Tail]) ->
+ commands(State#state{children=cowboy_children:up(Children, Pid, StreamID, Shutdown)},
+ StreamID, Tail);
+%% Error handling.
+commands(State, StreamID, [Error = {internal_error, _, _}|Tail]) ->
+ commands(stream_terminate(State, StreamID, Error), StreamID, Tail);
+%% Commands for a stream currently inactive.
+commands(State=#state{out_streamid=Current, streams=Streams0}, StreamID, Commands)
+ when Current =/= StreamID ->
+
+ %% @todo We still want to handle some commands...
+
+ Stream = #stream{queue=Queue} = lists:keyfind(StreamID, #stream.id, Streams0),
+ Streams = lists:keyreplace(StreamID, #stream.id, Streams0,
+ Stream#stream{queue=Queue ++ Commands}),
+ State#state{streams=Streams};
+%% When we have finished reading the request body, do nothing.
+commands(State=#state{flow=infinity}, StreamID, [{flow, _}|Tail]) ->
+ commands(State, StreamID, Tail);
+%% Read the request body.
+commands(State0=#state{flow=Flow0}, StreamID, [{flow, Size}|Tail]) ->
+ %% We must read *at least* Size of data otherwise functions
+ %% like cowboy_req:read_body/1,2 will wait indefinitely.
+ Flow = if
+ Flow0 < 0 -> Size;
+ true -> Flow0 + Size
+ end,
+ %% Reenable active mode if necessary.
+ State = if
+ Flow0 =< 0, Flow > 0 ->
+ active(State0);
+ true ->
+ State0
+ end,
+ commands(State#state{flow=Flow}, StreamID, Tail);
+%% Error responses are sent only if a response wasn't sent already.
+commands(State=#state{out_state=wait, out_streamid=StreamID}, StreamID,
+ [{error_response, Status, Headers0, Body}|Tail]) ->
+ %% We close the connection when the error response is 408, as it
+ %% indicates a timeout and the RFC recommends that we stop here. (RFC7231 6.5.7)
+ Headers = case Status of
+ 408 -> Headers0#{<<"connection">> => <<"close">>};
+ <<"408", _/bits>> -> Headers0#{<<"connection">> => <<"close">>};
+ _ -> Headers0
+ end,
+ commands(State, StreamID, [{response, Status, Headers, Body}|Tail]);
+commands(State, StreamID, [{error_response, _, _, _}|Tail]) ->
+ commands(State, StreamID, Tail);
+%% Send an informational response.
+commands(State=#state{socket=Socket, transport=Transport, out_state=wait, streams=Streams},
+ StreamID, [{inform, StatusCode, Headers}|Tail]) ->
+ %% @todo I'm pretty sure the last stream in the list is the one we want
+ %% considering all others are queued.
+ #stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams),
+ _ = case Version of
+ 'HTTP/1.1' ->
+ Transport:send(Socket, cow_http:response(StatusCode, 'HTTP/1.1',
+ headers_to_list(Headers)));
+ %% Do not send informational responses to HTTP/1.0 clients. (RFC7231 6.2)
+ 'HTTP/1.0' ->
+ ok
+ end,
+ commands(State, StreamID, Tail);
+%% Send a full response.
+%%
+%% @todo Kill the stream if it sent a response when one has already been sent.
+%% @todo Keep IsFin in the state.
+%% @todo Same two things above apply to DATA, possibly promise too.
+commands(State0=#state{socket=Socket, transport=Transport, out_state=wait, streams=Streams}, StreamID,
+ [{response, StatusCode, Headers0, Body}|Tail]) ->
+ %% @todo I'm pretty sure the last stream in the list is the one we want
+ %% considering all others are queued.
+ #stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams),
+ {State1, Headers} = connection(State0, Headers0, StreamID, Version),
+ State = State1#state{out_state=done},
+ %% @todo Ensure content-length is set. 204 must never have content-length set.
+ Response = cow_http:response(StatusCode, 'HTTP/1.1', headers_to_list(Headers)),
+ %% @todo 204 and 304 responses must not include a response body. (RFC7230 3.3.1, RFC7230 3.3.2)
+ case Body of
+ {sendfile, _, _, _} ->
+ Transport:send(Socket, Response),
+ sendfile(State, Body);
+ _ ->
+ Transport:send(Socket, [Response, Body])
+ end,
+ commands(State, StreamID, Tail);
+%% Send response headers and initiate chunked encoding or streaming.
+commands(State0=#state{socket=Socket, transport=Transport,
+ opts=Opts, overriden_opts=Override, streams=Streams0, out_state=OutState},
+ StreamID, [{headers, StatusCode, Headers0}|Tail]) ->
+ %% @todo Same as above (about the last stream in the list).
+ Stream = #stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams0),
+ Status = cow_http:status_to_integer(StatusCode),
+ ContentLength = maps:get(<<"content-length">>, Headers0, undefined),
+ %% Chunked transfer-encoding can be disabled on a per-request basis.
+ Chunked = case Override of
+ #{chunked := Chunked0} -> Chunked0;
+ _ -> maps:get(chunked, Opts, true)
+ end,
+ {State1, Headers1} = case {Status, ContentLength, Version} of
+ {204, _, 'HTTP/1.1'} ->
+ {State0#state{out_state=done}, Headers0};
+ {304, _, 'HTTP/1.1'} ->
+ {State0#state{out_state=done}, Headers0};
+ {_, undefined, 'HTTP/1.1'} when Chunked ->
+ {State0#state{out_state=chunked}, Headers0#{<<"transfer-encoding">> => <<"chunked">>}};
+ %% Close the connection after streaming without content-length
+ %% to all HTTP/1.0 clients and to HTTP/1.1 clients when chunked is disabled.
+ {_, undefined, _} ->
+ {State0#state{out_state=streaming, last_streamid=StreamID}, Headers0};
+ %% Stream the response body without chunked transfer-encoding.
+ _ ->
+ ExpectedSize = cow_http_hd:parse_content_length(ContentLength),
+ Streams = lists:keyreplace(StreamID, #stream.id, Streams0,
+ Stream#stream{local_expected_size=ExpectedSize}),
+ {State0#state{out_state=streaming, streams=Streams}, Headers0}
+ end,
+ Headers2 = case stream_te(OutState, Stream) of
+ trailers -> Headers1;
+ _ -> maps:remove(<<"trailer">>, Headers1)
+ end,
+ {State, Headers} = connection(State1, Headers2, StreamID, Version),
+ Transport:send(Socket, cow_http:response(StatusCode, 'HTTP/1.1', headers_to_list(Headers))),
+ commands(State, StreamID, Tail);
+%% Send a response body chunk.
+%% @todo We need to kill the stream if it tries to send data before headers.
+commands(State0=#state{socket=Socket, transport=Transport, streams=Streams0, out_state=OutState},
+ StreamID, [{data, IsFin, Data}|Tail]) ->
+ %% Do not send anything when the user asks to send an empty
+ %% data frame, as that would break the protocol.
+ Size = case Data of
+ {sendfile, _, B, _} -> B;
+ _ -> iolist_size(Data)
+ end,
+ %% Depending on the current state we may need to send nothing,
+ %% the last chunk, chunked data with/without the last chunk,
+ %% or just the data as-is.
+ Stream = case lists:keyfind(StreamID, #stream.id, Streams0) of
+ Stream0=#stream{method= <<"HEAD">>} ->
+ Stream0;
+ Stream0 when Size =:= 0, IsFin =:= fin, OutState =:= chunked ->
+ Transport:send(Socket, <<"0\r\n\r\n">>),
+ Stream0;
+ Stream0 when Size =:= 0 ->
+ Stream0;
+ Stream0 when is_tuple(Data), OutState =:= chunked ->
+ Transport:send(Socket, [integer_to_binary(Size, 16), <<"\r\n">>]),
+ sendfile(State0, Data),
+ Transport:send(Socket,
+ case IsFin of
+ fin -> <<"\r\n0\r\n\r\n">>;
+ nofin -> <<"\r\n">>
+ end),
+ Stream0;
+ Stream0 when OutState =:= chunked ->
+ Transport:send(Socket, [
+ integer_to_binary(Size, 16), <<"\r\n">>, Data,
+ case IsFin of
+ fin -> <<"\r\n0\r\n\r\n">>;
+ nofin -> <<"\r\n">>
+ end
+ ]),
+ Stream0;
+ Stream0 when OutState =:= streaming ->
+ #stream{local_sent_size=SentSize0, local_expected_size=ExpectedSize} = Stream0,
+ SentSize = SentSize0 + Size,
+ if
+ %% ExpectedSize may be undefined, which is > any integer value.
+ SentSize > ExpectedSize ->
+ terminate(State0, response_body_too_large);
+ is_tuple(Data) ->
+ sendfile(State0, Data);
+ true ->
+ Transport:send(Socket, Data)
+ end,
+ Stream0#stream{local_sent_size=SentSize}
+ end,
+ State = case IsFin of
+ fin -> State0#state{out_state=done};
+ nofin -> State0
+ end,
+ Streams = lists:keyreplace(StreamID, #stream.id, Streams0, Stream),
+ commands(State#state{streams=Streams}, StreamID, Tail);
+commands(State=#state{socket=Socket, transport=Transport, streams=Streams, out_state=OutState},
+ StreamID, [{trailers, Trailers}|Tail]) ->
+ case stream_te(OutState, lists:keyfind(StreamID, #stream.id, Streams)) of
+ trailers ->
+ Transport:send(Socket, [
+ <<"0\r\n">>,
+ cow_http:headers(maps:to_list(Trailers)),
+ <<"\r\n">>
+ ]);
+ no_trailers ->
+ Transport:send(Socket, <<"0\r\n\r\n">>);
+ not_chunked ->
+ ok
+ end,
+ commands(State#state{out_state=done}, StreamID, Tail);
+%% Protocol takeover.
+commands(State0=#state{ref=Ref, parent=Parent, socket=Socket, transport=Transport,
+ out_state=OutState, opts=Opts, buffer=Buffer, children=Children}, StreamID,
+ [{switch_protocol, Headers, Protocol, InitialState}|_Tail]) ->
+ %% @todo If there's streams opened after this one, fail instead of 101.
+ State1 = cancel_timeout(State0),
+ %% Before we send the 101 response we need to stop receiving data
+ %% from the socket, otherwise the data might be receive before the
+ %% call to flush/0 and we end up inadvertently dropping a packet.
+ %%
+ %% @todo Handle cases where the request came with a body. We need
+ %% to process or skip the body before the upgrade can be completed.
+ State = passive(State1),
+ %% Send a 101 response if necessary, then terminate the stream.
+ #state{streams=Streams} = case OutState of
+ wait -> info(State, StreamID, {inform, 101, Headers});
+ _ -> State
+ end,
+ #stream{state=StreamState} = lists:keyfind(StreamID, #stream.id, Streams),
+ %% @todo We need to shutdown processes here first.
+ stream_call_terminate(StreamID, switch_protocol, StreamState, State),
+ %% Terminate children processes and flush any remaining messages from the mailbox.
+ cowboy_children:terminate(Children),
+ flush(Parent),
+ Protocol:takeover(Parent, Ref, Socket, Transport, Opts, Buffer, InitialState);
+%% Set options dynamically.
+commands(State0=#state{overriden_opts=Opts},
+ StreamID, [{set_options, SetOpts}|Tail]) ->
+ State1 = case SetOpts of
+ #{idle_timeout := IdleTimeout} ->
+ set_timeout(State0#state{overriden_opts=Opts#{idle_timeout => IdleTimeout}},
+ idle_timeout);
+ _ ->
+ State0
+ end,
+ State = case SetOpts of
+ #{chunked := Chunked} ->
+ State1#state{overriden_opts=Opts#{chunked => Chunked}};
+ _ ->
+ State1
+ end,
+ commands(State, StreamID, Tail);
+%% Stream shutdown.
+commands(State, StreamID, [stop|Tail]) ->
+ %% @todo Do we want to run the commands after a stop?
+ %% @todo We currently wait for the stop command before we
+ %% continue with the next request/response. In theory, if
+ %% the request body was read fully and the response body
+ %% was sent fully we should be able to start working on
+ %% the next request concurrently. This can be done as a
+ %% future optimization.
+ maybe_terminate(State, StreamID, Tail);
+%% Log event.
+commands(State=#state{opts=Opts}, StreamID, [Log={log, _, _, _}|Tail]) ->
+ cowboy:log(Log, Opts),
+ commands(State, StreamID, Tail);
+%% HTTP/1.1 does not support push; ignore.
+commands(State, StreamID, [{push, _, _, _, _, _, _, _}|Tail]) ->
+ commands(State, StreamID, Tail).
+
+%% The set-cookie header is special; we can only send one cookie per header.
+headers_to_list(Headers0=#{<<"set-cookie">> := SetCookies}) ->
+ Headers1 = maps:to_list(maps:remove(<<"set-cookie">>, Headers0)),
+ Headers1 ++ [{<<"set-cookie">>, Value} || Value <- SetCookies];
+headers_to_list(Headers) ->
+ maps:to_list(Headers).
+
+%% We wrap the sendfile call into a try/catch because on OTP-20
+%% and earlier a few different crashes could occur for sockets
+%% that were closing or closed. For example a badarg in
+%% erlang:port_get_data(#Port<...>) or a badmatch like
+%% {{badmatch,{error,einval}},[{prim_file,sendfile,8,[]}...
+%%
+%% OTP-21 uses a NIF instead of a port so the implementation
+%% and behavior has dramatically changed and it is unclear
+%% whether it will be necessary in the future.
+%%
+%% This try/catch prevents some noisy logs to be written
+%% when these errors occur.
+sendfile(State=#state{socket=Socket, transport=Transport, opts=Opts},
+ {sendfile, Offset, Bytes, Path}) ->
+ try
+ %% When sendfile is disabled we explicitly use the fallback.
+ _ = case maps:get(sendfile, Opts, true) of
+ true -> Transport:sendfile(Socket, Path, Offset, Bytes);
+ false -> ranch_transport:sendfile(Transport, Socket, Path, Offset, Bytes, [])
+ end,
+ ok
+ catch _:_ ->
+ terminate(State, {socket_error, sendfile_crash,
+ 'An error occurred when using the sendfile function.'})
+ end.
+
+%% Flush messages specific to cowboy_http before handing over the
+%% connection to another protocol.
+flush(Parent) ->
+ receive
+ {timeout, _, _} ->
+ flush(Parent);
+ {{Pid, _}, _} when Pid =:= self() ->
+ flush(Parent);
+ {'EXIT', Pid, _} when Pid =/= Parent ->
+ flush(Parent)
+ after 0 ->
+ ok
+ end.
+
+%% @todo In these cases I'm not sure if we should continue processing commands.
+maybe_terminate(State=#state{last_streamid=StreamID}, StreamID, _Tail) ->
+ terminate(stream_terminate(State, StreamID, normal), normal); %% @todo Reason ok?
+maybe_terminate(State, StreamID, _Tail) ->
+ stream_terminate(State, StreamID, normal).
+
+stream_terminate(State0=#state{opts=Opts, in_streamid=InStreamID, in_state=InState,
+ out_streamid=OutStreamID, out_state=OutState, streams=Streams0,
+ children=Children0}, StreamID, Reason) ->
+ #stream{version=Version, local_expected_size=ExpectedSize, local_sent_size=SentSize}
+ = lists:keyfind(StreamID, #stream.id, Streams0),
+ %% Send a response or terminate chunks depending on the current output state.
+ State1 = #state{streams=Streams1} = case OutState of
+ wait when element(1, Reason) =:= internal_error ->
+ info(State0, StreamID, {response, 500, #{<<"content-length">> => <<"0">>}, <<>>});
+ wait when element(1, Reason) =:= connection_error ->
+ info(State0, StreamID, {response, 400, #{<<"content-length">> => <<"0">>}, <<>>});
+ wait ->
+ info(State0, StreamID, {response, 204, #{}, <<>>});
+ chunked when Version =:= 'HTTP/1.1' ->
+ info(State0, StreamID, {data, fin, <<>>});
+ streaming when SentSize < ExpectedSize ->
+ terminate(State0, response_body_too_small);
+ _ -> %% done or Version =:= 'HTTP/1.0'
+ State0
+ end,
+ %% Stop the stream, shutdown children and reset overriden options.
+ {value, #stream{state=StreamState}, Streams}
+ = lists:keytake(StreamID, #stream.id, Streams1),
+ stream_call_terminate(StreamID, Reason, StreamState, State1),
+ Children = cowboy_children:shutdown(Children0, StreamID),
+ State = State1#state{overriden_opts=#{}, streams=Streams, children=Children},
+ %% We want to drop the connection if the body was not read fully
+ %% and we don't know its length or more remains to be read than
+ %% configuration allows.
+ MaxSkipBodyLength = maps:get(max_skip_body_length, Opts, 1000000),
+ case InState of
+ #ps_body{length=undefined}
+ when InStreamID =:= OutStreamID ->
+ terminate(State, skip_body_unknown_length);
+ #ps_body{length=Len, received=Received}
+ when InStreamID =:= OutStreamID, Received + MaxSkipBodyLength < Len ->
+ terminate(State, skip_body_too_large);
+ #ps_body{} when InStreamID =:= OutStreamID ->
+ stream_next(State#state{flow=infinity});
+ _ ->
+ stream_next(State)
+ end.
+
+stream_next(State0=#state{opts=Opts, active=Active, out_streamid=OutStreamID, streams=Streams}) ->
+ NextOutStreamID = OutStreamID + 1,
+ case lists:keyfind(NextOutStreamID, #stream.id, Streams) of
+ false ->
+ State0#state{out_streamid=NextOutStreamID, out_state=wait};
+ #stream{queue=Commands} ->
+ State = case Active of
+ true -> State0;
+ false -> active(State0)
+ end,
+ %% @todo Remove queue from the stream.
+ %% We set the flow to the initial flow size even though
+ %% we might have sent some data through already due to pipelining.
+ Flow = maps:get(initial_stream_flow_size, Opts, 65535),
+ commands(State#state{flow=Flow, out_streamid=NextOutStreamID, out_state=wait},
+ NextOutStreamID, Commands)
+ end.
+
+stream_call_terminate(StreamID, Reason, StreamState, #state{opts=Opts}) ->
+ try
+ cowboy_stream:terminate(StreamID, Reason, StreamState)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(terminate,
+ [StreamID, Reason, StreamState],
+ Class, Exception, Stacktrace), Opts)
+ end.
+
+maybe_req_close(#state{opts=#{http10_keepalive := false}}, _, 'HTTP/1.0') ->
+ close;
+maybe_req_close(_, #{<<"connection">> := Conn}, 'HTTP/1.0') ->
+ Conns = cow_http_hd:parse_connection(Conn),
+ case lists:member(<<"keep-alive">>, Conns) of
+ true -> keepalive;
+ false -> close
+ end;
+maybe_req_close(_, _, 'HTTP/1.0') ->
+ close;
+maybe_req_close(_, #{<<"connection">> := Conn}, 'HTTP/1.1') ->
+ case connection_hd_is_close(Conn) of
+ true -> close;
+ false -> keepalive
+ end;
+maybe_req_close(_, _, _) ->
+ keepalive.
+
+connection(State=#state{last_streamid=StreamID}, Headers=#{<<"connection">> := Conn}, StreamID, _) ->
+ case connection_hd_is_close(Conn) of
+ true -> {State, Headers};
+ %% @todo Here we need to remove keep-alive and add close, not just add close.
+ false -> {State, Headers#{<<"connection">> => [<<"close, ">>, Conn]}}
+ end;
+connection(State=#state{last_streamid=StreamID}, Headers, StreamID, _) ->
+ {State, Headers#{<<"connection">> => <<"close">>}};
+connection(State, Headers=#{<<"connection">> := Conn}, StreamID, _) ->
+ case connection_hd_is_close(Conn) of
+ true -> {State#state{last_streamid=StreamID}, Headers};
+ %% @todo Here we need to set keep-alive only if it wasn't set before.
+ false -> {State, Headers}
+ end;
+connection(State, Headers, _, 'HTTP/1.0') ->
+ {State, Headers#{<<"connection">> => <<"keep-alive">>}};
+connection(State, Headers, _, _) ->
+ {State, Headers}.
+
+connection_hd_is_close(Conn) ->
+ Conns = cow_http_hd:parse_connection(iolist_to_binary(Conn)),
+ lists:member(<<"close">>, Conns).
+
+stream_te(streaming, _) ->
+ not_chunked;
+%% No TE header was sent.
+stream_te(_, #stream{te=undefined}) ->
+ no_trailers;
+stream_te(_, #stream{te=TE0}) ->
+ try cow_http_hd:parse_te(TE0) of
+ {TE1, _} -> TE1
+ catch _:_ ->
+ %% If we can't parse the TE header, assume we can't send trailers.
+ no_trailers
+ end.
+
+%% This function is only called when an error occurs on a new stream.
+-spec error_terminate(cowboy:http_status(), #state{}, _) -> no_return().
+error_terminate(StatusCode, State=#state{ref=Ref, peer=Peer, in_state=StreamState}, Reason) ->
+ PartialReq = case StreamState of
+ #ps_request_line{} -> #{
+ ref => Ref,
+ peer => Peer
+ };
+ #ps_header{method=Method, path=Path, qs=Qs,
+ version=Version, headers=ReqHeaders} -> #{
+ ref => Ref,
+ peer => Peer,
+ method => Method,
+ path => Path,
+ qs => Qs,
+ version => Version,
+ headers => case ReqHeaders of
+ undefined -> #{};
+ _ -> ReqHeaders
+ end
+ }
+ end,
+ early_error(StatusCode, State, Reason, PartialReq, #{<<"connection">> => <<"close">>}),
+ terminate(State, Reason).
+
+early_error(StatusCode, State, Reason, PartialReq) ->
+ early_error(StatusCode, State, Reason, PartialReq, #{}).
+
+early_error(StatusCode0, #state{socket=Socket, transport=Transport,
+ opts=Opts, in_streamid=StreamID}, Reason, PartialReq, RespHeaders0) ->
+ RespHeaders1 = RespHeaders0#{<<"content-length">> => <<"0">>},
+ Resp = {response, StatusCode0, RespHeaders1, <<>>},
+ try cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts) of
+ {response, StatusCode, RespHeaders, RespBody} ->
+ Transport:send(Socket, [
+ cow_http:response(StatusCode, 'HTTP/1.1', maps:to_list(RespHeaders)),
+ %% @todo We shouldn't send the body when the method is HEAD.
+ %% @todo Technically we allow the sendfile tuple.
+ RespBody
+ ])
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(early_error,
+ [StreamID, Reason, PartialReq, Resp, Opts],
+ Class, Exception, Stacktrace), Opts),
+ %% We still need to send an error response, so send what we initially
+ %% wanted to send. It's better than nothing.
+ Transport:send(Socket, cow_http:response(StatusCode0,
+ 'HTTP/1.1', maps:to_list(RespHeaders1)))
+ end,
+ ok.
+
+initiate_closing(State=#state{streams=[]}, Reason) ->
+ terminate(State, Reason);
+initiate_closing(State=#state{streams=[_Stream|Streams],
+ out_streamid=OutStreamID}, Reason) ->
+ terminate_all_streams(State, Streams, Reason),
+ State#state{last_streamid=OutStreamID}.
+
+-spec terminate(_, _) -> no_return().
+terminate(undefined, Reason) ->
+ exit({shutdown, Reason});
+terminate(State=#state{streams=Streams, children=Children}, Reason) ->
+ terminate_all_streams(State, Streams, Reason),
+ cowboy_children:terminate(Children),
+ terminate_linger(State),
+ exit({shutdown, Reason}).
+
+terminate_all_streams(_, [], _) ->
+ ok;
+terminate_all_streams(State, [#stream{id=StreamID, state=StreamState}|Tail], Reason) ->
+ stream_call_terminate(StreamID, Reason, StreamState, State),
+ terminate_all_streams(State, Tail, Reason).
+
+terminate_linger(State=#state{socket=Socket, transport=Transport, opts=Opts}) ->
+ case Transport:shutdown(Socket, write) of
+ ok ->
+ case maps:get(linger_timeout, Opts, 1000) of
+ 0 ->
+ ok;
+ infinity ->
+ terminate_linger_before_loop(State, undefined, Transport:messages());
+ Timeout ->
+ TimerRef = erlang:start_timer(Timeout, self(), linger_timeout),
+ terminate_linger_before_loop(State, TimerRef, Transport:messages())
+ end;
+ {error, _} ->
+ ok
+ end.
+
+terminate_linger_before_loop(State, TimerRef, Messages) ->
+ %% We may already be in active mode when we do this
+ %% but it's OK because we are shutting down anyway.
+ case setopts_active(State) of
+ ok ->
+ terminate_linger_loop(State, TimerRef, Messages);
+ {error, _} ->
+ ok
+ end.
+
+terminate_linger_loop(State=#state{socket=Socket}, TimerRef, Messages) ->
+ receive
+ {OK, Socket, _} when OK =:= element(1, Messages) ->
+ terminate_linger_loop(State, TimerRef, Messages);
+ {Closed, Socket} when Closed =:= element(2, Messages) ->
+ ok;
+ {Error, Socket, _} when Error =:= element(3, Messages) ->
+ ok;
+ {Passive, Socket} when Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ terminate_linger_before_loop(State, TimerRef, Messages);
+ {timeout, TimerRef, linger_timeout} ->
+ ok;
+ _ ->
+ terminate_linger_loop(State, TimerRef, Messages)
+ end.
+
+%% System callbacks.
+
+-spec system_continue(_, _, #state{}) -> ok.
+system_continue(_, _, State) ->
+ loop(State).
+
+-spec system_terminate(any(), _, _, #state{}) -> no_return().
+system_terminate(Reason0, _, _, State) ->
+ Reason = {stop, {exit, Reason0}, 'sys:terminate/2,3 was called.'},
+ loop(initiate_closing(State, Reason)).
+
+-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::{#state{}, binary()}.
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_http2.erl b/server/_build/default/lib/cowboy/src/cowboy_http2.erl
new file mode 100644
index 0000000..7440d91
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_http2.erl
@@ -0,0 +1,1225 @@
+%% Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_http2).
+
+-export([init/6]).
+-export([init/10]).
+-export([init/12]).
+
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-type opts() :: #{
+ active_n => pos_integer(),
+ compress_buffering => boolean(),
+ compress_threshold => non_neg_integer(),
+ connection_type => worker | supervisor,
+ connection_window_margin_size => 0..16#7fffffff,
+ connection_window_update_threshold => 0..16#7fffffff,
+ enable_connect_protocol => boolean(),
+ env => cowboy_middleware:env(),
+ goaway_initial_timeout => timeout(),
+ goaway_complete_timeout => timeout(),
+ idle_timeout => timeout(),
+ inactivity_timeout => timeout(),
+ initial_connection_window_size => 65535..16#7fffffff,
+ initial_stream_window_size => 0..16#7fffffff,
+ linger_timeout => timeout(),
+ logger => module(),
+ max_concurrent_streams => non_neg_integer() | infinity,
+ max_connection_buffer_size => non_neg_integer(),
+ max_connection_window_size => 0..16#7fffffff,
+ max_decode_table_size => non_neg_integer(),
+ max_encode_table_size => non_neg_integer(),
+ max_frame_size_received => 16384..16777215,
+ max_frame_size_sent => 16384..16777215 | infinity,
+ max_received_frame_rate => {pos_integer(), timeout()},
+ max_reset_stream_rate => {pos_integer(), timeout()},
+ max_stream_buffer_size => non_neg_integer(),
+ max_stream_window_size => 0..16#7fffffff,
+ metrics_callback => cowboy_metrics_h:metrics_callback(),
+ metrics_req_filter => fun((cowboy_req:req()) -> map()),
+ metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),
+ middlewares => [module()],
+ preface_timeout => timeout(),
+ proxy_header => boolean(),
+ sendfile => boolean(),
+ settings_timeout => timeout(),
+ shutdown_timeout => timeout(),
+ stream_handlers => [module()],
+ stream_window_data_threshold => 0..16#7fffffff,
+ stream_window_margin_size => 0..16#7fffffff,
+ stream_window_update_threshold => 0..16#7fffffff,
+ tracer_callback => cowboy_tracer_h:tracer_callback(),
+ tracer_flags => [atom()],
+ tracer_match_specs => cowboy_tracer_h:tracer_match_specs(),
+ %% Open ended because configured stream handlers might add options.
+ _ => _
+}.
+-export_type([opts/0]).
+
+-record(stream, {
+ %% Whether the stream is currently stopping.
+ status = running :: running | stopping,
+
+ %% Flow requested for this stream.
+ flow = 0 :: non_neg_integer(),
+
+ %% Stream state.
+ state :: {module, any()}
+}).
+
+-record(state, {
+ parent = undefined :: pid(),
+ ref :: ranch:ref(),
+ socket = undefined :: inet:socket(),
+ transport :: module(),
+ proxy_header :: undefined | ranch_proxy_header:proxy_info(),
+ opts = #{} :: opts(),
+
+ %% Timer for idle_timeout; also used for goaway timers.
+ timer = undefined :: undefined | reference(),
+
+ %% Remote address and port for the connection.
+ peer = undefined :: {inet:ip_address(), inet:port_number()},
+
+ %% Local address and port for the connection.
+ sock = undefined :: {inet:ip_address(), inet:port_number()},
+
+ %% Client certificate (TLS only).
+ cert :: undefined | binary(),
+
+ %% HTTP/2 state machine.
+ http2_status :: sequence | settings | upgrade | connected | closing_initiated | closing,
+ http2_machine :: cow_http2_machine:http2_machine(),
+
+ %% HTTP/2 frame rate flood protection.
+ frame_rate_num :: undefined | pos_integer(),
+ frame_rate_time :: undefined | integer(),
+
+ %% HTTP/2 reset stream flood protection.
+ reset_rate_num :: undefined | pos_integer(),
+ reset_rate_time :: undefined | integer(),
+
+ %% Flow requested for all streams.
+ flow = 0 :: non_neg_integer(),
+
+ %% 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{}},
+
+ %% Streams can spawn zero or more children which are then managed
+ %% by this module if operating as a supervisor.
+ children = cowboy_children:init() :: cowboy_children:children()
+}).
+
+-spec init(pid(), ranch:ref(), inet:socket(), module(),
+ ranch_proxy_header:proxy_info() | undefined, cowboy:opts()) -> ok.
+init(Parent, Ref, Socket, Transport, ProxyHeader, Opts) ->
+ Peer0 = Transport:peername(Socket),
+ Sock0 = Transport:sockname(Socket),
+ Cert1 = case Transport:name() of
+ ssl ->
+ case ssl:peercert(Socket) of
+ {error, no_peercert} ->
+ {ok, undefined};
+ Cert0 ->
+ Cert0
+ end;
+ _ ->
+ {ok, undefined}
+ end,
+ case {Peer0, Sock0, Cert1} of
+ {{ok, Peer}, {ok, Sock}, {ok, Cert}} ->
+ init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, <<>>);
+ {{error, Reason}, _, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the peer name.'});
+ {_, {error, Reason}, _} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the sock name.'});
+ {_, _, {error, Reason}} ->
+ terminate(undefined, {socket_error, Reason,
+ 'A socket error occurred when retrieving the client TLS certificate.'})
+ end.
+
+-spec init(pid(), ranch:ref(), inet:socket(), module(),
+ ranch_proxy_header:proxy_info() | undefined, cowboy:opts(),
+ {inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
+ binary() | undefined, binary()) -> ok.
+init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer) ->
+ {ok, Preface, HTTP2Machine} = cow_http2_machine:init(server, Opts),
+ State = set_idle_timeout(init_rate_limiting(#state{parent=Parent, ref=Ref, socket=Socket,
+ transport=Transport, proxy_header=ProxyHeader,
+ opts=Opts, peer=Peer, sock=Sock, cert=Cert,
+ http2_status=sequence, http2_machine=HTTP2Machine})),
+ Transport:send(Socket, Preface),
+ setopts_active(State),
+ case Buffer of
+ <<>> -> loop(State, Buffer);
+ _ -> parse(State, Buffer)
+ end.
+
+init_rate_limiting(State) ->
+ CurrentTime = erlang:monotonic_time(millisecond),
+ init_reset_rate_limiting(init_frame_rate_limiting(State, CurrentTime), CurrentTime).
+
+init_frame_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
+ {FrameRateNum, FrameRatePeriod} = maps:get(max_received_frame_rate, Opts, {10000, 10000}),
+ State#state{
+ frame_rate_num=FrameRateNum, frame_rate_time=add_period(CurrentTime, FrameRatePeriod)
+ }.
+
+init_reset_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->
+ {ResetRateNum, ResetRatePeriod} = maps:get(max_reset_stream_rate, Opts, {10, 10000}),
+ State#state{
+ reset_rate_num=ResetRateNum, reset_rate_time=add_period(CurrentTime, ResetRatePeriod)
+ }.
+
+add_period(_, infinity) -> infinity;
+add_period(Time, Period) -> Time + Period.
+
+%% @todo Add an argument for the request body.
+-spec init(pid(), ranch:ref(), inet:socket(), module(),
+ ranch_proxy_header:proxy_info() | undefined, cowboy:opts(),
+ {inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},
+ binary() | undefined, binary(), map() | undefined, cowboy_req:req()) -> ok.
+init(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer,
+ _Settings, Req=#{method := Method}) ->
+ {ok, Preface, HTTP2Machine0} = cow_http2_machine:init(server, Opts),
+ {ok, StreamID, HTTP2Machine}
+ = cow_http2_machine:init_upgrade_stream(Method, HTTP2Machine0),
+ State0 = #state{parent=Parent, ref=Ref, socket=Socket,
+ transport=Transport, proxy_header=ProxyHeader,
+ opts=Opts, peer=Peer, sock=Sock, cert=Cert,
+ http2_status=upgrade, http2_machine=HTTP2Machine},
+ State1 = headers_frame(State0#state{
+ http2_machine=HTTP2Machine}, StreamID, Req),
+ %% We assume that the upgrade will be applied. A stream handler
+ %% must not prevent the normal operations of the server.
+ State2 = info(State1, 1, {switch_protocol, #{
+ <<"connection">> => <<"Upgrade">>,
+ <<"upgrade">> => <<"h2c">>
+ }, ?MODULE, undefined}), %% @todo undefined or #{}?
+ State = set_idle_timeout(init_rate_limiting(State2#state{http2_status=sequence})),
+ Transport:send(Socket, Preface),
+ setopts_active(State),
+ case Buffer of
+ <<>> -> loop(State, Buffer);
+ _ -> parse(State, Buffer)
+ end.
+
+%% Because HTTP/2 has flow control and Cowboy has other rate limiting
+%% mechanisms implemented, a very large active_n value should be fine,
+%% as long as the stream handlers do their work in a timely manner.
+setopts_active(#state{socket=Socket, transport=Transport, opts=Opts}) ->
+ N = maps:get(active_n, Opts, 100),
+ Transport:setopts(Socket, [{active, N}]).
+
+loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
+ opts=Opts, timer=TimerRef, children=Children}, Buffer) ->
+ Messages = Transport:messages(),
+ InactivityTimeout = maps:get(inactivity_timeout, Opts, 300000),
+ receive
+ %% Socket messages.
+ {OK, Socket, Data} when OK =:= element(1, Messages) ->
+ parse(set_idle_timeout(State), << Buffer/binary, Data/binary >>);
+ {Closed, Socket} when Closed =:= element(2, Messages) ->
+ Reason = case State#state.http2_status of
+ closing -> {stop, closed, 'The client is going away.'};
+ _ -> {socket_error, closed, 'The socket has been closed.'}
+ end,
+ terminate(State, Reason);
+ {Error, Socket, Reason} when Error =:= element(3, Messages) ->
+ terminate(State, {socket_error, Reason, 'An error has occurred on the socket.'});
+ {Passive, Socket} when Passive =:= element(4, Messages);
+ %% Hardcoded for compatibility with Ranch 1.x.
+ Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ setopts_active(State),
+ loop(State, Buffer);
+ %% System messages.
+ {'EXIT', Parent, shutdown} ->
+ Reason = {stop, {exit, shutdown}, 'Parent process requested shutdown.'},
+ loop(initiate_closing(State, Reason), Buffer);
+ {'EXIT', Parent, Reason} ->
+ terminate(State, {stop, {exit, Reason}, 'Parent process terminated.'});
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {State, Buffer});
+ %% Timeouts.
+ {timeout, TimerRef, idle_timeout} ->
+ terminate(State, {stop, timeout,
+ 'Connection idle longer than configuration allows.'});
+ {timeout, Ref, {shutdown, Pid}} ->
+ cowboy_children:shutdown_timeout(Children, Ref, Pid),
+ loop(State, Buffer);
+ {timeout, TRef, {cow_http2_machine, Name}} ->
+ loop(timeout(State, Name, TRef), Buffer);
+ {timeout, TimerRef, {goaway_initial_timeout, Reason}} ->
+ loop(closing(State, Reason), Buffer);
+ {timeout, TimerRef, {goaway_complete_timeout, Reason}} ->
+ terminate(State, {stop, stop_reason(Reason),
+ 'Graceful shutdown timed out.'});
+ %% Messages pertaining to a stream.
+ {{Pid, StreamID}, Msg} when Pid =:= self() ->
+ loop(info(State, StreamID, Msg), Buffer);
+ %% Exit signal from children.
+ Msg = {'EXIT', Pid, _} ->
+ loop(down(State, Pid, Msg), Buffer);
+ %% Calls from supervisor module.
+ {'$gen_call', From, Call} ->
+ cowboy_children:handle_supervisor_call(Call, From, Children, ?MODULE),
+ loop(State, Buffer);
+ Msg ->
+ cowboy:log(warning, "Received stray message ~p.", [Msg], Opts),
+ loop(State, Buffer)
+ after InactivityTimeout ->
+ terminate(State, {internal_error, timeout, 'No message or data received before timeout.'})
+ end.
+
+set_idle_timeout(State=#state{http2_status=Status, timer=TimerRef})
+ when Status =:= closing_initiated orelse Status =:= closing,
+ TimerRef =/= undefined ->
+ State;
+set_idle_timeout(State=#state{opts=Opts}) ->
+ set_timeout(State, maps:get(idle_timeout, Opts, 60000), idle_timeout).
+
+set_timeout(State=#state{timer=TimerRef0}, Timeout, Message) ->
+ ok = case TimerRef0 of
+ undefined -> ok;
+ _ -> erlang:cancel_timer(TimerRef0, [{async, true}, {info, false}])
+ end,
+ TimerRef = case Timeout of
+ infinity -> undefined;
+ Timeout -> erlang:start_timer(Timeout, self(), Message)
+ end,
+ State#state{timer=TimerRef}.
+
+%% HTTP/2 protocol parsing.
+
+parse(State=#state{http2_status=sequence}, Data) ->
+ case cow_http2:parse_sequence(Data) of
+ {ok, Rest} ->
+ parse(State#state{http2_status=settings}, Rest);
+ more ->
+ loop(State, Data);
+ Error = {connection_error, _, _} ->
+ terminate(State, Error)
+ end;
+parse(State=#state{http2_status=Status, http2_machine=HTTP2Machine, streams=Streams}, Data) ->
+ MaxFrameSize = cow_http2_machine:get_local_setting(max_frame_size, HTTP2Machine),
+ case cow_http2:parse(Data, MaxFrameSize) of
+ {ok, Frame, Rest} ->
+ parse(frame_rate(State, Frame), Rest);
+ {ignore, Rest} ->
+ parse(frame_rate(State, ignore), Rest);
+ {stream_error, StreamID, Reason, Human, Rest} ->
+ parse(reset_stream(State, StreamID, {stream_error, Reason, Human}), Rest);
+ Error = {connection_error, _, _} ->
+ terminate(State, Error);
+ %% Terminate the connection if we are closing and all streams have completed.
+ more when Status =:= closing, Streams =:= #{} ->
+ terminate(State, {stop, normal, 'The connection is going away.'});
+ more ->
+ loop(State, Data)
+ end.
+
+%% Frame rate flood protection.
+
+frame_rate(State0=#state{frame_rate_num=Num0, frame_rate_time=Time}, Frame) ->
+ {Result, State} = case Num0 - 1 of
+ 0 ->
+ CurrentTime = erlang:monotonic_time(millisecond),
+ if
+ CurrentTime < Time ->
+ {error, State0};
+ true ->
+ %% When the option has a period of infinity we cannot reach this clause.
+ {ok, init_frame_rate_limiting(State0, CurrentTime)}
+ end;
+ Num ->
+ {ok, State0#state{frame_rate_num=Num}}
+ end,
+ case {Result, Frame} of
+ {ok, ignore} -> ignored_frame(State);
+ {ok, _} -> frame(State, Frame);
+ {error, _} -> terminate(State, {connection_error, enhance_your_calm,
+ 'Frame rate larger than configuration allows. Flood? (CVE-2019-9512, CVE-2019-9515, CVE-2019-9518)'})
+ end.
+
+%% Frames received.
+
+%% We do nothing when receiving a lingering DATA frame.
+%% We already removed the stream flow from the connection
+%% flow and are therefore already accounting for the window
+%% being reduced by these frames.
+frame(State=#state{http2_machine=HTTP2Machine0}, Frame) ->
+ case cow_http2_machine:frame(Frame, HTTP2Machine0) of
+ {ok, HTTP2Machine} ->
+ maybe_ack(State#state{http2_machine=HTTP2Machine}, Frame);
+ {ok, {data, StreamID, IsFin, Data}, HTTP2Machine} ->
+ data_frame(State#state{http2_machine=HTTP2Machine}, StreamID, IsFin, Data);
+ {ok, {headers, StreamID, IsFin, Headers, PseudoHeaders, BodyLen}, HTTP2Machine} ->
+ headers_frame(State#state{http2_machine=HTTP2Machine},
+ StreamID, IsFin, Headers, PseudoHeaders, BodyLen);
+ {ok, {trailers, _StreamID, _Trailers}, HTTP2Machine} ->
+ %% @todo Propagate trailers.
+ State#state{http2_machine=HTTP2Machine};
+ {ok, {rst_stream, StreamID, Reason}, HTTP2Machine} ->
+ rst_stream_frame(State#state{http2_machine=HTTP2Machine}, StreamID, Reason);
+ {ok, GoAway={goaway, _, _, _}, HTTP2Machine} ->
+ goaway(State#state{http2_machine=HTTP2Machine}, GoAway);
+ {send, SendData, HTTP2Machine} ->
+ %% We may need to send an alarm for each of the streams sending data.
+ lists:foldl(
+ fun({StreamID, _, _}, S) -> maybe_send_data_alarm(S, HTTP2Machine0, StreamID) end,
+ send_data(maybe_ack(State#state{http2_machine=HTTP2Machine}, Frame), SendData, []),
+ SendData);
+ {error, {stream_error, StreamID, Reason, Human}, HTTP2Machine} ->
+ reset_stream(State#state{http2_machine=HTTP2Machine},
+ StreamID, {stream_error, Reason, Human});
+ {error, Error={connection_error, _, _}, HTTP2Machine} ->
+ terminate(State#state{http2_machine=HTTP2Machine}, Error)
+ end.
+
+%% We use this opportunity to mark the HTTP/2 status as connected
+%% if we were still waiting for a SETTINGS frame.
+maybe_ack(State=#state{http2_status=settings}, Frame) ->
+ maybe_ack(State#state{http2_status=connected}, Frame);
+maybe_ack(State=#state{socket=Socket, transport=Transport}, Frame) ->
+ case Frame of
+ {settings, _} -> Transport:send(Socket, cow_http2:settings_ack());
+ {ping, Opaque} -> Transport:send(Socket, cow_http2:ping_ack(Opaque));
+ _ -> ok
+ end,
+ State.
+
+data_frame(State0=#state{opts=Opts, flow=Flow, streams=Streams}, StreamID, IsFin, Data) ->
+ case Streams of
+ #{StreamID := Stream=#stream{status=running, flow=StreamFlow, state=StreamState0}} ->
+ try cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of
+ {Commands, StreamState} ->
+ %% Remove the amount of data received from the flow.
+ %% We may receive more data than we requested. We ensure
+ %% that the flow value doesn't go lower than 0.
+ Size = byte_size(Data),
+ State = update_window(State0#state{flow=max(0, Flow - Size),
+ streams=Streams#{StreamID => Stream#stream{
+ flow=max(0, StreamFlow - Size), state=StreamState}}},
+ StreamID),
+ commands(State, StreamID, Commands)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(data,
+ [StreamID, IsFin, Data, StreamState0],
+ Class, Exception, Stacktrace), Opts),
+ reset_stream(State0, StreamID, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:data/4.'})
+ end;
+ %% We ignore DATA frames for streams that are stopping.
+ #{} ->
+ State0
+ end.
+
+headers_frame(State, StreamID, IsFin, Headers,
+ PseudoHeaders=#{method := <<"CONNECT">>}, _)
+ when map_size(PseudoHeaders) =:= 2 ->
+ early_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501,
+ 'The CONNECT method is currently not implemented. (RFC7231 4.3.6)');
+headers_frame(State, StreamID, IsFin, Headers,
+ PseudoHeaders=#{method := <<"TRACE">>}, _) ->
+ early_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501,
+ 'The TRACE method is currently not implemented. (RFC7231 4.3.8)');
+headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders=#{authority := Authority}, BodyLen) ->
+ headers_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority);
+headers_frame(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen) ->
+ case lists:keyfind(<<"host">>, 1, Headers) of
+ {_, Authority} ->
+ headers_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority);
+ _ ->
+ reset_stream(State, StreamID, {stream_error, protocol_error,
+ 'Requests translated from HTTP/1.1 must include a host header. (RFC7540 8.1.2.3, RFC7230 5.4)'})
+ end.
+
+headers_frame_parse_host(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert, proxy_header=ProxyHeader},
+ StreamID, IsFin, Headers, PseudoHeaders=#{method := Method, scheme := Scheme, path := PathWithQs},
+ BodyLen, Authority) ->
+ try cow_http_hd:parse_host(Authority) of
+ {Host, Port0} ->
+ Port = ensure_port(Scheme, Port0),
+ try cow_http:parse_fullpath(PathWithQs) of
+ {<<>>, _} ->
+ reset_stream(State, StreamID, {stream_error, protocol_error,
+ 'The path component must not be empty. (RFC7540 8.1.2.3)'});
+ {Path, Qs} ->
+ Req0 = #{
+ ref => Ref,
+ pid => self(),
+ streamid => StreamID,
+ peer => Peer,
+ sock => Sock,
+ cert => Cert,
+ method => Method,
+ scheme => Scheme,
+ host => Host,
+ port => Port,
+ path => Path,
+ qs => Qs,
+ version => 'HTTP/2',
+ headers => headers_to_map(Headers, #{}),
+ has_body => IsFin =:= nofin,
+ body_length => BodyLen
+ },
+ %% We add the PROXY header information if any.
+ Req1 = case ProxyHeader of
+ undefined -> Req0;
+ _ -> Req0#{proxy_header => ProxyHeader}
+ end,
+ %% We add the protocol information for extended CONNECTs.
+ Req = case PseudoHeaders of
+ #{protocol := Protocol} -> Req1#{protocol => Protocol};
+ _ -> Req1
+ end,
+ headers_frame(State, StreamID, Req)
+ catch _:_ ->
+ reset_stream(State, StreamID, {stream_error, protocol_error,
+ 'The :path pseudo-header is invalid. (RFC7540 8.1.2.3)'})
+ end
+ catch _:_ ->
+ reset_stream(State, StreamID, {stream_error, protocol_error,
+ 'The :authority pseudo-header is invalid. (RFC7540 8.1.2.3)'})
+ end.
+
+ensure_port(<<"http">>, undefined) -> 80;
+ensure_port(<<"https">>, undefined) -> 443;
+ensure_port(_, Port) -> Port.
+
+%% This function is necessary to properly handle duplicate headers
+%% and the special-case cookie header.
+headers_to_map([], Acc) ->
+ Acc;
+headers_to_map([{Name, Value}|Tail], Acc0) ->
+ Acc = case Acc0 of
+ %% The cookie header does not use proper HTTP header lists.
+ #{Name := Value0} when Name =:= <<"cookie">> ->
+ Acc0#{Name => << Value0/binary, "; ", Value/binary >>};
+ #{Name := Value0} ->
+ Acc0#{Name => << Value0/binary, ", ", Value/binary >>};
+ _ ->
+ Acc0#{Name => Value}
+ end,
+ headers_to_map(Tail, Acc).
+
+headers_frame(State=#state{opts=Opts, streams=Streams}, StreamID, Req) ->
+ try cowboy_stream:init(StreamID, Req, Opts) of
+ {Commands, StreamState} ->
+ commands(State#state{
+ streams=Streams#{StreamID => #stream{state=StreamState}}},
+ StreamID, Commands)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(init,
+ [StreamID, Req, Opts],
+ Class, Exception, Stacktrace), Opts),
+ reset_stream(State, StreamID, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:init/3.'})
+ end.
+
+early_error(State0=#state{ref=Ref, opts=Opts, peer=Peer},
+ StreamID, _IsFin, Headers, #{method := Method},
+ StatusCode0, HumanReadable) ->
+ %% We automatically terminate the stream but it is not an error
+ %% per se (at least not in the first implementation).
+ Reason = {stream_error, no_error, HumanReadable},
+ %% The partial Req is minimal for now. We only have one case
+ %% where it can be called (when a method is completely disabled).
+ %% @todo Fill in the other elements.
+ PartialReq = #{
+ ref => Ref,
+ peer => Peer,
+ method => Method,
+ headers => headers_to_map(Headers, #{})
+ },
+ Resp = {response, StatusCode0, RespHeaders0=#{<<"content-length">> => <<"0">>}, <<>>},
+ try cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts) of
+ {response, StatusCode, RespHeaders, RespBody} ->
+ send_response(State0, StreamID, StatusCode, RespHeaders, RespBody)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(early_error,
+ [StreamID, Reason, PartialReq, Resp, Opts],
+ Class, Exception, Stacktrace), Opts),
+ %% We still need to send an error response, so send what we initially
+ %% wanted to send. It's better than nothing.
+ send_headers(State0, StreamID, fin, StatusCode0, RespHeaders0)
+ end.
+
+rst_stream_frame(State=#state{streams=Streams0, children=Children0}, StreamID, Reason) ->
+ case maps:take(StreamID, Streams0) of
+ {#stream{state=StreamState}, Streams} ->
+ terminate_stream_handler(State, StreamID, Reason, StreamState),
+ Children = cowboy_children:shutdown(Children0, StreamID),
+ State#state{streams=Streams, children=Children};
+ error ->
+ State
+ end.
+
+ignored_frame(State=#state{http2_machine=HTTP2Machine0}) ->
+ case cow_http2_machine:ignored_frame(HTTP2Machine0) of
+ {ok, HTTP2Machine} ->
+ State#state{http2_machine=HTTP2Machine};
+ {error, Error={connection_error, _, _}, HTTP2Machine} ->
+ terminate(State#state{http2_machine=HTTP2Machine}, Error)
+ end.
+
+%% HTTP/2 timeouts.
+
+timeout(State=#state{http2_machine=HTTP2Machine0}, Name, TRef) ->
+ case cow_http2_machine:timeout(Name, TRef, HTTP2Machine0) of
+ {ok, HTTP2Machine} ->
+ State#state{http2_machine=HTTP2Machine};
+ {error, Error={connection_error, _, _}, HTTP2Machine} ->
+ terminate(State#state{http2_machine=HTTP2Machine}, Error)
+ end.
+
+%% Erlang messages.
+
+down(State0=#state{opts=Opts, children=Children0}, Pid, Msg) ->
+ State = case cowboy_children:down(Children0, Pid) of
+ %% The stream was terminated already.
+ {ok, undefined, Children} ->
+ State0#state{children=Children};
+ %% The stream is still running.
+ {ok, StreamID, Children} ->
+ info(State0#state{children=Children}, StreamID, Msg);
+ %% The process was unknown.
+ error ->
+ cowboy:log(warning, "Received EXIT signal ~p for unknown process ~p.~n",
+ [Msg, Pid], Opts),
+ State0
+ end,
+ if
+ State#state.http2_status =:= closing, State#state.streams =:= #{} ->
+ terminate(State, {stop, normal, 'The connection is going away.'});
+ true ->
+ State
+ end.
+
+info(State=#state{opts=Opts, http2_machine=HTTP2Machine, streams=Streams}, StreamID, Msg) ->
+ case Streams of
+ #{StreamID := Stream=#stream{state=StreamState0}} ->
+ try cowboy_stream:info(StreamID, Msg, StreamState0) of
+ {Commands, StreamState} ->
+ commands(State#state{streams=Streams#{StreamID => Stream#stream{state=StreamState}}},
+ StreamID, Commands)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(info,
+ [StreamID, Msg, StreamState0],
+ Class, Exception, Stacktrace), Opts),
+ reset_stream(State, StreamID, {internal_error, {Class, Exception},
+ 'Unhandled exception in cowboy_stream:info/3.'})
+ end;
+ _ ->
+ case cow_http2_machine:is_lingering_stream(StreamID, HTTP2Machine) of
+ true ->
+ ok;
+ false ->
+ cowboy:log(warning, "Received message ~p for unknown stream ~p.",
+ [Msg, StreamID], Opts)
+ end,
+ State
+ end.
+
+%% Stream handler commands.
+%%
+%% @todo Kill the stream if it tries to send a response, headers,
+%% data or push promise when the stream is closed or half-closed.
+
+commands(State, _, []) ->
+ State;
+%% Error responses are sent only if a response wasn't sent already.
+commands(State=#state{http2_machine=HTTP2Machine}, StreamID,
+ [{error_response, StatusCode, Headers, Body}|Tail]) ->
+ case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of
+ {ok, idle, _} ->
+ commands(State, StreamID, [{response, StatusCode, Headers, Body}|Tail]);
+ _ ->
+ commands(State, StreamID, Tail)
+ end;
+%% Send an informational response.
+commands(State0, StreamID, [{inform, StatusCode, Headers}|Tail]) ->
+ State = send_headers(State0, StreamID, idle, StatusCode, Headers),
+ commands(State, StreamID, Tail);
+%% Send response headers.
+commands(State0, StreamID, [{response, StatusCode, Headers, Body}|Tail]) ->
+ State = send_response(State0, StreamID, StatusCode, Headers, Body),
+ commands(State, StreamID, Tail);
+%% Send response headers.
+commands(State0, StreamID, [{headers, StatusCode, Headers}|Tail]) ->
+ State = send_headers(State0, StreamID, nofin, StatusCode, Headers),
+ commands(State, StreamID, Tail);
+%% Send a response body chunk.
+commands(State0, StreamID, [{data, IsFin, Data}|Tail]) ->
+ State = maybe_send_data(State0, StreamID, IsFin, Data, []),
+ commands(State, StreamID, Tail);
+%% Send trailers.
+commands(State0, StreamID, [{trailers, Trailers}|Tail]) ->
+ State = maybe_send_data(State0, StreamID, fin, {trailers, maps:to_list(Trailers)}, []),
+ commands(State, StreamID, Tail);
+%% Send a push promise.
+%%
+%% @todo Responses sent as a result of a push_promise request
+%% must not send push_promise frames themselves.
+%%
+%% @todo We should not send push_promise frames when we are
+%% in the closing http2_status.
+commands(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine0},
+ StreamID, [{push, Method, Scheme, Host, Port, Path, Qs, Headers0}|Tail]) ->
+ Authority = case {Scheme, Port} of
+ {<<"http">>, 80} -> Host;
+ {<<"https">>, 443} -> Host;
+ _ -> iolist_to_binary([Host, $:, integer_to_binary(Port)])
+ end,
+ PathWithQs = iolist_to_binary(case Qs of
+ <<>> -> Path;
+ _ -> [Path, $?, Qs]
+ end),
+ PseudoHeaders = #{
+ method => Method,
+ scheme => Scheme,
+ authority => Authority,
+ path => PathWithQs
+ },
+ %% We need to make sure the header value is binary before we can
+ %% create the Req object, as it expects them to be flat.
+ Headers = maps:to_list(maps:map(fun(_, V) -> iolist_to_binary(V) end, Headers0)),
+ State = case cow_http2_machine:prepare_push_promise(StreamID, HTTP2Machine0,
+ PseudoHeaders, Headers) of
+ {ok, PromisedStreamID, HeaderBlock, HTTP2Machine} ->
+ Transport:send(Socket, cow_http2:push_promise(
+ StreamID, PromisedStreamID, HeaderBlock)),
+ headers_frame(State0#state{http2_machine=HTTP2Machine},
+ PromisedStreamID, fin, Headers, PseudoHeaders, 0);
+ {error, no_push} ->
+ State0
+ end,
+ commands(State, StreamID, Tail);
+%% Read the request body.
+commands(State0=#state{flow=Flow, streams=Streams}, StreamID, [{flow, Size}|Tail]) ->
+ #{StreamID := Stream=#stream{flow=StreamFlow}} = Streams,
+ State = update_window(State0#state{flow=Flow + Size,
+ streams=Streams#{StreamID => Stream#stream{flow=StreamFlow + Size}}},
+ StreamID),
+ commands(State, StreamID, Tail);
+%% Supervise a child process.
+commands(State=#state{children=Children}, StreamID, [{spawn, Pid, Shutdown}|Tail]) ->
+ commands(State#state{children=cowboy_children:up(Children, Pid, StreamID, Shutdown)},
+ StreamID, Tail);
+%% Error handling.
+commands(State, StreamID, [Error = {internal_error, _, _}|_Tail]) ->
+ %% @todo Do we want to run the commands after an internal_error?
+ %% @todo Do we even allow commands after?
+ %% @todo Only reset when the stream still exists.
+ reset_stream(State, StreamID, Error);
+%% Upgrade to HTTP/2. This is triggered by cowboy_http2 itself.
+commands(State=#state{socket=Socket, transport=Transport, http2_status=upgrade},
+ StreamID, [{switch_protocol, Headers, ?MODULE, _}|Tail]) ->
+ %% @todo This 101 response needs to be passed through stream handlers.
+ Transport:send(Socket, cow_http:response(101, 'HTTP/1.1', maps:to_list(Headers))),
+ commands(State, StreamID, Tail);
+%% Use a different protocol within the stream (CONNECT :protocol).
+%% @todo Make sure we error out when the feature is disabled.
+commands(State0, StreamID, [{switch_protocol, Headers, _Mod, _ModState}|Tail]) ->
+ State = info(State0, StreamID, {headers, 200, Headers}),
+ commands(State, StreamID, Tail);
+%% Set options dynamically.
+commands(State, StreamID, [{set_options, _Opts}|Tail]) ->
+ commands(State, StreamID, Tail);
+commands(State, StreamID, [stop|_Tail]) ->
+ %% @todo Do we want to run the commands after a stop?
+ %% @todo Do we even allow commands after?
+ stop_stream(State, StreamID);
+%% Log event.
+commands(State=#state{opts=Opts}, StreamID, [Log={log, _, _, _}|Tail]) ->
+ cowboy:log(Log, Opts),
+ commands(State, StreamID, Tail).
+
+%% Tentatively update the window after the flow was updated.
+
+update_window(State=#state{socket=Socket, transport=Transport,
+ http2_machine=HTTP2Machine0, flow=Flow, streams=Streams}, StreamID) ->
+ #{StreamID := #stream{flow=StreamFlow}} = Streams,
+ {Data1, HTTP2Machine2} = case cow_http2_machine:ensure_window(Flow, HTTP2Machine0) of
+ ok -> {<<>>, HTTP2Machine0};
+ {ok, Increment1, HTTP2Machine1} -> {cow_http2:window_update(Increment1), HTTP2Machine1}
+ end,
+ {Data2, HTTP2Machine} = case cow_http2_machine:ensure_window(StreamID, StreamFlow, HTTP2Machine2) of
+ ok -> {<<>>, HTTP2Machine2};
+ {ok, Increment2, HTTP2Machine3} -> {cow_http2:window_update(StreamID, Increment2), HTTP2Machine3}
+ end,
+ case {Data1, Data2} of
+ {<<>>, <<>>} -> ok;
+ _ -> Transport:send(Socket, [Data1, Data2])
+ end,
+ State#state{http2_machine=HTTP2Machine}.
+
+%% Send the response, trailers or data.
+
+send_response(State0=#state{http2_machine=HTTP2Machine0}, StreamID, StatusCode, Headers, Body) ->
+ Size = case Body of
+ {sendfile, _, Bytes, _} -> Bytes;
+ _ -> iolist_size(Body)
+ end,
+ case Size of
+ 0 ->
+ State = send_headers(State0, StreamID, fin, StatusCode, Headers),
+ maybe_terminate_stream(State, StreamID, fin);
+ _ ->
+ %% @todo Add a test for HEAD to make sure we don't send the body when
+ %% returning {response...} from a stream handler (or {headers...} then {data...}).
+ {ok, _IsFin, HeaderBlock, HTTP2Machine}
+ = cow_http2_machine:prepare_headers(StreamID, HTTP2Machine0, nofin,
+ #{status => cow_http:status_to_integer(StatusCode)},
+ headers_to_list(Headers)),
+ maybe_send_data(State0#state{http2_machine=HTTP2Machine}, StreamID, fin, Body,
+ [cow_http2:headers(StreamID, nofin, HeaderBlock)])
+ end.
+
+send_headers(State=#state{socket=Socket, transport=Transport,
+ http2_machine=HTTP2Machine0}, StreamID, IsFin0, StatusCode, Headers) ->
+ {ok, IsFin, HeaderBlock, HTTP2Machine}
+ = cow_http2_machine:prepare_headers(StreamID, HTTP2Machine0, IsFin0,
+ #{status => cow_http:status_to_integer(StatusCode)},
+ headers_to_list(Headers)),
+ Transport:send(Socket, cow_http2:headers(StreamID, IsFin, HeaderBlock)),
+ State#state{http2_machine=HTTP2Machine}.
+
+%% The set-cookie header is special; we can only send one cookie per header.
+headers_to_list(Headers0=#{<<"set-cookie">> := SetCookies}) ->
+ Headers = maps:to_list(maps:remove(<<"set-cookie">>, Headers0)),
+ Headers ++ [{<<"set-cookie">>, Value} || Value <- SetCookies];
+headers_to_list(Headers) ->
+ maps:to_list(Headers).
+
+maybe_send_data(State0=#state{socket=Socket, transport=Transport,
+ http2_machine=HTTP2Machine0}, StreamID, IsFin, Data0, Prefix) ->
+ Data = case is_tuple(Data0) of
+ false -> {data, Data0};
+ true -> Data0
+ end,
+ case cow_http2_machine:send_or_queue_data(StreamID, HTTP2Machine0, IsFin, Data) of
+ {ok, HTTP2Machine} ->
+ %% If we have prefix data (like a HEADERS frame) we need to send it
+ %% even if we do not send any DATA frames.
+ case Prefix of
+ [] -> ok;
+ _ -> Transport:send(Socket, Prefix)
+ end,
+ maybe_send_data_alarm(State0#state{http2_machine=HTTP2Machine}, HTTP2Machine0, StreamID);
+ {send, SendData, HTTP2Machine} ->
+ State = #state{http2_status=Status, streams=Streams}
+ = send_data(State0#state{http2_machine=HTTP2Machine}, SendData, Prefix),
+ %% Terminate the connection if we are closing and all streams have completed.
+ if
+ Status =:= closing, Streams =:= #{} ->
+ terminate(State, {stop, normal, 'The connection is going away.'});
+ true ->
+ maybe_send_data_alarm(State, HTTP2Machine0, StreamID)
+ end
+ end.
+
+send_data(State0=#state{socket=Socket, transport=Transport, opts=Opts}, SendData, Prefix) ->
+ {Acc, State} = prepare_data(State0, SendData, [], Prefix),
+ _ = [case Data of
+ {sendfile, Offset, Bytes, Path} ->
+ %% When sendfile is disabled we explicitly use the fallback.
+ _ = case maps:get(sendfile, Opts, true) of
+ true -> Transport:sendfile(Socket, Path, Offset, Bytes);
+ false -> ranch_transport:sendfile(Transport, Socket, Path, Offset, Bytes, [])
+ end;
+ _ ->
+ Transport:send(Socket, Data)
+ end || Data <- Acc],
+ send_data_terminate(State, SendData).
+
+send_data_terminate(State, []) ->
+ State;
+send_data_terminate(State0, [{StreamID, IsFin, _}|Tail]) ->
+ State = maybe_terminate_stream(State0, StreamID, IsFin),
+ send_data_terminate(State, Tail).
+
+prepare_data(State, [], Acc, []) ->
+ {lists:reverse(Acc), State};
+prepare_data(State, [], Acc, Buffer) ->
+ {lists:reverse([lists:reverse(Buffer)|Acc]), State};
+prepare_data(State0, [{StreamID, IsFin, SendData}|Tail], Acc0, Buffer0) ->
+ {Acc, Buffer, State} = prepare_data(State0, StreamID, IsFin, SendData, Acc0, Buffer0),
+ prepare_data(State, Tail, Acc, Buffer).
+
+prepare_data(State, _, _, [], Acc, Buffer) ->
+ {Acc, Buffer, State};
+prepare_data(State0, StreamID, IsFin, [FrameData|Tail], Acc, Buffer) ->
+ FrameIsFin = case Tail of
+ [] -> IsFin;
+ _ -> nofin
+ end,
+ case prepare_data_frame(State0, StreamID, FrameIsFin, FrameData) of
+ {{MoreData, Sendfile}, State} when is_tuple(Sendfile) ->
+ case Buffer of
+ [] ->
+ prepare_data(State, StreamID, IsFin, Tail,
+ [Sendfile, MoreData|Acc], []);
+ _ ->
+ prepare_data(State, StreamID, IsFin, Tail,
+ [Sendfile, lists:reverse([MoreData|Buffer])|Acc], [])
+ end;
+ {MoreData, State} ->
+ prepare_data(State, StreamID, IsFin, Tail,
+ Acc, [MoreData|Buffer])
+ end.
+
+prepare_data_frame(State, StreamID, IsFin, {data, Data}) ->
+ {cow_http2:data(StreamID, IsFin, Data),
+ State};
+prepare_data_frame(State, StreamID, IsFin, Sendfile={sendfile, _, Bytes, _}) ->
+ {{cow_http2:data_header(StreamID, IsFin, Bytes), Sendfile},
+ State};
+%% The stream is terminated in cow_http2_machine:prepare_trailers.
+prepare_data_frame(State=#state{http2_machine=HTTP2Machine0},
+ StreamID, nofin, {trailers, Trailers}) ->
+ {ok, HeaderBlock, HTTP2Machine}
+ = cow_http2_machine:prepare_trailers(StreamID, HTTP2Machine0, Trailers),
+ {cow_http2:headers(StreamID, fin, HeaderBlock),
+ State#state{http2_machine=HTTP2Machine}}.
+
+%% After we have sent or queued data we may need to set or clear an alarm.
+%% We do this by comparing the HTTP2Machine buffer state before/after for
+%% the relevant streams.
+maybe_send_data_alarm(State=#state{opts=Opts, http2_machine=HTTP2Machine}, HTTP2Machine0, StreamID) ->
+ ConnBufferSizeBefore = cow_http2_machine:get_connection_local_buffer_size(HTTP2Machine0),
+ ConnBufferSizeAfter = cow_http2_machine:get_connection_local_buffer_size(HTTP2Machine),
+ {ok, StreamBufferSizeBefore} = cow_http2_machine:get_stream_local_buffer_size(StreamID, HTTP2Machine0),
+ %% When the stream ends up closed after it finished sending data,
+ %% we do not want to trigger an alarm. We act as if the buffer
+ %% size did not change.
+ StreamBufferSizeAfter = case cow_http2_machine:get_stream_local_buffer_size(StreamID, HTTP2Machine) of
+ {ok, BSA} -> BSA;
+ {error, closed} -> StreamBufferSizeBefore
+ end,
+ MaxConnBufferSize = maps:get(max_connection_buffer_size, Opts, 16000000),
+ MaxStreamBufferSize = maps:get(max_stream_buffer_size, Opts, 8000000),
+ %% I do not want to document these internal events yet. I am not yet
+ %% convinced it should be {alarm, Name, on|off} and not {internal_event, E}
+ %% or something else entirely. Though alarms are probably right.
+ if
+ ConnBufferSizeBefore >= MaxConnBufferSize, ConnBufferSizeAfter < MaxConnBufferSize ->
+ connection_alarm(State, connection_buffer_full, off);
+ ConnBufferSizeBefore < MaxConnBufferSize, ConnBufferSizeAfter >= MaxConnBufferSize ->
+ connection_alarm(State, connection_buffer_full, on);
+ StreamBufferSizeBefore >= MaxStreamBufferSize, StreamBufferSizeAfter < MaxStreamBufferSize ->
+ stream_alarm(State, StreamID, stream_buffer_full, off);
+ StreamBufferSizeBefore < MaxStreamBufferSize, StreamBufferSizeAfter >= MaxStreamBufferSize ->
+ stream_alarm(State, StreamID, stream_buffer_full, on);
+ true ->
+ State
+ end.
+
+connection_alarm(State0=#state{streams=Streams}, Name, Value) ->
+ lists:foldl(fun(StreamID, State) ->
+ stream_alarm(State, StreamID, Name, Value)
+ end, State0, maps:keys(Streams)).
+
+stream_alarm(State, StreamID, Name, Value) ->
+ info(State, StreamID, {alarm, Name, Value}).
+
+%% Terminate a stream or the connection.
+
+%% We may have to cancel streams even if we receive multiple
+%% GOAWAY frames as the LastStreamID value may be lower than
+%% the one previously received.
+goaway(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine0,
+ http2_status=Status, streams=Streams0}, {goaway, LastStreamID, Reason, _})
+ when Status =:= connected; Status =:= closing_initiated; Status =:= closing ->
+ Streams = goaway_streams(State0, maps:to_list(Streams0), LastStreamID,
+ {stop, {goaway, Reason}, 'The connection is going away.'}, []),
+ State = State0#state{streams=maps:from_list(Streams)},
+ if
+ Status =:= connected; Status =:= closing_initiated ->
+ {OurLastStreamID, HTTP2Machine} =
+ cow_http2_machine:set_last_streamid(HTTP2Machine0),
+ Transport:send(Socket, cow_http2:goaway(
+ OurLastStreamID, no_error, <<>>)),
+ State#state{http2_status=closing,
+ http2_machine=HTTP2Machine};
+ true ->
+ State
+ end;
+%% We terminate the connection immediately if it hasn't fully been initialized.
+goaway(State, {goaway, _, Reason, _}) ->
+ terminate(State, {stop, {goaway, Reason}, 'The connection is going away.'}).
+
+%% Cancel client-initiated streams that are above LastStreamID.
+goaway_streams(_, [], _, _, Acc) ->
+ Acc;
+goaway_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], LastStreamID, Reason, Acc)
+ when StreamID > LastStreamID, (StreamID rem 2) =:= 0 ->
+ terminate_stream_handler(State, StreamID, Reason, StreamState),
+ goaway_streams(State, Tail, LastStreamID, Reason, Acc);
+goaway_streams(State, [Stream|Tail], LastStreamID, Reason, Acc) ->
+ goaway_streams(State, Tail, LastStreamID, Reason, [Stream|Acc]).
+
+%% A server that is attempting to gracefully shut down a connection SHOULD send
+%% an initial GOAWAY frame with the last stream identifier set to 2^31-1 and a
+%% NO_ERROR code. This signals to the client that a shutdown is imminent and
+%% that initiating further requests is prohibited. After allowing time for any
+%% in-flight stream creation (at least one round-trip time), the server can send
+%% another GOAWAY frame with an updated last stream identifier. This ensures
+%% that a connection can be cleanly shut down without losing requests.
+-spec initiate_closing(#state{}, _) -> #state{}.
+initiate_closing(State=#state{http2_status=connected, socket=Socket,
+ transport=Transport, opts=Opts}, Reason) ->
+ Transport:send(Socket, cow_http2:goaway(16#7fffffff, no_error, <<>>)),
+ Timeout = maps:get(goaway_initial_timeout, Opts, 1000),
+ Message = {goaway_initial_timeout, Reason},
+ set_timeout(State#state{http2_status=closing_initiated}, Timeout, Message);
+initiate_closing(State=#state{http2_status=Status}, _Reason)
+ when Status =:= closing_initiated; Status =:= closing ->
+ %% This happens if sys:terminate/2,3 is called twice or if the supervisor
+ %% tells us to shutdown after sys:terminate/2,3 is called or vice versa.
+ State;
+initiate_closing(State, Reason) ->
+ terminate(State, {stop, stop_reason(Reason), 'The connection is going away.'}).
+
+%% Switch to 'closing' state and stop accepting new streams.
+-spec closing(#state{}, Reason :: term()) -> #state{}.
+closing(State=#state{streams=Streams}, Reason) when Streams =:= #{} ->
+ terminate(State, Reason);
+closing(State=#state{http2_status=closing_initiated,
+ http2_machine=HTTP2Machine0, socket=Socket, transport=Transport},
+ Reason) ->
+ %% Stop accepting new streams.
+ {LastStreamID, HTTP2Machine} =
+ cow_http2_machine:set_last_streamid(HTTP2Machine0),
+ Transport:send(Socket, cow_http2:goaway(LastStreamID, no_error, <<>>)),
+ closing(State#state{http2_status=closing, http2_machine=HTTP2Machine}, Reason);
+closing(State=#state{http2_status=closing, opts=Opts}, Reason) ->
+ %% If client sent GOAWAY, we may already be in 'closing' but without the
+ %% goaway complete timeout set.
+ Timeout = maps:get(goaway_complete_timeout, Opts, 3000),
+ Message = {goaway_complete_timeout, Reason},
+ set_timeout(State, Timeout, Message).
+
+stop_reason({stop, Reason, _}) -> Reason;
+stop_reason(Reason) -> Reason.
+
+-spec terminate(#state{}, _) -> no_return().
+terminate(undefined, Reason) ->
+ exit({shutdown, Reason});
+terminate(State=#state{socket=Socket, transport=Transport, http2_status=Status,
+ http2_machine=HTTP2Machine, streams=Streams, children=Children}, Reason)
+ when Status =:= connected; Status =:= closing_initiated; Status =:= closing ->
+ %% @todo We might want to optionally send the Reason value
+ %% as debug data in the GOAWAY frame here. Perhaps more.
+ if
+ Status =:= connected; Status =:= closing_initiated ->
+ Transport:send(Socket, cow_http2:goaway(
+ cow_http2_machine:get_last_streamid(HTTP2Machine),
+ terminate_reason(Reason), <<>>));
+ %% We already sent the GOAWAY frame.
+ Status =:= closing ->
+ ok
+ end,
+ terminate_all_streams(State, maps:to_list(Streams), Reason),
+ cowboy_children:terminate(Children),
+ terminate_linger(State),
+ exit({shutdown, Reason});
+terminate(#state{socket=Socket, transport=Transport}, Reason) ->
+ Transport:close(Socket),
+ exit({shutdown, Reason}).
+
+terminate_reason({connection_error, Reason, _}) -> Reason;
+terminate_reason({stop, _, _}) -> no_error;
+terminate_reason({socket_error, _, _}) -> internal_error;
+terminate_reason({internal_error, _, _}) -> internal_error.
+
+terminate_all_streams(_, [], _) ->
+ ok;
+terminate_all_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], Reason) ->
+ terminate_stream_handler(State, StreamID, Reason, StreamState),
+ terminate_all_streams(State, Tail, Reason).
+
+%% This code is copied from cowboy_http.
+terminate_linger(State=#state{socket=Socket, transport=Transport, opts=Opts}) ->
+ case Transport:shutdown(Socket, write) of
+ ok ->
+ case maps:get(linger_timeout, Opts, 1000) of
+ 0 ->
+ ok;
+ infinity ->
+ terminate_linger_before_loop(State, undefined, Transport:messages());
+ Timeout ->
+ TimerRef = erlang:start_timer(Timeout, self(), linger_timeout),
+ terminate_linger_before_loop(State, TimerRef, Transport:messages())
+ end;
+ {error, _} ->
+ ok
+ end.
+
+terminate_linger_before_loop(State, TimerRef, Messages) ->
+ %% We may already be in active mode when we do this
+ %% but it's OK because we are shutting down anyway.
+ case setopts_active(State) of
+ ok ->
+ terminate_linger_loop(State, TimerRef, Messages);
+ {error, _} ->
+ ok
+ end.
+
+terminate_linger_loop(State=#state{socket=Socket}, TimerRef, Messages) ->
+ receive
+ {OK, Socket, _} when OK =:= element(1, Messages) ->
+ terminate_linger_loop(State, TimerRef, Messages);
+ {Closed, Socket} when Closed =:= element(2, Messages) ->
+ ok;
+ {Error, Socket, _} when Error =:= element(3, Messages) ->
+ ok;
+ {Passive, Socket} when Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ terminate_linger_before_loop(State, TimerRef, Messages);
+ {timeout, TimerRef, linger_timeout} ->
+ ok;
+ _ ->
+ terminate_linger_loop(State, TimerRef, Messages)
+ end.
+
+%% @todo Don't send an RST_STREAM if one was already sent.
+reset_stream(State0=#state{socket=Socket, transport=Transport,
+ http2_machine=HTTP2Machine0}, StreamID, Error) ->
+ Reason = case Error of
+ {internal_error, _, _} -> internal_error;
+ {stream_error, Reason0, _} -> Reason0
+ end,
+ Transport:send(Socket, cow_http2:rst_stream(StreamID, Reason)),
+ State1 = case cow_http2_machine:reset_stream(StreamID, HTTP2Machine0) of
+ {ok, HTTP2Machine} ->
+ terminate_stream(State0#state{http2_machine=HTTP2Machine}, StreamID, Error);
+ {error, not_found} ->
+ terminate_stream(State0, StreamID, Error)
+ end,
+ case reset_rate(State1) of
+ {ok, State} ->
+ State;
+ error ->
+ terminate(State1, {connection_error, enhance_your_calm,
+ 'Stream reset rate larger than configuration allows. Flood? (CVE-2019-9514)'})
+ end.
+
+reset_rate(State0=#state{reset_rate_num=Num0, reset_rate_time=Time}) ->
+ case Num0 - 1 of
+ 0 ->
+ CurrentTime = erlang:monotonic_time(millisecond),
+ if
+ CurrentTime < Time ->
+ error;
+ true ->
+ %% When the option has a period of infinity we cannot reach this clause.
+ {ok, init_reset_rate_limiting(State0, CurrentTime)}
+ end;
+ Num ->
+ {ok, State0#state{reset_rate_num=Num}}
+ end.
+
+stop_stream(State=#state{http2_machine=HTTP2Machine}, StreamID) ->
+ case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of
+ %% When the stream terminates normally (without sending RST_STREAM)
+ %% and no response was sent, we need to send a proper response back to the client.
+ %% We delay the termination of the stream until the response is fully sent.
+ {ok, idle, _} ->
+ info(stopping(State, StreamID), StreamID, {response, 204, #{}, <<>>});
+ %% When a response was sent but not terminated, we need to close the stream.
+ %% We delay the termination of the stream until the response is fully sent.
+ {ok, nofin, fin} ->
+ stopping(State, StreamID);
+ %% We only send a final DATA frame if there isn't one queued yet.
+ {ok, nofin, _} ->
+ info(stopping(State, StreamID), StreamID, {data, fin, <<>>});
+ %% When a response was sent fully we can terminate the stream,
+ %% regardless of the stream being in half-closed or closed state.
+ _ ->
+ terminate_stream(State, StreamID)
+ end.
+
+stopping(State=#state{streams=Streams}, StreamID) ->
+ #{StreamID := Stream} = Streams,
+ State#state{streams=Streams#{StreamID => Stream#stream{status=stopping}}}.
+
+%% If we finished sending data and the stream is stopping, terminate it.
+maybe_terminate_stream(State=#state{streams=Streams}, StreamID, fin) ->
+ case Streams of
+ #{StreamID := #stream{status=stopping}} ->
+ terminate_stream(State, StreamID);
+ _ ->
+ State
+ end;
+maybe_terminate_stream(State, _, _) ->
+ State.
+
+%% When the stream stops normally without reading the request
+%% body fully we need to tell the client to stop sending it.
+%% We do this by sending an RST_STREAM with reason NO_ERROR. (RFC7540 8.1.0)
+terminate_stream(State0=#state{socket=Socket, transport=Transport,
+ http2_machine=HTTP2Machine0}, StreamID) ->
+ State = case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine0) of
+ {ok, fin, _} ->
+ Transport:send(Socket, cow_http2:rst_stream(StreamID, no_error)),
+ {ok, HTTP2Machine} = cow_http2_machine:reset_stream(StreamID, HTTP2Machine0),
+ State0#state{http2_machine=HTTP2Machine};
+ {error, closed} ->
+ State0
+ end,
+ terminate_stream(State, StreamID, normal).
+
+%% We remove the stream flow from the connection flow. Any further
+%% data received for this stream is therefore fully contained within
+%% the extra window we allocated for this stream.
+terminate_stream(State=#state{flow=Flow, streams=Streams0, children=Children0}, StreamID, Reason) ->
+ case maps:take(StreamID, Streams0) of
+ {#stream{flow=StreamFlow, state=StreamState}, Streams} ->
+ terminate_stream_handler(State, StreamID, Reason, StreamState),
+ Children = cowboy_children:shutdown(Children0, StreamID),
+ State#state{flow=Flow - StreamFlow, streams=Streams, children=Children};
+ error ->
+ State
+ end.
+
+terminate_stream_handler(#state{opts=Opts}, StreamID, Reason, StreamState) ->
+ try
+ cowboy_stream:terminate(StreamID, Reason, StreamState)
+ catch Class:Exception:Stacktrace ->
+ cowboy:log(cowboy_stream:make_error_log(terminate,
+ [StreamID, Reason, StreamState],
+ Class, Exception, Stacktrace), Opts)
+ end.
+
+%% System callbacks.
+
+-spec system_continue(_, _, {#state{}, binary()}) -> ok.
+system_continue(_, _, {State, Buffer}) ->
+ loop(State, Buffer).
+
+-spec system_terminate(any(), _, _, {#state{}, binary()}) -> no_return().
+system_terminate(Reason0, _, _, {State, Buffer}) ->
+ Reason = {stop, {exit, Reason0}, 'sys:terminate/2,3 was called.'},
+ loop(initiate_closing(State, Reason), Buffer).
+
+-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::{#state{}, binary()}.
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_loop.erl b/server/_build/default/lib/cowboy/src/cowboy_loop.erl
new file mode 100644
index 0000000..21eb96e
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_loop.erl
@@ -0,0 +1,108 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_loop).
+-behaviour(cowboy_sub_protocol).
+
+-export([upgrade/4]).
+-export([upgrade/5]).
+-export([loop/4]).
+
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-callback init(Req, any())
+ -> {ok | module(), Req, any()}
+ | {module(), Req, any(), any()}
+ when Req::cowboy_req:req().
+
+-callback info(any(), Req, State)
+ -> {ok, Req, State}
+ | {ok, Req, State, hibernate}
+ | {stop, Req, State}
+ when Req::cowboy_req:req(), State::any().
+
+-callback terminate(any(), cowboy_req:req(), any()) -> ok.
+-optional_callbacks([terminate/3]).
+
+-spec upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req, Env, Handler, HandlerState) ->
+ loop(Req, Env, Handler, HandlerState).
+
+-spec upgrade(Req, Env, module(), any(), hibernate)
+ -> {suspend, ?MODULE, loop, [any()]}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req, Env, Handler, HandlerState, hibernate) ->
+ suspend(Req, Env, Handler, HandlerState).
+
+-spec loop(Req, Env, module(), any())
+ -> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+%% @todo Handle system messages.
+loop(Req=#{pid := Parent}, Env, Handler, HandlerState) ->
+ receive
+ %% System messages.
+ {'EXIT', Parent, Reason} ->
+ terminate(Req, Env, Handler, HandlerState, Reason);
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
+ {Req, Env, Handler, HandlerState});
+ %% Calls from supervisor module.
+ {'$gen_call', From, Call} ->
+ cowboy_children:handle_supervisor_call(Call, From, [], ?MODULE),
+ loop(Req, Env, Handler, HandlerState);
+ Message ->
+ call(Req, Env, Handler, HandlerState, Message)
+ end.
+
+call(Req0, Env, Handler, HandlerState0, Message) ->
+ try Handler:info(Message, Req0, HandlerState0) of
+ {ok, Req, HandlerState} ->
+ loop(Req, Env, Handler, HandlerState);
+ {ok, Req, HandlerState, hibernate} ->
+ suspend(Req, Env, Handler, HandlerState);
+ {stop, Req, HandlerState} ->
+ terminate(Req, Env, Handler, HandlerState, stop)
+ catch Class:Reason:Stacktrace ->
+ cowboy_handler:terminate({crash, Class, Reason}, Req0, HandlerState0, Handler),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+suspend(Req, Env, Handler, HandlerState) ->
+ {suspend, ?MODULE, loop, [Req, Env, Handler, HandlerState]}.
+
+terminate(Req, Env, Handler, HandlerState, Reason) ->
+ Result = cowboy_handler:terminate(Reason, Req, HandlerState, Handler),
+ {ok, Req, Env#{result => Result}}.
+
+%% System callbacks.
+
+-spec system_continue(_, _, {Req, Env, module(), any()})
+ -> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+system_continue(_, _, {Req, Env, Handler, HandlerState}) ->
+ loop(Req, Env, Handler, HandlerState).
+
+-spec system_terminate(any(), _, _, {Req, Env, module(), any()})
+ -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+system_terminate(Reason, _, _, {Req, Env, Handler, HandlerState}) ->
+ terminate(Req, Env, Handler, HandlerState, Reason).
+
+-spec system_code_change(Misc, _, _, _) -> {ok, Misc}
+ when Misc::{cowboy_req:req(), cowboy_middleware:env(), module(), any()}.
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_metrics_h.erl b/server/_build/default/lib/cowboy/src/cowboy_metrics_h.erl
new file mode 100644
index 0000000..4107aac
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_metrics_h.erl
@@ -0,0 +1,331 @@
+%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_metrics_h).
+-behavior(cowboy_stream).
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+
+-type proc_metrics() :: #{pid() => #{
+ %% Time at which the process spawned.
+ spawn := integer(),
+
+ %% Time at which the process exited.
+ exit => integer(),
+
+ %% Reason for the process exit.
+ reason => any()
+}}.
+
+-type informational_metrics() :: #{
+ %% Informational response status.
+ status := cowboy:http_status(),
+
+ %% Headers sent with the informational response.
+ headers := cowboy:http_headers(),
+
+ %% Time when the informational response was sent.
+ time := integer()
+}.
+
+-type metrics() :: #{
+ %% The identifier for this listener.
+ ref := ranch:ref(),
+
+ %% The pid for this connection.
+ pid := pid(),
+
+ %% The streamid also indicates the total number of requests on
+ %% this connection (StreamID div 2 + 1).
+ streamid := cowboy_stream:streamid(),
+
+ %% The terminate reason is always useful.
+ reason := cowboy_stream:reason(),
+
+ %% A filtered Req object or a partial Req object
+ %% depending on how far the request got to.
+ req => cowboy_req:req(),
+ partial_req => cowboy_stream:partial_req(),
+
+ %% Response status.
+ resp_status := cowboy:http_status(),
+
+ %% Filtered response headers.
+ resp_headers := cowboy:http_headers(),
+
+ %% Start/end of the processing of the request.
+ %%
+ %% This represents the time from this stream handler's init
+ %% to terminate.
+ req_start => integer(),
+ req_end => integer(),
+
+ %% Start/end of the receiving of the request body.
+ %% Begins when the first packet has been received.
+ req_body_start => integer(),
+ req_body_end => integer(),
+
+ %% Start/end of the sending of the response.
+ %% Begins when we send the headers and ends on the final
+ %% packet of the response body. If everything is sent at
+ %% once these values are identical.
+ resp_start => integer(),
+ resp_end => integer(),
+
+ %% For early errors all we get is the time we received it.
+ early_error_time => integer(),
+
+ %% Start/end of spawned processes. This is where most of
+ %% the user code lies, excluding stream handlers. On a
+ %% default Cowboy configuration there should be only one
+ %% process: the request process.
+ procs => proc_metrics(),
+
+ %% Informational responses sent before the final response.
+ informational => [informational_metrics()],
+
+ %% Length of the request and response bodies. This does
+ %% not include the framing.
+ req_body_length => non_neg_integer(),
+ resp_body_length => non_neg_integer(),
+
+ %% Additional metadata set by the user.
+ user_data => map()
+}.
+-export_type([metrics/0]).
+
+-type metrics_callback() :: fun((metrics()) -> any()).
+-export_type([metrics_callback/0]).
+
+-record(state, {
+ next :: any(),
+ callback :: fun((metrics()) -> any()),
+ resp_headers_filter :: undefined | fun((cowboy:http_headers()) -> cowboy:http_headers()),
+ req :: map(),
+ resp_status :: undefined | cowboy:http_status(),
+ resp_headers :: undefined | cowboy:http_headers(),
+ ref :: ranch:ref(),
+ req_start :: integer(),
+ req_end :: undefined | integer(),
+ req_body_start :: undefined | integer(),
+ req_body_end :: undefined | integer(),
+ resp_start :: undefined | integer(),
+ resp_end :: undefined | integer(),
+ procs = #{} :: proc_metrics(),
+ informational = [] :: [informational_metrics()],
+ req_body_length = 0 :: non_neg_integer(),
+ resp_body_length = 0 :: non_neg_integer(),
+ user_data = #{} :: map()
+}).
+
+-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
+ -> {[{spawn, pid(), timeout()}], #state{}}.
+init(StreamID, Req=#{ref := Ref}, Opts=#{metrics_callback := Fun}) ->
+ ReqStart = erlang:monotonic_time(),
+ {Commands, Next} = cowboy_stream:init(StreamID, Req, Opts),
+ FilteredReq = case maps:get(metrics_req_filter, Opts, undefined) of
+ undefined -> Req;
+ ReqFilter -> ReqFilter(Req)
+ end,
+ RespHeadersFilter = maps:get(metrics_resp_headers_filter, Opts, undefined),
+ {Commands, fold(Commands, #state{
+ next=Next,
+ callback=Fun,
+ resp_headers_filter=RespHeadersFilter,
+ req=FilteredReq,
+ ref=Ref,
+ req_start=ReqStart
+ })}.
+
+-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+data(StreamID, IsFin=fin, Data, State=#state{req_body_start=undefined}) ->
+ ReqBody = erlang:monotonic_time(),
+ do_data(StreamID, IsFin, Data, State#state{
+ req_body_start=ReqBody,
+ req_body_end=ReqBody,
+ req_body_length=byte_size(Data)
+ });
+data(StreamID, IsFin=fin, Data, State=#state{req_body_length=ReqBodyLen}) ->
+ ReqBodyEnd = erlang:monotonic_time(),
+ do_data(StreamID, IsFin, Data, State#state{
+ req_body_end=ReqBodyEnd,
+ req_body_length=ReqBodyLen + byte_size(Data)
+ });
+data(StreamID, IsFin, Data, State=#state{req_body_start=undefined}) ->
+ ReqBodyStart = erlang:monotonic_time(),
+ do_data(StreamID, IsFin, Data, State#state{
+ req_body_start=ReqBodyStart,
+ req_body_length=byte_size(Data)
+ });
+data(StreamID, IsFin, Data, State=#state{req_body_length=ReqBodyLen}) ->
+ do_data(StreamID, IsFin, Data, State#state{
+ req_body_length=ReqBodyLen + byte_size(Data)
+ }).
+
+do_data(StreamID, IsFin, Data, State0=#state{next=Next0}) ->
+ {Commands, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),
+ {Commands, fold(Commands, State0#state{next=Next})}.
+
+-spec info(cowboy_stream:streamid(), any(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+info(StreamID, Info={'EXIT', Pid, Reason}, State0=#state{procs=Procs}) ->
+ ProcEnd = erlang:monotonic_time(),
+ P = maps:get(Pid, Procs),
+ State = State0#state{procs=Procs#{Pid => P#{
+ exit => ProcEnd,
+ reason => Reason
+ }}},
+ do_info(StreamID, Info, State);
+info(StreamID, Info, State) ->
+ do_info(StreamID, Info, State).
+
+do_info(StreamID, Info, State0=#state{next=Next0}) ->
+ {Commands, Next} = cowboy_stream:info(StreamID, Info, Next0),
+ {Commands, fold(Commands, State0#state{next=Next})}.
+
+fold([], State) ->
+ State;
+fold([{spawn, Pid, _}|Tail], State0=#state{procs=Procs}) ->
+ ProcStart = erlang:monotonic_time(),
+ State = State0#state{procs=Procs#{Pid => #{spawn => ProcStart}}},
+ fold(Tail, State);
+fold([{inform, Status, Headers}|Tail],
+ State=#state{informational=Infos}) ->
+ Time = erlang:monotonic_time(),
+ fold(Tail, State#state{informational=[#{
+ status => Status,
+ headers => Headers,
+ time => Time
+ }|Infos]});
+fold([{response, Status, Headers, Body}|Tail],
+ State=#state{resp_headers_filter=RespHeadersFilter}) ->
+ Resp = erlang:monotonic_time(),
+ fold(Tail, State#state{
+ resp_status=Status,
+ resp_headers=case RespHeadersFilter of
+ undefined -> Headers;
+ _ -> RespHeadersFilter(Headers)
+ end,
+ resp_start=Resp,
+ resp_end=Resp,
+ resp_body_length=resp_body_length(Body)
+ });
+fold([{error_response, Status, Headers, Body}|Tail],
+ State=#state{resp_status=RespStatus}) ->
+ %% The error_response command only results in a response
+ %% if no response was sent before.
+ case RespStatus of
+ undefined ->
+ fold([{response, Status, Headers, Body}|Tail], State);
+ _ ->
+ fold(Tail, State)
+ end;
+fold([{headers, Status, Headers}|Tail],
+ State=#state{resp_headers_filter=RespHeadersFilter}) ->
+ RespStart = erlang:monotonic_time(),
+ fold(Tail, State#state{
+ resp_status=Status,
+ resp_headers=case RespHeadersFilter of
+ undefined -> Headers;
+ _ -> RespHeadersFilter(Headers)
+ end,
+ resp_start=RespStart
+ });
+%% @todo It might be worthwhile to keep the sendfile information around,
+%% especially if these frames ultimately result in a sendfile syscall.
+fold([{data, nofin, Data}|Tail], State=#state{resp_body_length=RespBodyLen}) ->
+ fold(Tail, State#state{
+ resp_body_length=RespBodyLen + resp_body_length(Data)
+ });
+fold([{data, fin, Data}|Tail], State=#state{resp_body_length=RespBodyLen}) ->
+ RespEnd = erlang:monotonic_time(),
+ fold(Tail, State#state{
+ resp_end=RespEnd,
+ resp_body_length=RespBodyLen + resp_body_length(Data)
+ });
+fold([{set_options, SetOpts}|Tail], State0=#state{user_data=OldUserData}) ->
+ State = case SetOpts of
+ #{metrics_user_data := NewUserData} ->
+ State0#state{user_data=maps:merge(OldUserData, NewUserData)};
+ _ ->
+ State0
+ end,
+ fold(Tail, State);
+fold([_|Tail], State) ->
+ fold(Tail, State).
+
+-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> any().
+terminate(StreamID, Reason, #state{next=Next, callback=Fun,
+ req=Req, resp_status=RespStatus, resp_headers=RespHeaders, ref=Ref,
+ req_start=ReqStart, req_body_start=ReqBodyStart,
+ req_body_end=ReqBodyEnd, resp_start=RespStart, resp_end=RespEnd,
+ procs=Procs, informational=Infos, user_data=UserData,
+ req_body_length=ReqBodyLen, resp_body_length=RespBodyLen}) ->
+ Res = cowboy_stream:terminate(StreamID, Reason, Next),
+ ReqEnd = erlang:monotonic_time(),
+ Metrics = #{
+ ref => Ref,
+ pid => self(),
+ streamid => StreamID,
+ reason => Reason,
+ req => Req,
+ resp_status => RespStatus,
+ resp_headers => RespHeaders,
+ req_start => ReqStart,
+ req_end => ReqEnd,
+ req_body_start => ReqBodyStart,
+ req_body_end => ReqBodyEnd,
+ resp_start => RespStart,
+ resp_end => RespEnd,
+ procs => Procs,
+ informational => lists:reverse(Infos),
+ req_body_length => ReqBodyLen,
+ resp_body_length => RespBodyLen,
+ user_data => UserData
+ },
+ Fun(Metrics),
+ Res.
+
+-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
+ cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
+ when Resp::cowboy_stream:resp_command().
+early_error(StreamID, Reason, PartialReq=#{ref := Ref}, Resp0, Opts=#{metrics_callback := Fun}) ->
+ Time = erlang:monotonic_time(),
+ Resp = {response, RespStatus, RespHeaders, RespBody}
+ = cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp0, Opts),
+ %% As far as metrics go we are limited in what we can provide
+ %% in this case.
+ Metrics = #{
+ ref => Ref,
+ pid => self(),
+ streamid => StreamID,
+ reason => Reason,
+ partial_req => PartialReq,
+ resp_status => RespStatus,
+ resp_headers => RespHeaders,
+ early_error_time => Time,
+ resp_body_length => resp_body_length(RespBody)
+ },
+ Fun(Metrics),
+ Resp.
+
+resp_body_length({sendfile, _, Len, _}) ->
+ Len;
+resp_body_length(Data) ->
+ iolist_size(Data).
diff --git a/server/_build/default/lib/cowboy/src/cowboy_middleware.erl b/server/_build/default/lib/cowboy/src/cowboy_middleware.erl
new file mode 100644
index 0000000..9a739f1
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_middleware.erl
@@ -0,0 +1,24 @@
+%% Copyright (c) 2013-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_middleware).
+
+-type env() :: #{atom() => any()}.
+-export_type([env/0]).
+
+-callback execute(Req, Env)
+ -> {ok, Req, Env}
+ | {suspend, module(), atom(), [any()]}
+ | {stop, Req}
+ when Req::cowboy_req:req(), Env::env().
diff --git a/server/_build/default/lib/cowboy/src/cowboy_req.erl b/server/_build/default/lib/cowboy/src/cowboy_req.erl
new file mode 100644
index 0000000..90c5a3a
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_req.erl
@@ -0,0 +1,1016 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_req).
+
+%% Request.
+-export([method/1]).
+-export([version/1]).
+-export([peer/1]).
+-export([sock/1]).
+-export([cert/1]).
+-export([scheme/1]).
+-export([host/1]).
+-export([host_info/1]).
+-export([port/1]).
+-export([path/1]).
+-export([path_info/1]).
+-export([qs/1]).
+-export([parse_qs/1]).
+-export([match_qs/2]).
+-export([uri/1]).
+-export([uri/2]).
+-export([binding/2]).
+-export([binding/3]).
+-export([bindings/1]).
+-export([header/2]).
+-export([header/3]).
+-export([headers/1]).
+-export([parse_header/2]).
+-export([parse_header/3]).
+-export([filter_cookies/2]).
+-export([parse_cookies/1]).
+-export([match_cookies/2]).
+
+%% Request body.
+-export([has_body/1]).
+-export([body_length/1]).
+-export([read_body/1]).
+-export([read_body/2]).
+-export([read_urlencoded_body/1]).
+-export([read_urlencoded_body/2]).
+-export([read_and_match_urlencoded_body/2]).
+-export([read_and_match_urlencoded_body/3]).
+
+%% Multipart.
+-export([read_part/1]).
+-export([read_part/2]).
+-export([read_part_body/1]).
+-export([read_part_body/2]).
+
+%% Response.
+-export([set_resp_cookie/3]).
+-export([set_resp_cookie/4]).
+-export([resp_header/2]).
+-export([resp_header/3]).
+-export([resp_headers/1]).
+-export([set_resp_header/3]).
+-export([set_resp_headers/2]).
+-export([has_resp_header/2]).
+-export([delete_resp_header/2]).
+-export([set_resp_body/2]).
+%% @todo set_resp_body/3 with a ContentType or even Headers argument, to set content headers.
+-export([has_resp_body/1]).
+-export([inform/2]).
+-export([inform/3]).
+-export([reply/2]).
+-export([reply/3]).
+-export([reply/4]).
+-export([stream_reply/2]).
+-export([stream_reply/3]).
+%% @todo stream_body/2 (nofin)
+-export([stream_body/3]).
+%% @todo stream_events/2 (nofin)
+-export([stream_events/3]).
+-export([stream_trailers/2]).
+-export([push/3]).
+-export([push/4]).
+
+%% Stream handlers.
+-export([cast/2]).
+
+%% Internal.
+-export([response_headers/2]).
+
+-type read_body_opts() :: #{
+ length => non_neg_integer() | infinity,
+ period => non_neg_integer(),
+ timeout => timeout()
+}.
+-export_type([read_body_opts/0]).
+
+%% While sendfile allows a Len of 0 that means "everything past Offset",
+%% Cowboy expects the real length as it is used as metadata.
+-type resp_body() :: iodata()
+ | {sendfile, non_neg_integer(), non_neg_integer(), file:name_all()}.
+-export_type([resp_body/0]).
+
+-type push_opts() :: #{
+ method => binary(),
+ scheme => binary(),
+ host => binary(),
+ port => inet:port_number(),
+ qs => binary()
+}.
+-export_type([push_opts/0]).
+
+-type req() :: #{
+ %% Public interface.
+ method := binary(),
+ version := cowboy:http_version() | atom(),
+ scheme := binary(),
+ host := binary(),
+ port := inet:port_number(),
+ path := binary(),
+ qs := binary(),
+ headers := cowboy:http_headers(),
+ peer := {inet:ip_address(), inet:port_number()},
+ sock := {inet:ip_address(), inet:port_number()},
+ cert := binary() | undefined,
+
+ %% Private interface.
+ ref := ranch:ref(),
+ pid := pid(),
+ streamid := cowboy_stream:streamid(),
+
+ host_info => cowboy_router:tokens(),
+ path_info => cowboy_router:tokens(),
+ bindings => cowboy_router:bindings(),
+
+ has_body := boolean(),
+ body_length := non_neg_integer() | undefined,
+ has_read_body => true,
+ multipart => {binary(), binary()} | done,
+
+ has_sent_resp => headers | true,
+ resp_cookies => #{iodata() => iodata()},
+ resp_headers => #{binary() => iodata()},
+ resp_body => resp_body(),
+
+ proxy_header => ranch_proxy_header:proxy_info(),
+ media_type => {binary(), binary(), [{binary(), binary()}]},
+ language => binary() | undefined,
+ charset => binary() | undefined,
+ range => {binary(), binary()
+ | [{non_neg_integer(), non_neg_integer() | infinity} | neg_integer()]},
+ websocket_version => 7 | 8 | 13,
+
+ %% The user is encouraged to use the Req to store information
+ %% when no better solution is available.
+ _ => _
+}.
+-export_type([req/0]).
+
+%% Request.
+
+-spec method(req()) -> binary().
+method(#{method := Method}) ->
+ Method.
+
+-spec version(req()) -> cowboy:http_version().
+version(#{version := Version}) ->
+ Version.
+
+-spec peer(req()) -> {inet:ip_address(), inet:port_number()}.
+peer(#{peer := Peer}) ->
+ Peer.
+
+-spec sock(req()) -> {inet:ip_address(), inet:port_number()}.
+sock(#{sock := Sock}) ->
+ Sock.
+
+-spec cert(req()) -> binary() | undefined.
+cert(#{cert := Cert}) ->
+ Cert.
+
+-spec scheme(req()) -> binary().
+scheme(#{scheme := Scheme}) ->
+ Scheme.
+
+-spec host(req()) -> binary().
+host(#{host := Host}) ->
+ Host.
+
+%% @todo The host_info is undefined if cowboy_router isn't used. Do we want to crash?
+-spec host_info(req()) -> cowboy_router:tokens() | undefined.
+host_info(#{host_info := HostInfo}) ->
+ HostInfo.
+
+-spec port(req()) -> inet:port_number().
+port(#{port := Port}) ->
+ Port.
+
+-spec path(req()) -> binary().
+path(#{path := Path}) ->
+ Path.
+
+%% @todo The path_info is undefined if cowboy_router isn't used. Do we want to crash?
+-spec path_info(req()) -> cowboy_router:tokens() | undefined.
+path_info(#{path_info := PathInfo}) ->
+ PathInfo.
+
+-spec qs(req()) -> binary().
+qs(#{qs := Qs}) ->
+ Qs.
+
+%% @todo Might be useful to limit the number of keys.
+-spec parse_qs(req()) -> [{binary(), binary() | true}].
+parse_qs(#{qs := Qs}) ->
+ try
+ cow_qs:parse_qs(Qs)
+ catch _:_:Stacktrace ->
+ erlang:raise(exit, {request_error, qs,
+ 'Malformed query string; application/x-www-form-urlencoded expected.'
+ }, Stacktrace)
+ end.
+
+-spec match_qs(cowboy:fields(), req()) -> map().
+match_qs(Fields, Req) ->
+ case filter(Fields, kvlist_to_map(Fields, parse_qs(Req))) of
+ {ok, Map} ->
+ Map;
+ {error, Errors} ->
+ exit({request_error, {match_qs, Errors},
+ 'Query string validation constraints failed for the reasons provided.'})
+ end.
+
+-spec uri(req()) -> iodata().
+uri(Req) ->
+ uri(Req, #{}).
+
+-spec uri(req(), map()) -> iodata().
+uri(#{scheme := Scheme0, host := Host0, port := Port0,
+ path := Path0, qs := Qs0}, Opts) ->
+ Scheme = case maps:get(scheme, Opts, Scheme0) of
+ S = undefined -> S;
+ S -> iolist_to_binary(S)
+ end,
+ Host = maps:get(host, Opts, Host0),
+ Port = maps:get(port, Opts, Port0),
+ {Path, Qs} = case maps:get(path, Opts, Path0) of
+ <<"*">> -> {<<>>, <<>>};
+ P -> {P, maps:get(qs, Opts, Qs0)}
+ end,
+ Fragment = maps:get(fragment, Opts, undefined),
+ [uri_host(Scheme, Scheme0, Port, Host), uri_path(Path), uri_qs(Qs), uri_fragment(Fragment)].
+
+uri_host(_, _, _, undefined) -> <<>>;
+uri_host(Scheme, Scheme0, Port, Host) ->
+ case iolist_size(Host) of
+ 0 -> <<>>;
+ _ -> [uri_scheme(Scheme), <<"//">>, Host, uri_port(Scheme, Scheme0, Port)]
+ end.
+
+uri_scheme(undefined) -> <<>>;
+uri_scheme(Scheme) ->
+ case iolist_size(Scheme) of
+ 0 -> Scheme;
+ _ -> [Scheme, $:]
+ end.
+
+uri_port(_, _, undefined) -> <<>>;
+uri_port(undefined, <<"http">>, 80) -> <<>>;
+uri_port(undefined, <<"https">>, 443) -> <<>>;
+uri_port(<<"http">>, _, 80) -> <<>>;
+uri_port(<<"https">>, _, 443) -> <<>>;
+uri_port(_, _, Port) ->
+ [$:, integer_to_binary(Port)].
+
+uri_path(undefined) -> <<>>;
+uri_path(Path) -> Path.
+
+uri_qs(undefined) -> <<>>;
+uri_qs(Qs) ->
+ case iolist_size(Qs) of
+ 0 -> Qs;
+ _ -> [$?, Qs]
+ end.
+
+uri_fragment(undefined) -> <<>>;
+uri_fragment(Fragment) ->
+ case iolist_size(Fragment) of
+ 0 -> Fragment;
+ _ -> [$#, Fragment]
+ end.
+
+-ifdef(TEST).
+uri1_test() ->
+ <<"http://localhost/path">> = iolist_to_binary(uri(#{
+ scheme => <<"http">>, host => <<"localhost">>, port => 80,
+ path => <<"/path">>, qs => <<>>})),
+ <<"http://localhost:443/path">> = iolist_to_binary(uri(#{
+ scheme => <<"http">>, host => <<"localhost">>, port => 443,
+ path => <<"/path">>, qs => <<>>})),
+ <<"http://localhost:8080/path">> = iolist_to_binary(uri(#{
+ scheme => <<"http">>, host => <<"localhost">>, port => 8080,
+ path => <<"/path">>, qs => <<>>})),
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(#{
+ scheme => <<"http">>, host => <<"localhost">>, port => 8080,
+ path => <<"/path">>, qs => <<"dummy=2785">>})),
+ <<"https://localhost/path">> = iolist_to_binary(uri(#{
+ scheme => <<"https">>, host => <<"localhost">>, port => 443,
+ path => <<"/path">>, qs => <<>>})),
+ <<"https://localhost:8443/path">> = iolist_to_binary(uri(#{
+ scheme => <<"https">>, host => <<"localhost">>, port => 8443,
+ path => <<"/path">>, qs => <<>>})),
+ <<"https://localhost:8443/path?dummy=2785">> = iolist_to_binary(uri(#{
+ scheme => <<"https">>, host => <<"localhost">>, port => 8443,
+ path => <<"/path">>, qs => <<"dummy=2785">>})),
+ ok.
+
+uri2_test() ->
+ Req = #{
+ scheme => <<"http">>, host => <<"localhost">>, port => 8080,
+ path => <<"/path">>, qs => <<"dummy=2785">>
+ },
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{})),
+ %% Disable individual components.
+ <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => undefined})),
+ <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => undefined})),
+ <<"http://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => undefined})),
+ <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => undefined})),
+ <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => undefined})),
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => undefined})),
+ <<"http://localhost:8080">> = iolist_to_binary(uri(Req, #{path => undefined, qs => undefined})),
+ <<>> = iolist_to_binary(uri(Req, #{host => undefined, path => undefined, qs => undefined})),
+ %% Empty values.
+ <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => <<>>})),
+ <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => ""})),
+ <<"//localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => [<<>>]})),
+ <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => <<>>})),
+ <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => ""})),
+ <<"/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => [<<>>]})),
+ <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => <<>>})),
+ <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => ""})),
+ <<"http://localhost:8080?dummy=2785">> = iolist_to_binary(uri(Req, #{path => [<<>>]})),
+ <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => <<>>})),
+ <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => ""})),
+ <<"http://localhost:8080/path">> = iolist_to_binary(uri(Req, #{qs => [<<>>]})),
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => <<>>})),
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => ""})),
+ <<"http://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{fragment => [<<>>]})),
+ %% Port is integer() | undefined.
+ {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => <<>>}))),
+ {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => ""}))),
+ {'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => [<<>>]}))),
+ %% Update components.
+ <<"https://localhost:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => "https"})),
+ <<"http://example.org:8080/path?dummy=2785">> = iolist_to_binary(uri(Req, #{host => "example.org"})),
+ <<"http://localhost:123/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => 123})),
+ <<"http://localhost:8080/custom?dummy=2785">> = iolist_to_binary(uri(Req, #{path => "/custom"})),
+ <<"http://localhost:8080/path?smart=42">> = iolist_to_binary(uri(Req, #{qs => "smart=42"})),
+ <<"http://localhost:8080/path?dummy=2785#intro">> = iolist_to_binary(uri(Req, #{fragment => "intro"})),
+ %% Interesting combinations.
+ <<"http://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{port => 80})),
+ <<"https://localhost/path?dummy=2785">> = iolist_to_binary(uri(Req, #{scheme => "https", port => 443})),
+ ok.
+-endif.
+
+-spec binding(atom(), req()) -> any() | undefined.
+binding(Name, Req) ->
+ binding(Name, Req, undefined).
+
+-spec binding(atom(), req(), Default) -> any() | Default when Default::any().
+binding(Name, #{bindings := Bindings}, Default) when is_atom(Name) ->
+ case Bindings of
+ #{Name := Value} -> Value;
+ _ -> Default
+ end;
+binding(Name, _, Default) when is_atom(Name) ->
+ Default.
+
+-spec bindings(req()) -> cowboy_router:bindings().
+bindings(#{bindings := Bindings}) ->
+ Bindings;
+bindings(_) ->
+ #{}.
+
+-spec header(binary(), req()) -> binary() | undefined.
+header(Name, Req) ->
+ header(Name, Req, undefined).
+
+-spec header(binary(), req(), Default) -> binary() | Default when Default::any().
+header(Name, #{headers := Headers}, Default) ->
+ maps:get(Name, Headers, Default).
+
+-spec headers(req()) -> cowboy:http_headers().
+headers(#{headers := Headers}) ->
+ Headers.
+
+-spec parse_header(binary(), Req) -> any() when Req::req().
+parse_header(Name = <<"content-length">>, Req) ->
+ parse_header(Name, Req, 0);
+parse_header(Name = <<"cookie">>, Req) ->
+ parse_header(Name, Req, []);
+parse_header(Name, Req) ->
+ parse_header(Name, Req, undefined).
+
+-spec parse_header(binary(), Req, any()) -> any() when Req::req().
+parse_header(Name, Req, Default) ->
+ try
+ parse_header(Name, Req, Default, parse_header_fun(Name))
+ catch _:_:Stacktrace ->
+ erlang:raise(exit, {request_error, {header, Name},
+ 'Malformed header. Please consult the relevant specification.'
+ }, Stacktrace)
+ end.
+
+parse_header_fun(<<"accept">>) -> fun cow_http_hd:parse_accept/1;
+parse_header_fun(<<"accept-charset">>) -> fun cow_http_hd:parse_accept_charset/1;
+parse_header_fun(<<"accept-encoding">>) -> fun cow_http_hd:parse_accept_encoding/1;
+parse_header_fun(<<"accept-language">>) -> fun cow_http_hd:parse_accept_language/1;
+parse_header_fun(<<"access-control-request-headers">>) -> fun cow_http_hd:parse_access_control_request_headers/1;
+parse_header_fun(<<"access-control-request-method">>) -> fun cow_http_hd:parse_access_control_request_method/1;
+parse_header_fun(<<"authorization">>) -> fun cow_http_hd:parse_authorization/1;
+parse_header_fun(<<"connection">>) -> fun cow_http_hd:parse_connection/1;
+parse_header_fun(<<"content-encoding">>) -> fun cow_http_hd:parse_content_encoding/1;
+parse_header_fun(<<"content-language">>) -> fun cow_http_hd:parse_content_language/1;
+parse_header_fun(<<"content-length">>) -> fun cow_http_hd:parse_content_length/1;
+parse_header_fun(<<"content-type">>) -> fun cow_http_hd:parse_content_type/1;
+parse_header_fun(<<"cookie">>) -> fun cow_cookie:parse_cookie/1;
+parse_header_fun(<<"expect">>) -> fun cow_http_hd:parse_expect/1;
+parse_header_fun(<<"if-match">>) -> fun cow_http_hd:parse_if_match/1;
+parse_header_fun(<<"if-modified-since">>) -> fun cow_http_hd:parse_if_modified_since/1;
+parse_header_fun(<<"if-none-match">>) -> fun cow_http_hd:parse_if_none_match/1;
+parse_header_fun(<<"if-range">>) -> fun cow_http_hd:parse_if_range/1;
+parse_header_fun(<<"if-unmodified-since">>) -> fun cow_http_hd:parse_if_unmodified_since/1;
+parse_header_fun(<<"max-forwards">>) -> fun cow_http_hd:parse_max_forwards/1;
+parse_header_fun(<<"origin">>) -> fun cow_http_hd:parse_origin/1;
+parse_header_fun(<<"proxy-authorization">>) -> fun cow_http_hd:parse_proxy_authorization/1;
+parse_header_fun(<<"range">>) -> fun cow_http_hd:parse_range/1;
+parse_header_fun(<<"sec-websocket-extensions">>) -> fun cow_http_hd:parse_sec_websocket_extensions/1;
+parse_header_fun(<<"sec-websocket-protocol">>) -> fun cow_http_hd:parse_sec_websocket_protocol_req/1;
+parse_header_fun(<<"sec-websocket-version">>) -> fun cow_http_hd:parse_sec_websocket_version_req/1;
+parse_header_fun(<<"trailer">>) -> fun cow_http_hd:parse_trailer/1;
+parse_header_fun(<<"upgrade">>) -> fun cow_http_hd:parse_upgrade/1;
+parse_header_fun(<<"x-forwarded-for">>) -> fun cow_http_hd:parse_x_forwarded_for/1.
+
+parse_header(Name, Req, Default, ParseFun) ->
+ case header(Name, Req) of
+ undefined -> Default;
+ Value -> ParseFun(Value)
+ end.
+
+-spec filter_cookies([atom() | binary()], Req) -> Req when Req::req().
+filter_cookies(Names0, Req=#{headers := Headers}) ->
+ Names = [if
+ is_atom(N) -> atom_to_binary(N, utf8);
+ true -> N
+ end || N <- Names0],
+ case header(<<"cookie">>, Req) of
+ undefined -> Req;
+ Value0 ->
+ Cookies0 = binary:split(Value0, <<$;>>),
+ Cookies = lists:filter(fun(Cookie) ->
+ lists:member(cookie_name(Cookie), Names)
+ end, Cookies0),
+ Value = iolist_to_binary(lists:join($;, Cookies)),
+ Req#{headers => Headers#{<<"cookie">> => Value}}
+ end.
+
+%% This is a specialized function to extract a cookie name
+%% regardless of whether the name is valid or not. We skip
+%% whitespace at the beginning and take whatever's left to
+%% be the cookie name, up to the = sign.
+cookie_name(<<$\s, Rest/binary>>) -> cookie_name(Rest);
+cookie_name(<<$\t, Rest/binary>>) -> cookie_name(Rest);
+cookie_name(Name) -> cookie_name(Name, <<>>).
+
+cookie_name(<<>>, Name) -> Name;
+cookie_name(<<$=, _/bits>>, Name) -> Name;
+cookie_name(<<C, Rest/bits>>, Acc) -> cookie_name(Rest, <<Acc/binary, C>>).
+
+-spec parse_cookies(req()) -> [{binary(), binary()}].
+parse_cookies(Req) ->
+ parse_header(<<"cookie">>, Req).
+
+-spec match_cookies(cowboy:fields(), req()) -> map().
+match_cookies(Fields, Req) ->
+ case filter(Fields, kvlist_to_map(Fields, parse_cookies(Req))) of
+ {ok, Map} ->
+ Map;
+ {error, Errors} ->
+ exit({request_error, {match_cookies, Errors},
+ 'Cookie validation constraints failed for the reasons provided.'})
+ end.
+
+%% Request body.
+
+-spec has_body(req()) -> boolean().
+has_body(#{has_body := HasBody}) ->
+ HasBody.
+
+%% The length may not be known if HTTP/1.1 with a transfer-encoding;
+%% or HTTP/2 with no content-length header. The length is always
+%% known once the body has been completely read.
+-spec body_length(req()) -> undefined | non_neg_integer().
+body_length(#{body_length := Length}) ->
+ Length.
+
+-spec read_body(Req) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().
+read_body(Req) ->
+ read_body(Req, #{}).
+
+-spec read_body(Req, read_body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().
+read_body(Req=#{has_body := false}, _) ->
+ {ok, <<>>, Req};
+read_body(Req=#{has_read_body := true}, _) ->
+ {ok, <<>>, Req};
+read_body(Req, Opts) ->
+ Length = maps:get(length, Opts, 8000000),
+ Period = maps:get(period, Opts, 15000),
+ Timeout = maps:get(timeout, Opts, Period + 1000),
+ Ref = make_ref(),
+ cast({read_body, self(), Ref, Length, Period}, Req),
+ receive
+ {request_body, Ref, nofin, Body} ->
+ {more, Body, Req};
+ {request_body, Ref, fin, BodyLength, Body} ->
+ {ok, Body, set_body_length(Req, BodyLength)}
+ after Timeout ->
+ exit(timeout)
+ end.
+
+set_body_length(Req=#{headers := Headers}, BodyLength) ->
+ Req#{
+ headers => Headers#{<<"content-length">> => integer_to_binary(BodyLength)},
+ body_length => BodyLength,
+ has_read_body => true
+ }.
+
+-spec read_urlencoded_body(Req) -> {ok, [{binary(), binary() | true}], Req} when Req::req().
+read_urlencoded_body(Req) ->
+ read_urlencoded_body(Req, #{length => 64000, period => 5000}).
+
+-spec read_urlencoded_body(Req, read_body_opts()) -> {ok, [{binary(), binary() | true}], Req} when Req::req().
+read_urlencoded_body(Req0, Opts) ->
+ case read_body(Req0, Opts) of
+ {ok, Body, Req} ->
+ try
+ {ok, cow_qs:parse_qs(Body), Req}
+ catch _:_:Stacktrace ->
+ erlang:raise(exit, {request_error, urlencoded_body,
+ 'Malformed body; application/x-www-form-urlencoded expected.'
+ }, Stacktrace)
+ end;
+ {more, Body, _} ->
+ Length = maps:get(length, Opts, 64000),
+ if
+ byte_size(Body) < Length ->
+ exit({request_error, timeout,
+ 'The request body was not received within the configured time.'});
+ true ->
+ exit({request_error, payload_too_large,
+ 'The request body is larger than allowed by configuration.'})
+ end
+ end.
+
+-spec read_and_match_urlencoded_body(cowboy:fields(), Req)
+ -> {ok, map(), Req} when Req::req().
+read_and_match_urlencoded_body(Fields, Req) ->
+ read_and_match_urlencoded_body(Fields, Req, #{length => 64000, period => 5000}).
+
+-spec read_and_match_urlencoded_body(cowboy:fields(), Req, read_body_opts())
+ -> {ok, map(), Req} when Req::req().
+read_and_match_urlencoded_body(Fields, Req0, Opts) ->
+ {ok, Qs, Req} = read_urlencoded_body(Req0, Opts),
+ case filter(Fields, kvlist_to_map(Fields, Qs)) of
+ {ok, Map} ->
+ {ok, Map, Req};
+ {error, Errors} ->
+ exit({request_error, {read_and_match_urlencoded_body, Errors},
+ 'Urlencoded request body validation constraints failed for the reasons provided.'})
+ end.
+
+%% Multipart.
+
+-spec read_part(Req)
+ -> {ok, cowboy:http_headers(), Req} | {done, Req}
+ when Req::req().
+read_part(Req) ->
+ read_part(Req, #{length => 64000, period => 5000}).
+
+-spec read_part(Req, read_body_opts())
+ -> {ok, cowboy:http_headers(), Req} | {done, Req}
+ when Req::req().
+read_part(Req, Opts) ->
+ case maps:is_key(multipart, Req) of
+ true ->
+ {Data, Req2} = stream_multipart(Req, Opts, headers),
+ read_part(Data, Opts, Req2);
+ false ->
+ read_part(init_multipart(Req), Opts)
+ end.
+
+read_part(Buffer, Opts, Req=#{multipart := {Boundary, _}}) ->
+ try cow_multipart:parse_headers(Buffer, Boundary) of
+ more ->
+ {Data, Req2} = stream_multipart(Req, Opts, headers),
+ read_part(<< Buffer/binary, Data/binary >>, Opts, Req2);
+ {more, Buffer2} ->
+ {Data, Req2} = stream_multipart(Req, Opts, headers),
+ read_part(<< Buffer2/binary, Data/binary >>, Opts, Req2);
+ {ok, Headers0, Rest} ->
+ Headers = maps:from_list(Headers0),
+ %% Reject multipart content containing duplicate headers.
+ true = map_size(Headers) =:= length(Headers0),
+ {ok, Headers, Req#{multipart => {Boundary, Rest}}};
+ %% Ignore epilogue.
+ {done, _} ->
+ {done, Req#{multipart => done}}
+ catch _:_:Stacktrace ->
+ erlang:raise(exit, {request_error, {multipart, headers},
+ 'Malformed body; multipart expected.'
+ }, Stacktrace)
+ end.
+
+-spec read_part_body(Req)
+ -> {ok, binary(), Req} | {more, binary(), Req}
+ when Req::req().
+read_part_body(Req) ->
+ read_part_body(Req, #{}).
+
+-spec read_part_body(Req, read_body_opts())
+ -> {ok, binary(), Req} | {more, binary(), Req}
+ when Req::req().
+read_part_body(Req, Opts) ->
+ case maps:is_key(multipart, Req) of
+ true ->
+ read_part_body(<<>>, Opts, Req, <<>>);
+ false ->
+ read_part_body(init_multipart(Req), Opts)
+ end.
+
+read_part_body(Buffer, Opts, Req=#{multipart := {Boundary, _}}, Acc) ->
+ Length = maps:get(length, Opts, 8000000),
+ case byte_size(Acc) > Length of
+ true ->
+ {more, Acc, Req#{multipart => {Boundary, Buffer}}};
+ false ->
+ {Data, Req2} = stream_multipart(Req, Opts, body),
+ case cow_multipart:parse_body(<< Buffer/binary, Data/binary >>, Boundary) of
+ {ok, Body} ->
+ read_part_body(<<>>, Opts, Req2, << Acc/binary, Body/binary >>);
+ {ok, Body, Rest} ->
+ read_part_body(Rest, Opts, Req2, << Acc/binary, Body/binary >>);
+ done ->
+ {ok, Acc, Req2};
+ {done, Body} ->
+ {ok, << Acc/binary, Body/binary >>, Req2};
+ {done, Body, Rest} ->
+ {ok, << Acc/binary, Body/binary >>,
+ Req2#{multipart => {Boundary, Rest}}}
+ end
+ end.
+
+init_multipart(Req) ->
+ {<<"multipart">>, _, Params} = parse_header(<<"content-type">>, Req),
+ case lists:keyfind(<<"boundary">>, 1, Params) of
+ {_, Boundary} ->
+ Req#{multipart => {Boundary, <<>>}};
+ false ->
+ exit({request_error, {multipart, boundary},
+ 'Missing boundary parameter for multipart media type.'})
+ end.
+
+stream_multipart(Req=#{multipart := done}, _, _) ->
+ {<<>>, Req};
+stream_multipart(Req=#{multipart := {_, <<>>}}, Opts, Type) ->
+ case read_body(Req, Opts) of
+ {more, Data, Req2} ->
+ {Data, Req2};
+ %% We crash when the data ends unexpectedly.
+ {ok, <<>>, _} ->
+ exit({request_error, {multipart, Type},
+ 'Malformed body; multipart expected.'});
+ {ok, Data, Req2} ->
+ {Data, Req2}
+ end;
+stream_multipart(Req=#{multipart := {Boundary, Buffer}}, _, _) ->
+ {Buffer, Req#{multipart => {Boundary, <<>>}}}.
+
+%% Response.
+
+-spec set_resp_cookie(iodata(), iodata(), Req)
+ -> Req when Req::req().
+set_resp_cookie(Name, Value, Req) ->
+ set_resp_cookie(Name, Value, Req, #{}).
+
+%% The cookie name cannot contain any of the following characters:
+%% =,;\s\t\r\n\013\014
+%%
+%% The cookie value cannot contain any of the following characters:
+%% ,; \t\r\n\013\014
+-spec set_resp_cookie(binary(), iodata(), Req, cow_cookie:cookie_opts())
+ -> Req when Req::req().
+set_resp_cookie(Name, Value, Req, Opts) ->
+ Cookie = cow_cookie:setcookie(Name, Value, Opts),
+ RespCookies = maps:get(resp_cookies, Req, #{}),
+ Req#{resp_cookies => RespCookies#{Name => Cookie}}.
+
+%% @todo We could add has_resp_cookie and delete_resp_cookie now.
+
+-spec set_resp_header(binary(), iodata(), Req)
+ -> Req when Req::req().
+set_resp_header(Name, Value, Req=#{resp_headers := RespHeaders}) ->
+ Req#{resp_headers => RespHeaders#{Name => Value}};
+set_resp_header(Name,Value, Req) ->
+ Req#{resp_headers => #{Name => Value}}.
+
+-spec set_resp_headers(cowboy:http_headers(), Req)
+ -> Req when Req::req().
+set_resp_headers(Headers, Req=#{resp_headers := RespHeaders}) ->
+ Req#{resp_headers => maps:merge(RespHeaders, Headers)};
+set_resp_headers(Headers, Req) ->
+ Req#{resp_headers => Headers}.
+
+-spec resp_header(binary(), req()) -> binary() | undefined.
+resp_header(Name, Req) ->
+ resp_header(Name, Req, undefined).
+
+-spec resp_header(binary(), req(), Default)
+ -> binary() | Default when Default::any().
+resp_header(Name, #{resp_headers := Headers}, Default) ->
+ maps:get(Name, Headers, Default);
+resp_header(_, #{}, Default) ->
+ Default.
+
+-spec resp_headers(req()) -> cowboy:http_headers().
+resp_headers(#{resp_headers := RespHeaders}) ->
+ RespHeaders;
+resp_headers(#{}) ->
+ #{}.
+
+-spec set_resp_body(resp_body(), Req) -> Req when Req::req().
+set_resp_body(Body, Req) ->
+ Req#{resp_body => Body}.
+
+-spec has_resp_header(binary(), req()) -> boolean().
+has_resp_header(Name, #{resp_headers := RespHeaders}) ->
+ maps:is_key(Name, RespHeaders);
+has_resp_header(_, _) ->
+ false.
+
+-spec has_resp_body(req()) -> boolean().
+has_resp_body(#{resp_body := {sendfile, _, _, _}}) ->
+ true;
+has_resp_body(#{resp_body := RespBody}) ->
+ iolist_size(RespBody) > 0;
+has_resp_body(_) ->
+ false.
+
+-spec delete_resp_header(binary(), Req)
+ -> Req when Req::req().
+delete_resp_header(Name, Req=#{resp_headers := RespHeaders}) ->
+ Req#{resp_headers => maps:remove(Name, RespHeaders)};
+%% There are no resp headers so we have nothing to delete.
+delete_resp_header(_, Req) ->
+ Req.
+
+-spec inform(cowboy:http_status(), req()) -> ok.
+inform(Status, Req) ->
+ inform(Status, #{}, Req).
+
+-spec inform(cowboy:http_status(), cowboy:http_headers(), req()) -> ok.
+inform(_, _, #{has_sent_resp := _}) ->
+ error(function_clause); %% @todo Better error message.
+inform(Status, Headers, Req) when is_integer(Status); is_binary(Status) ->
+ cast({inform, Status, Headers}, Req).
+
+-spec reply(cowboy:http_status(), Req) -> Req when Req::req().
+reply(Status, Req) ->
+ reply(Status, #{}, Req).
+
+-spec reply(cowboy:http_status(), cowboy:http_headers(), Req)
+ -> Req when Req::req().
+reply(Status, Headers, Req=#{resp_body := Body}) ->
+ reply(Status, Headers, Body, Req);
+reply(Status, Headers, Req) ->
+ reply(Status, Headers, <<>>, Req).
+
+-spec reply(cowboy:http_status(), cowboy:http_headers(), resp_body(), Req)
+ -> Req when Req::req().
+reply(_, _, _, #{has_sent_resp := _}) ->
+ error(function_clause); %% @todo Better error message.
+reply(Status, Headers, {sendfile, _, 0, _}, Req)
+ when is_integer(Status); is_binary(Status) ->
+ do_reply(Status, Headers#{
+ <<"content-length">> => <<"0">>
+ }, <<>>, Req);
+reply(Status, Headers, SendFile = {sendfile, _, Len, _}, Req)
+ when is_integer(Status); is_binary(Status) ->
+ do_reply(Status, Headers#{
+ <<"content-length">> => integer_to_binary(Len)
+ }, SendFile, Req);
+%% 204 responses must not include content-length. 304 responses may
+%% but only when set explicitly. (RFC7230 3.3.1, RFC7230 3.3.2)
+%% Neither status code must include a response body. (RFC7230 3.3)
+reply(Status, Headers, Body, Req)
+ when Status =:= 204; Status =:= 304 ->
+ 0 = iolist_size(Body),
+ do_reply(Status, Headers, Body, Req);
+reply(Status = <<"204",_/bits>>, Headers, Body, Req) ->
+ 0 = iolist_size(Body),
+ do_reply(Status, Headers, Body, Req);
+reply(Status = <<"304",_/bits>>, Headers, Body, Req) ->
+ 0 = iolist_size(Body),
+ do_reply(Status, Headers, Body, Req);
+reply(Status, Headers, Body, Req)
+ when is_integer(Status); is_binary(Status) ->
+ do_reply(Status, Headers#{
+ <<"content-length">> => integer_to_binary(iolist_size(Body))
+ }, Body, Req).
+
+%% Don't send any body for HEAD responses. While the protocol code is
+%% supposed to enforce this rule, we prefer to avoid copying too much
+%% data around if we can avoid it.
+do_reply(Status, Headers, _, Req=#{method := <<"HEAD">>}) ->
+ cast({response, Status, response_headers(Headers, Req), <<>>}, Req),
+ done_replying(Req, true);
+do_reply(Status, Headers, Body, Req) ->
+ cast({response, Status, response_headers(Headers, Req), Body}, Req),
+ done_replying(Req, true).
+
+done_replying(Req, HasSentResp) ->
+ maps:without([resp_cookies, resp_headers, resp_body], Req#{has_sent_resp => HasSentResp}).
+
+-spec stream_reply(cowboy:http_status(), Req) -> Req when Req::req().
+stream_reply(Status, Req) ->
+ stream_reply(Status, #{}, Req).
+
+-spec stream_reply(cowboy:http_status(), cowboy:http_headers(), Req)
+ -> Req when Req::req().
+stream_reply(_, _, #{has_sent_resp := _}) ->
+ error(function_clause);
+%% 204 and 304 responses must NOT send a body. We therefore
+%% transform the call to a full response and expect the user
+%% to NOT call stream_body/3 afterwards. (RFC7230 3.3)
+stream_reply(Status = 204, Headers=#{}, Req) ->
+ reply(Status, Headers, <<>>, Req);
+stream_reply(Status = <<"204",_/bits>>, Headers=#{}, Req) ->
+ reply(Status, Headers, <<>>, Req);
+stream_reply(Status = 304, Headers=#{}, Req) ->
+ reply(Status, Headers, <<>>, Req);
+stream_reply(Status = <<"304",_/bits>>, Headers=#{}, Req) ->
+ reply(Status, Headers, <<>>, Req);
+stream_reply(Status, Headers=#{}, Req) when is_integer(Status); is_binary(Status) ->
+ cast({headers, Status, response_headers(Headers, Req)}, Req),
+ done_replying(Req, headers).
+
+-spec stream_body(resp_body(), fin | nofin, req()) -> ok.
+%% Error out if headers were not sent.
+%% Don't send any body for HEAD responses.
+stream_body(_, _, #{method := <<"HEAD">>, has_sent_resp := headers}) ->
+ ok;
+%% Don't send a message if the data is empty, except for the
+%% very last message with IsFin=fin. When using sendfile this
+%% is converted to a data tuple, however.
+stream_body({sendfile, _, 0, _}, nofin, _) ->
+ ok;
+stream_body({sendfile, _, 0, _}, IsFin=fin, Req=#{has_sent_resp := headers}) ->
+ stream_body({data, self(), IsFin, <<>>}, Req);
+stream_body({sendfile, O, B, P}, IsFin, Req=#{has_sent_resp := headers})
+ when is_integer(O), O >= 0, is_integer(B), B > 0 ->
+ stream_body({data, self(), IsFin, {sendfile, O, B, P}}, Req);
+stream_body(Data, IsFin=nofin, Req=#{has_sent_resp := headers})
+ when not is_tuple(Data) ->
+ case iolist_size(Data) of
+ 0 -> ok;
+ _ -> stream_body({data, self(), IsFin, Data}, Req)
+ end;
+stream_body(Data, IsFin, Req=#{has_sent_resp := headers})
+ when not is_tuple(Data) ->
+ stream_body({data, self(), IsFin, Data}, Req).
+
+%% @todo Do we need a timeout?
+stream_body(Msg, Req=#{pid := Pid}) ->
+ cast(Msg, Req),
+ receive {data_ack, Pid} -> ok end.
+
+-spec stream_events(cow_sse:event() | [cow_sse:event()], fin | nofin, req()) -> ok.
+stream_events(Event, IsFin, Req) when is_map(Event) ->
+ stream_events([Event], IsFin, Req);
+stream_events(Events, IsFin, Req=#{has_sent_resp := headers}) ->
+ stream_body({data, self(), IsFin, cow_sse:events(Events)}, Req).
+
+-spec stream_trailers(cowboy:http_headers(), req()) -> ok.
+stream_trailers(Trailers, Req=#{has_sent_resp := headers}) ->
+ cast({trailers, Trailers}, Req).
+
+-spec push(iodata(), cowboy:http_headers(), req()) -> ok.
+push(Path, Headers, Req) ->
+ push(Path, Headers, Req, #{}).
+
+%% @todo Optimization: don't send anything at all for HTTP/1.0 and HTTP/1.1.
+%% @todo Path, Headers, Opts, everything should be in proper binary,
+%% or normalized when creating the Req object.
+-spec push(iodata(), cowboy:http_headers(), req(), push_opts()) -> ok.
+push(Path, Headers, Req=#{scheme := Scheme0, host := Host0, port := Port0}, Opts) ->
+ Method = maps:get(method, Opts, <<"GET">>),
+ Scheme = maps:get(scheme, Opts, Scheme0),
+ Host = maps:get(host, Opts, Host0),
+ Port = maps:get(port, Opts, Port0),
+ Qs = maps:get(qs, Opts, <<>>),
+ cast({push, Method, Scheme, Host, Port, Path, Qs, Headers}, Req).
+
+%% Stream handlers.
+
+-spec cast(any(), req()) -> ok.
+cast(Msg, #{pid := Pid, streamid := StreamID}) ->
+ Pid ! {{Pid, StreamID}, Msg},
+ ok.
+
+%% Internal.
+
+%% @todo What about set-cookie headers set through set_resp_header or reply?
+-spec response_headers(Headers, req()) -> Headers when Headers::cowboy:http_headers().
+response_headers(Headers0, Req) ->
+ RespHeaders = maps:get(resp_headers, Req, #{}),
+ Headers = maps:merge(#{
+ <<"date">> => cowboy_clock:rfc1123(),
+ <<"server">> => <<"Cowboy">>
+ }, maps:merge(RespHeaders, Headers0)),
+ %% The set-cookie header is special; we can only send one cookie per header.
+ %% We send the list of values for many cookies in one key of the map,
+ %% and let the protocols deal with it directly.
+ case maps:get(resp_cookies, Req, undefined) of
+ undefined -> Headers;
+ RespCookies -> Headers#{<<"set-cookie">> => maps:values(RespCookies)}
+ end.
+
+%% Create map, convert keys to atoms and group duplicate keys into lists.
+%% Keys that are not found in the user provided list are entirely skipped.
+%% @todo Can probably be done directly while parsing.
+kvlist_to_map(Fields, KvList) ->
+ Keys = [case K of
+ {Key, _} -> Key;
+ {Key, _, _} -> Key;
+ Key -> Key
+ end || K <- Fields],
+ kvlist_to_map(Keys, KvList, #{}).
+
+kvlist_to_map(_, [], Map) ->
+ Map;
+kvlist_to_map(Keys, [{Key, Value}|Tail], Map) ->
+ try binary_to_existing_atom(Key, utf8) of
+ Atom ->
+ case lists:member(Atom, Keys) of
+ true ->
+ case maps:find(Atom, Map) of
+ {ok, MapValue} when is_list(MapValue) ->
+ kvlist_to_map(Keys, Tail,
+ Map#{Atom => [Value|MapValue]});
+ {ok, MapValue} ->
+ kvlist_to_map(Keys, Tail,
+ Map#{Atom => [Value, MapValue]});
+ error ->
+ kvlist_to_map(Keys, Tail,
+ Map#{Atom => Value})
+ end;
+ false ->
+ kvlist_to_map(Keys, Tail, Map)
+ end
+ catch error:badarg ->
+ kvlist_to_map(Keys, Tail, Map)
+ end.
+
+filter(Fields, Map0) ->
+ filter(Fields, Map0, #{}).
+
+%% Loop through fields, if value is missing and no default,
+%% record the error; else if value is missing and has a
+%% default, set default; otherwise apply constraints. If
+%% constraint fails, record the error.
+%%
+%% When there is an error at the end, crash.
+filter([], Map, Errors) ->
+ case maps:size(Errors) of
+ 0 -> {ok, Map};
+ _ -> {error, Errors}
+ end;
+filter([{Key, Constraints}|Tail], Map, Errors) ->
+ filter_constraints(Tail, Map, Errors, Key, maps:get(Key, Map), Constraints);
+filter([{Key, Constraints, Default}|Tail], Map, Errors) ->
+ case maps:find(Key, Map) of
+ {ok, Value} ->
+ filter_constraints(Tail, Map, Errors, Key, Value, Constraints);
+ error ->
+ filter(Tail, Map#{Key => Default}, Errors)
+ end;
+filter([Key|Tail], Map, Errors) ->
+ case maps:is_key(Key, Map) of
+ true ->
+ filter(Tail, Map, Errors);
+ false ->
+ filter(Tail, Map, Errors#{Key => required})
+ end.
+
+filter_constraints(Tail, Map, Errors, Key, Value0, Constraints) ->
+ case cowboy_constraints:validate(Value0, Constraints) of
+ {ok, Value} ->
+ filter(Tail, Map#{Key => Value}, Errors);
+ {error, Reason} ->
+ filter(Tail, Map, Errors#{Key => Reason})
+ end.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_rest.erl b/server/_build/default/lib/cowboy/src/cowboy_rest.erl
new file mode 100644
index 0000000..7d0fe80
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_rest.erl
@@ -0,0 +1,1637 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% Originally based on the Webmachine Diagram from Alan Dean and
+%% Justin Sheehy.
+-module(cowboy_rest).
+-behaviour(cowboy_sub_protocol).
+
+-export([upgrade/4]).
+-export([upgrade/5]).
+
+-type switch_handler() :: {switch_handler, module()}
+ | {switch_handler, module(), any()}.
+
+%% Common handler callbacks.
+
+-callback init(Req, any())
+ -> {ok | module(), Req, any()}
+ | {module(), Req, any(), any()}
+ when Req::cowboy_req:req().
+
+-callback terminate(any(), cowboy_req:req(), any()) -> ok.
+-optional_callbacks([terminate/3]).
+
+%% REST handler callbacks.
+
+-callback allowed_methods(Req, State)
+ -> {[binary()], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([allowed_methods/2]).
+
+-callback allow_missing_post(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([allow_missing_post/2]).
+
+-callback charsets_provided(Req, State)
+ -> {[binary()], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([charsets_provided/2]).
+
+-callback content_types_accepted(Req, State)
+ -> {[{'*' | binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([content_types_accepted/2]).
+
+-callback content_types_provided(Req, State)
+ -> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([content_types_provided/2]).
+
+-callback delete_completed(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([delete_completed/2]).
+
+-callback delete_resource(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([delete_resource/2]).
+
+-callback expires(Req, State)
+ -> {calendar:datetime() | binary() | undefined, Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([expires/2]).
+
+-callback forbidden(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([forbidden/2]).
+
+-callback generate_etag(Req, State)
+ -> {binary() | {weak | strong, binary()}, Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([generate_etag/2]).
+
+-callback is_authorized(Req, State)
+ -> {true | {false, iodata()}, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([is_authorized/2]).
+
+-callback is_conflict(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([is_conflict/2]).
+
+-callback known_methods(Req, State)
+ -> {[binary()], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([known_methods/2]).
+
+-callback languages_provided(Req, State)
+ -> {[binary()], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([languages_provided/2]).
+
+-callback last_modified(Req, State)
+ -> {calendar:datetime(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([last_modified/2]).
+
+-callback malformed_request(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([malformed_request/2]).
+
+-callback moved_permanently(Req, State)
+ -> {{true, iodata()} | false, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([moved_permanently/2]).
+
+-callback moved_temporarily(Req, State)
+ -> {{true, iodata()} | false, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([moved_temporarily/2]).
+
+-callback multiple_choices(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([multiple_choices/2]).
+
+-callback options(Req, State)
+ -> {ok, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([options/2]).
+
+-callback previously_existed(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([previously_existed/2]).
+
+-callback range_satisfiable(Req, State)
+ -> {boolean() | {false, non_neg_integer() | iodata()}, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([range_satisfiable/2]).
+
+-callback ranges_provided(Req, State)
+ -> {[{binary(), atom()}], Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([ranges_provided/2]).
+
+-callback rate_limited(Req, State)
+ -> {{true, non_neg_integer() | calendar:datetime()} | false, Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([rate_limited/2]).
+
+-callback resource_exists(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([resource_exists/2]).
+
+-callback service_available(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([service_available/2]).
+
+-callback uri_too_long(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([uri_too_long/2]).
+
+-callback valid_content_headers(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([valid_content_headers/2]).
+
+-callback valid_entity_length(Req, State)
+ -> {boolean(), Req, State}
+ | {stop, Req, State}
+ | {switch_handler(), Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([valid_entity_length/2]).
+
+-callback variances(Req, State)
+ -> {[binary()], Req, State}
+ when Req::cowboy_req:req(), State::any().
+-optional_callbacks([variances/2]).
+
+%% End of REST callbacks. Whew!
+
+-record(state, {
+ method = undefined :: binary(),
+
+ %% Handler.
+ handler :: atom(),
+ handler_state :: any(),
+
+ %% Allowed methods. Only used for OPTIONS requests.
+ allowed_methods :: [binary()] | undefined,
+
+ %% Media type.
+ content_types_p = [] ::
+ [{binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
+ atom()}],
+ content_type_a :: undefined
+ | {binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
+ atom()},
+
+ %% Language.
+ languages_p = [] :: [binary()],
+ language_a :: undefined | binary(),
+
+ %% Charset.
+ charsets_p = undefined :: undefined | [binary()],
+ charset_a :: undefined | binary(),
+
+ %% Range units.
+ ranges_a = [] :: [{binary(), atom()}],
+
+ %% Whether the resource exists.
+ exists = false :: boolean(),
+
+ %% Cached resource calls.
+ etag :: undefined | no_call | {strong | weak, binary()},
+ last_modified :: undefined | no_call | calendar:datetime(),
+ expires :: undefined | no_call | calendar:datetime() | binary()
+}).
+
+-spec upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req0, Env, Handler, HandlerState0) ->
+ Method = cowboy_req:method(Req0),
+ case service_available(Req0, #state{method=Method,
+ handler=Handler, handler_state=HandlerState0}) of
+ {ok, Req, Result} ->
+ {ok, Req, Env#{result => Result}};
+ {Mod, Req, HandlerState} ->
+ Mod:upgrade(Req, Env, Handler, HandlerState);
+ {Mod, Req, HandlerState, Opts} ->
+ Mod:upgrade(Req, Env, Handler, HandlerState, Opts)
+ end.
+
+-spec upgrade(Req, Env, module(), any(), any())
+ -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+%% cowboy_rest takes no options.
+upgrade(Req, Env, Handler, HandlerState, _Opts) ->
+ upgrade(Req, Env, Handler, HandlerState).
+
+service_available(Req, State) ->
+ expect(Req, State, service_available, true, fun known_methods/2, 503).
+
+%% known_methods/2 should return a list of binary methods.
+known_methods(Req, State=#state{method=Method}) ->
+ case call(Req, State, known_methods) of
+ no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
+ Method =:= <<"POST">>; Method =:= <<"PUT">>;
+ Method =:= <<"PATCH">>; Method =:= <<"DELETE">>;
+ Method =:= <<"OPTIONS">> ->
+ next(Req, State, fun uri_too_long/2);
+ no_call ->
+ next(Req, State, 501);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {List, Req2, State2} ->
+ case lists:member(Method, List) of
+ true -> next(Req2, State2, fun uri_too_long/2);
+ false -> next(Req2, State2, 501)
+ end
+ end.
+
+uri_too_long(Req, State) ->
+ expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414).
+
+%% allowed_methods/2 should return a list of binary methods.
+allowed_methods(Req, State=#state{method=Method}) ->
+ case call(Req, State, allowed_methods) of
+ no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
+ next(Req, State, fun malformed_request/2);
+ no_call when Method =:= <<"OPTIONS">> ->
+ next(Req, State#state{allowed_methods=
+ [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]},
+ fun malformed_request/2);
+ no_call ->
+ method_not_allowed(Req, State,
+ [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {List, Req2, State2} ->
+ case lists:member(Method, List) of
+ true when Method =:= <<"OPTIONS">> ->
+ next(Req2, State2#state{allowed_methods=List},
+ fun malformed_request/2);
+ true ->
+ next(Req2, State2, fun malformed_request/2);
+ false ->
+ method_not_allowed(Req2, State2, List)
+ end
+ end.
+
+method_not_allowed(Req, State, []) ->
+ Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req),
+ respond(Req2, State, 405);
+method_not_allowed(Req, State, Methods) ->
+ << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>,
+ Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
+ respond(Req2, State, 405).
+
+malformed_request(Req, State) ->
+ expect(Req, State, malformed_request, false, fun is_authorized/2, 400).
+
+%% is_authorized/2 should return true or {false, WwwAuthenticateHeader}.
+is_authorized(Req, State) ->
+ case call(Req, State, is_authorized) of
+ no_call ->
+ forbidden(Req, State);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {true, Req2, State2} ->
+ forbidden(Req2, State2);
+ {{false, AuthHead}, Req2, State2} ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"www-authenticate">>, AuthHead, Req2),
+ respond(Req3, State2, 401)
+ end.
+
+forbidden(Req, State) ->
+ expect(Req, State, forbidden, false, fun rate_limited/2, 403).
+
+rate_limited(Req, State) ->
+ case call(Req, State, rate_limited) of
+ no_call ->
+ valid_content_headers(Req, State);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {false, Req2, State2} ->
+ valid_content_headers(Req2, State2);
+ {{true, RetryAfter0}, Req2, State2} ->
+ RetryAfter = if
+ is_integer(RetryAfter0), RetryAfter0 >= 0 ->
+ integer_to_binary(RetryAfter0);
+ is_tuple(RetryAfter0) ->
+ cowboy_clock:rfc1123(RetryAfter0)
+ end,
+ Req3 = cowboy_req:set_resp_header(<<"retry-after">>, RetryAfter, Req2),
+ respond(Req3, State2, 429)
+ end.
+
+valid_content_headers(Req, State) ->
+ expect(Req, State, valid_content_headers, true,
+ fun valid_entity_length/2, 501).
+
+valid_entity_length(Req, State) ->
+ expect(Req, State, valid_entity_length, true, fun options/2, 413).
+
+%% If you need to add additional headers to the response at this point,
+%% you should do it directly in the options/2 call using set_resp_headers.
+options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) ->
+ case call(Req, State, options) of
+ no_call when Methods =:= [] ->
+ Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req),
+ respond(Req2, State, 200);
+ no_call ->
+ << ", ", Allow/binary >>
+ = << << ", ", M/binary >> || M <- Methods >>,
+ Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
+ respond(Req2, State, 200);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {ok, Req2, State2} ->
+ respond(Req2, State2, 200)
+ end;
+options(Req, State) ->
+ content_types_provided(Req, State).
+
+%% content_types_provided/2 should return a list of content types and their
+%% associated callback function as a tuple: {{Type, SubType, Params}, Fun}.
+%% Type and SubType are the media type as binary. Params is a list of
+%% Key/Value tuple, with Key and Value a binary. Fun is the name of the
+%% callback that will be used to return the content of the response. It is
+%% given as an atom.
+%%
+%% An example of such return value would be:
+%% {{<<"text">>, <<"html">>, []}, to_html}
+%%
+%% Note that it is also possible to return a binary content type that will
+%% then be parsed by Cowboy. However note that while this may make your
+%% resources a little more readable, this is a lot less efficient.
+%%
+%% An example of such return value would be:
+%% {<<"text/html">>, to_html}
+content_types_provided(Req, State) ->
+ case call(Req, State, content_types_provided) of
+ no_call ->
+ State2 = State#state{
+ content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]},
+ try cowboy_req:parse_header(<<"accept">>, Req) of
+ undefined ->
+ languages_provided(
+ Req#{media_type => {<<"text">>, <<"html">>, []}},
+ State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}});
+ Accept ->
+ choose_media_type(Req, State2, prioritize_accept(Accept))
+ catch _:_ ->
+ respond(Req, State2, 400)
+ end;
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {[], Req2, State2} ->
+ not_acceptable(Req2, State2);
+ {CTP, Req2, State2} ->
+ CTP2 = [normalize_content_types(P) || P <- CTP],
+ State3 = State2#state{content_types_p=CTP2},
+ try cowboy_req:parse_header(<<"accept">>, Req2) of
+ undefined ->
+ {PMT0, _Fun} = HeadCTP = hd(CTP2),
+ %% We replace the wildcard by an empty list of parameters.
+ PMT = case PMT0 of
+ {Type, SubType, '*'} -> {Type, SubType, []};
+ _ -> PMT0
+ end,
+ languages_provided(
+ Req2#{media_type => PMT},
+ State3#state{content_type_a=HeadCTP});
+ Accept ->
+ choose_media_type(Req2, State3, prioritize_accept(Accept))
+ catch _:_ ->
+ respond(Req2, State3, 400)
+ end
+ end.
+
+normalize_content_types({ContentType, Callback})
+ when is_binary(ContentType) ->
+ {cow_http_hd:parse_content_type(ContentType), Callback};
+normalize_content_types(Normalized) ->
+ Normalized.
+
+prioritize_accept(Accept) ->
+ lists:sort(
+ fun ({MediaTypeA, Quality, _AcceptParamsA},
+ {MediaTypeB, Quality, _AcceptParamsB}) ->
+ %% Same quality, check precedence in more details.
+ prioritize_mediatype(MediaTypeA, MediaTypeB);
+ ({_MediaTypeA, QualityA, _AcceptParamsA},
+ {_MediaTypeB, QualityB, _AcceptParamsB}) ->
+ %% Just compare the quality.
+ QualityA > QualityB
+ end, Accept).
+
+%% Media ranges can be overridden by more specific media ranges or
+%% specific media types. If more than one media range applies to a given
+%% type, the most specific reference has precedence.
+%%
+%% We always choose B over A when we can't decide between the two.
+prioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) ->
+ case TypeB of
+ TypeA ->
+ case SubTypeB of
+ SubTypeA -> length(ParamsA) > length(ParamsB);
+ <<"*">> -> true;
+ _Any -> false
+ end;
+ <<"*">> -> true;
+ _Any -> false
+ end.
+
+%% Ignoring the rare AcceptParams. Not sure what should be done about them.
+choose_media_type(Req, State, []) ->
+ not_acceptable(Req, State);
+choose_media_type(Req, State=#state{content_types_p=CTP},
+ [MediaType|Tail]) ->
+ match_media_type(Req, State, Tail, CTP, MediaType).
+
+match_media_type(Req, State, Accept, [], _MediaType) ->
+ choose_media_type(Req, State, Accept);
+match_media_type(Req, State, Accept, CTP,
+ MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) ->
+ match_media_type_params(Req, State, Accept, CTP, MediaType);
+match_media_type(Req, State, Accept,
+ CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail],
+ MediaType = {{Type, SubType_A, _PA}, _QA, _APA})
+ when SubType_P =:= SubType_A; SubType_A =:= <<"*">> ->
+ match_media_type_params(Req, State, Accept, CTP, MediaType);
+match_media_type(Req, State, Accept, [_Any|Tail], MediaType) ->
+ match_media_type(Req, State, Accept, Tail, MediaType).
+
+match_media_type_params(Req, State, Accept,
+ [Provided = {{TP, STP, '*'}, _Fun}|Tail],
+ MediaType = {{TA, _STA, Params_A0}, _QA, _APA}) ->
+ case lists:keytake(<<"charset">>, 1, Params_A0) of
+ {value, {_, Charset}, Params_A} when TA =:= <<"text">> ->
+ %% When we match against a wildcard, the media type is text
+ %% and has a charset parameter, we call charsets_provided
+ %% and check that the charset is provided. If the callback
+ %% is not exported, we accept inconditionally but ignore
+ %% the given charset so as to not send a wrong value back.
+ case call(Req, State, charsets_provided) of
+ no_call ->
+ languages_provided(Req#{media_type => {TP, STP, Params_A0}},
+ State#state{content_type_a=Provided});
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {CP, Req2, State2} ->
+ State3 = State2#state{charsets_p=CP},
+ case lists:member(Charset, CP) of
+ false ->
+ match_media_type(Req2, State3, Accept, Tail, MediaType);
+ true ->
+ languages_provided(Req2#{media_type => {TP, STP, Params_A}},
+ State3#state{content_type_a=Provided,
+ charset_a=Charset})
+ end
+ end;
+ _ ->
+ languages_provided(Req#{media_type => {TP, STP, Params_A0}},
+ State#state{content_type_a=Provided})
+ end;
+match_media_type_params(Req, State, Accept,
+ [Provided = {PMT = {TP, STP, Params_P0}, Fun}|Tail],
+ MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) ->
+ case lists:sort(Params_P0) =:= lists:sort(Params_A) of
+ true when TP =:= <<"text">> ->
+ %% When a charset was provided explicitly in both the charset header
+ %% and the media types provided and the negotiation is successful,
+ %% we keep the charset and don't call charsets_provided. This only
+ %% applies to text media types, however.
+ {Charset, Params_P} = case lists:keytake(<<"charset">>, 1, Params_P0) of
+ false -> {undefined, Params_P0};
+ {value, {_, Charset0}, Params_P1} -> {Charset0, Params_P1}
+ end,
+ languages_provided(Req#{media_type => {TP, STP, Params_P}},
+ State#state{content_type_a={{TP, STP, Params_P}, Fun},
+ charset_a=Charset});
+ true ->
+ languages_provided(Req#{media_type => PMT},
+ State#state{content_type_a=Provided});
+ false ->
+ match_media_type(Req, State, Accept, Tail, MediaType)
+ end.
+
+%% languages_provided should return a list of binary values indicating
+%% which languages are accepted by the resource.
+%%
+%% @todo I suppose we should also ask the resource if it wants to
+%% set a language itself or if it wants it to be automatically chosen.
+languages_provided(Req, State) ->
+ case call(Req, State, languages_provided) of
+ no_call ->
+ charsets_provided(Req, State);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {[], Req2, State2} ->
+ not_acceptable(Req2, State2);
+ {LP, Req2, State2} ->
+ State3 = State2#state{languages_p=LP},
+ case cowboy_req:parse_header(<<"accept-language">>, Req2) of
+ undefined ->
+ set_language(Req2, State3#state{language_a=hd(LP)});
+ AcceptLanguage ->
+ AcceptLanguage2 = prioritize_languages(AcceptLanguage),
+ choose_language(Req2, State3, AcceptLanguage2)
+ end
+ end.
+
+%% A language-range matches a language-tag if it exactly equals the tag,
+%% or if it exactly equals a prefix of the tag such that the first tag
+%% character following the prefix is "-". The special range "*", if
+%% present in the Accept-Language field, matches every tag not matched
+%% by any other range present in the Accept-Language field.
+%%
+%% @todo The last sentence probably means we should always put '*'
+%% at the end of the list.
+prioritize_languages(AcceptLanguages) ->
+ lists:sort(
+ fun ({_TagA, QualityA}, {_TagB, QualityB}) ->
+ QualityA > QualityB
+ end, AcceptLanguages).
+
+choose_language(Req, State, []) ->
+ not_acceptable(Req, State);
+choose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) ->
+ match_language(Req, State, Tail, LP, Language).
+
+match_language(Req, State, Accept, [], _Language) ->
+ choose_language(Req, State, Accept);
+match_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) ->
+ set_language(Req, State#state{language_a=Provided});
+match_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) ->
+ set_language(Req, State#state{language_a=Provided});
+match_language(Req, State, Accept, [Provided|Tail],
+ Language = {Tag, _Quality}) ->
+ Length = byte_size(Tag),
+ case Provided of
+ << Tag:Length/binary, $-, _Any/bits >> ->
+ set_language(Req, State#state{language_a=Provided});
+ _Any ->
+ match_language(Req, State, Accept, Tail, Language)
+ end.
+
+set_language(Req, State=#state{language_a=Language}) ->
+ Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req),
+ charsets_provided(Req2#{language => Language}, State).
+
+%% charsets_provided should return a list of binary values indicating
+%% which charsets are accepted by the resource.
+%%
+%% A charset may have been selected while negotiating the accept header.
+%% There's no need to select one again.
+charsets_provided(Req, State=#state{charset_a=Charset})
+ when Charset =/= undefined ->
+ set_content_type(Req, State);
+%% If charsets_p is defined, use it instead of calling charsets_provided
+%% again. We also call this clause during normal execution to avoid
+%% duplicating code.
+charsets_provided(Req, State=#state{charsets_p=[]}) ->
+ not_acceptable(Req, State);
+charsets_provided(Req, State=#state{charsets_p=CP})
+ when CP =/= undefined ->
+ case cowboy_req:parse_header(<<"accept-charset">>, Req) of
+ undefined ->
+ set_content_type(Req, State#state{charset_a=hd(CP)});
+ AcceptCharset0 ->
+ AcceptCharset = prioritize_charsets(AcceptCharset0),
+ choose_charset(Req, State, AcceptCharset)
+ end;
+charsets_provided(Req, State) ->
+ case call(Req, State, charsets_provided) of
+ no_call ->
+ set_content_type(Req, State);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {CP, Req2, State2} ->
+ charsets_provided(Req2, State2#state{charsets_p=CP})
+ end.
+
+prioritize_charsets(AcceptCharsets) ->
+ lists:sort(
+ fun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) ->
+ QualityA > QualityB
+ end, AcceptCharsets).
+
+choose_charset(Req, State, []) ->
+ not_acceptable(Req, State);
+%% A q-value of 0 means not acceptable.
+choose_charset(Req, State, [{_, 0}|Tail]) ->
+ choose_charset(Req, State, Tail);
+choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) ->
+ match_charset(Req, State, Tail, CP, Charset).
+
+match_charset(Req, State, Accept, [], _Charset) ->
+ choose_charset(Req, State, Accept);
+match_charset(Req, State, _Accept, [Provided|_], {<<"*">>, _}) ->
+ set_content_type(Req, State#state{charset_a=Provided});
+match_charset(Req, State, _Accept, [Provided|_], {Provided, _}) ->
+ set_content_type(Req, State#state{charset_a=Provided});
+match_charset(Req, State, Accept, [_|Tail], Charset) ->
+ match_charset(Req, State, Accept, Tail, Charset).
+
+set_content_type(Req, State=#state{
+ content_type_a={{Type, SubType, Params}, _Fun},
+ charset_a=Charset}) ->
+ ParamsBin = set_content_type_build_params(Params, []),
+ ContentType = [Type, <<"/">>, SubType, ParamsBin],
+ ContentType2 = case {Type, Charset} of
+ {<<"text">>, Charset} when Charset =/= undefined ->
+ [ContentType, <<"; charset=">>, Charset];
+ _ ->
+ ContentType
+ end,
+ Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req),
+ encodings_provided(Req2#{charset => Charset}, State).
+
+set_content_type_build_params('*', []) ->
+ <<>>;
+set_content_type_build_params([], []) ->
+ <<>>;
+set_content_type_build_params([], Acc) ->
+ lists:reverse(Acc);
+set_content_type_build_params([{Attr, Value}|Tail], Acc) ->
+ set_content_type_build_params(Tail, [[Attr, <<"=">>, Value], <<";">>|Acc]).
+
+%% @todo Match for identity as we provide nothing else for now.
+%% @todo Don't forget to set the Content-Encoding header when we reply a body
+%% and the found encoding is something other than identity.
+encodings_provided(Req, State) ->
+ ranges_provided(Req, State).
+
+not_acceptable(Req, State) ->
+ respond(Req, State, 406).
+
+ranges_provided(Req, State) ->
+ case call(Req, State, ranges_provided) of
+ no_call ->
+ variances(Req, State);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {[], Req2, State2} ->
+ Req3 = cowboy_req:set_resp_header(<<"accept-ranges">>, <<"none">>, Req2),
+ variances(Req3, State2#state{ranges_a=[]});
+ {RP, Req2, State2} ->
+ <<", ", AcceptRanges/binary>> = <<<<", ", R/binary>> || {R, _} <- RP>>,
+ Req3 = cowboy_req:set_resp_header(<<"accept-ranges">>, AcceptRanges, Req2),
+ variances(Req3, State2#state{ranges_a=RP})
+ end.
+
+%% variances/2 should return a list of headers that will be added
+%% to the Vary response header. The Accept, Accept-Language,
+%% Accept-Charset and Accept-Encoding headers do not need to be
+%% specified.
+%%
+%% @todo Do Accept-Encoding too when we handle it.
+%% @todo Does the order matter?
+variances(Req, State=#state{content_types_p=CTP,
+ languages_p=LP, charsets_p=CP}) ->
+ Variances = case CTP of
+ [] -> [];
+ [_] -> [];
+ [_|_] -> [<<"accept">>]
+ end,
+ Variances2 = case LP of
+ [] -> Variances;
+ [_] -> Variances;
+ [_|_] -> [<<"accept-language">>|Variances]
+ end,
+ Variances3 = case CP of
+ undefined -> Variances2;
+ [] -> Variances2;
+ [_] -> Variances2;
+ [_|_] -> [<<"accept-charset">>|Variances2]
+ end,
+ try variances(Req, State, Variances3) of
+ {Variances4, Req2, State2} ->
+ case [[<<", ">>, V] || V <- Variances4] of
+ [] ->
+ resource_exists(Req2, State2);
+ [[<<", ">>, H]|Variances5] ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"vary">>, [H|Variances5], Req2),
+ resource_exists(Req3, State2)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+variances(Req, State, Variances) ->
+ case unsafe_call(Req, State, variances) of
+ no_call ->
+ {Variances, Req, State};
+ {HandlerVariances, Req2, State2} ->
+ {Variances ++ HandlerVariances, Req2, State2}
+ end.
+
+resource_exists(Req, State) ->
+ expect(Req, State, resource_exists, true,
+ fun if_match_exists/2, fun if_match_must_not_exist/2).
+
+if_match_exists(Req, State) ->
+ State2 = State#state{exists=true},
+ case cowboy_req:parse_header(<<"if-match">>, Req) of
+ undefined ->
+ if_unmodified_since_exists(Req, State2);
+ '*' ->
+ if_unmodified_since_exists(Req, State2);
+ ETagsList ->
+ if_match(Req, State2, ETagsList)
+ end.
+
+if_match(Req, State, EtagsList) ->
+ try generate_etag(Req, State) of
+ %% Strong Etag comparison: weak Etag never matches.
+ {{weak, _}, Req2, State2} ->
+ precondition_failed(Req2, State2);
+ {Etag, Req2, State2} ->
+ case lists:member(Etag, EtagsList) of
+ true -> if_none_match_exists(Req2, State2);
+ %% Etag may be `undefined' which cannot be a member.
+ false -> precondition_failed(Req2, State2)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+if_match_must_not_exist(Req, State) ->
+ case cowboy_req:header(<<"if-match">>, Req) of
+ undefined -> is_put_to_missing_resource(Req, State);
+ _ -> precondition_failed(Req, State)
+ end.
+
+if_unmodified_since_exists(Req, State) ->
+ try cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
+ undefined ->
+ if_none_match_exists(Req, State);
+ IfUnmodifiedSince ->
+ if_unmodified_since(Req, State, IfUnmodifiedSince)
+ catch _:_ ->
+ if_none_match_exists(Req, State)
+ end.
+
+%% If LastModified is the atom 'no_call', we continue.
+if_unmodified_since(Req, State, IfUnmodifiedSince) ->
+ try last_modified(Req, State) of
+ {LastModified, Req2, State2} ->
+ case LastModified > IfUnmodifiedSince of
+ true -> precondition_failed(Req2, State2);
+ false -> if_none_match_exists(Req2, State2)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+if_none_match_exists(Req, State) ->
+ case cowboy_req:parse_header(<<"if-none-match">>, Req) of
+ undefined ->
+ if_modified_since_exists(Req, State);
+ '*' ->
+ precondition_is_head_get(Req, State);
+ EtagsList ->
+ if_none_match(Req, State, EtagsList)
+ end.
+
+if_none_match(Req, State, EtagsList) ->
+ try generate_etag(Req, State) of
+ {Etag, Req2, State2} ->
+ case Etag of
+ undefined ->
+ precondition_failed(Req2, State2);
+ Etag ->
+ case is_weak_match(Etag, EtagsList) of
+ true -> precondition_is_head_get(Req2, State2);
+ false -> method(Req2, State2)
+ end
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+%% Weak Etag comparison: only check the opaque tag.
+is_weak_match(_, []) ->
+ false;
+is_weak_match({_, Tag}, [{_, Tag}|_]) ->
+ true;
+is_weak_match(Etag, [_|Tail]) ->
+ is_weak_match(Etag, Tail).
+
+precondition_is_head_get(Req, State=#state{method=Method})
+ when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
+ not_modified(Req, State);
+precondition_is_head_get(Req, State) ->
+ precondition_failed(Req, State).
+
+if_modified_since_exists(Req, State) ->
+ try cowboy_req:parse_header(<<"if-modified-since">>, Req) of
+ undefined ->
+ method(Req, State);
+ IfModifiedSince ->
+ if_modified_since_now(Req, State, IfModifiedSince)
+ catch _:_ ->
+ method(Req, State)
+ end.
+
+if_modified_since_now(Req, State, IfModifiedSince) ->
+ case IfModifiedSince > erlang:universaltime() of
+ true -> method(Req, State);
+ false -> if_modified_since(Req, State, IfModifiedSince)
+ end.
+
+if_modified_since(Req, State, IfModifiedSince) ->
+ try last_modified(Req, State) of
+ {undefined, Req2, State2} ->
+ method(Req2, State2);
+ {LastModified, Req2, State2} ->
+ case LastModified > IfModifiedSince of
+ true -> method(Req2, State2);
+ false -> not_modified(Req2, State2)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+not_modified(Req, State) ->
+ Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req),
+ try set_resp_etag(Req2, State) of
+ {Req3, State2} ->
+ try set_resp_expires(Req3, State2) of
+ {Req4, State3} ->
+ respond(Req4, State3, 304)
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State2, Class, Reason, Stacktrace)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+precondition_failed(Req, State) ->
+ respond(Req, State, 412).
+
+is_put_to_missing_resource(Req, State=#state{method= <<"PUT">>}) ->
+ moved_permanently(Req, State, fun is_conflict/2);
+is_put_to_missing_resource(Req, State) ->
+ previously_existed(Req, State).
+
+%% moved_permanently/2 should return either false or {true, Location}
+%% with Location the full new URI of the resource.
+moved_permanently(Req, State, OnFalse) ->
+ case call(Req, State, moved_permanently) of
+ {{true, Location}, Req2, State2} ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, Location, Req2),
+ respond(Req3, State2, 301);
+ {false, Req2, State2} ->
+ OnFalse(Req2, State2);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ no_call ->
+ OnFalse(Req, State)
+ end.
+
+previously_existed(Req, State) ->
+ expect(Req, State, previously_existed, false,
+ fun (R, S) -> is_post_to_missing_resource(R, S, 404) end,
+ fun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end).
+
+%% moved_temporarily/2 should return either false or {true, Location}
+%% with Location the full new URI of the resource.
+moved_temporarily(Req, State) ->
+ case call(Req, State, moved_temporarily) of
+ {{true, Location}, Req2, State2} ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, Location, Req2),
+ respond(Req3, State2, 307);
+ {false, Req2, State2} ->
+ is_post_to_missing_resource(Req2, State2, 410);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ no_call ->
+ is_post_to_missing_resource(Req, State, 410)
+ end.
+
+is_post_to_missing_resource(Req, State=#state{method= <<"POST">>}, OnFalse) ->
+ allow_missing_post(Req, State, OnFalse);
+is_post_to_missing_resource(Req, State, OnFalse) ->
+ respond(Req, State, OnFalse).
+
+allow_missing_post(Req, State, OnFalse) ->
+ expect(Req, State, allow_missing_post, true, fun accept_resource/2, OnFalse).
+
+method(Req, State=#state{method= <<"DELETE">>}) ->
+ delete_resource(Req, State);
+method(Req, State=#state{method= <<"PUT">>}) ->
+ is_conflict(Req, State);
+method(Req, State=#state{method=Method})
+ when Method =:= <<"POST">>; Method =:= <<"PATCH">> ->
+ accept_resource(Req, State);
+method(Req, State=#state{method=Method})
+ when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
+ set_resp_body_etag(Req, State);
+method(Req, State) ->
+ multiple_choices(Req, State).
+
+%% delete_resource/2 should start deleting the resource and return.
+delete_resource(Req, State) ->
+ expect(Req, State, delete_resource, false, 500, fun delete_completed/2).
+
+%% delete_completed/2 indicates whether the resource has been deleted yet.
+delete_completed(Req, State) ->
+ expect(Req, State, delete_completed, true, fun has_resp_body/2, 202).
+
+is_conflict(Req, State) ->
+ expect(Req, State, is_conflict, false, fun accept_resource/2, 409).
+
+%% content_types_accepted should return a list of media types and their
+%% associated callback functions in the same format as content_types_provided.
+%%
+%% The callback will then be called and is expected to process the content
+%% pushed to the resource in the request body.
+%%
+%% content_types_accepted SHOULD return a different list
+%% for each HTTP method.
+accept_resource(Req, State) ->
+ case call(Req, State, content_types_accepted) of
+ no_call ->
+ respond(Req, State, 415);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {CTA, Req2, State2} ->
+ CTA2 = [normalize_content_types(P) || P <- CTA],
+ try cowboy_req:parse_header(<<"content-type">>, Req2) of
+ %% We do not match against the boundary parameter for multipart.
+ {Type = <<"multipart">>, SubType, Params} ->
+ ContentType = {Type, SubType, lists:keydelete(<<"boundary">>, 1, Params)},
+ choose_content_type(Req2, State2, ContentType, CTA2);
+ ContentType ->
+ choose_content_type(Req2, State2, ContentType, CTA2)
+ catch _:_ ->
+ respond(Req2, State2, 415)
+ end
+ end.
+
+%% The special content type '*' will always match. It can be used as a
+%% catch-all content type for accepting any kind of request content.
+%% Note that because it will always match, it should be the last of the
+%% list of content types, otherwise it'll shadow the ones following.
+choose_content_type(Req, State, _ContentType, []) ->
+ respond(Req, State, 415);
+choose_content_type(Req, State, ContentType, [{Accepted, Fun}|_Tail])
+ when Accepted =:= '*'; Accepted =:= ContentType ->
+ process_content_type(Req, State, Fun);
+%% The special parameter '*' will always match any kind of content type
+%% parameters.
+%% Note that because it will always match, it should be the last of the
+%% list for specific content type, otherwise it'll shadow the ones following.
+choose_content_type(Req, State, {Type, SubType, Param},
+ [{{Type, SubType, AcceptedParam}, Fun}|_Tail])
+ when AcceptedParam =:= '*'; AcceptedParam =:= Param ->
+ process_content_type(Req, State, Fun);
+choose_content_type(Req, State, ContentType, [_Any|Tail]) ->
+ choose_content_type(Req, State, ContentType, Tail).
+
+process_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) ->
+ try case call(Req, State, Fun) of
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {true, Req2, State2} when Exists ->
+ next(Req2, State2, fun has_resp_body/2);
+ {true, Req2, State2} ->
+ next(Req2, State2, fun maybe_created/2);
+ {false, Req2, State2} ->
+ respond(Req2, State2, 400);
+ {{created, ResURL}, Req2, State2} when Method =:= <<"POST">> ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, ResURL, Req2),
+ respond(Req3, State2, 201);
+ {{see_other, ResURL}, Req2, State2} when Method =:= <<"POST">> ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, ResURL, Req2),
+ respond(Req3, State2, 303);
+ {{true, ResURL}, Req2, State2} when Method =:= <<"POST">> ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"location">>, ResURL, Req2),
+ if
+ Exists -> respond(Req3, State2, 303);
+ true -> respond(Req3, State2, 201)
+ end
+ end catch Class:Reason = {case_clause, no_call}:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+%% If PUT was used then the resource has been created at the current URL.
+%% Otherwise, if a location header has been set then the resource has been
+%% created at a new URL. If not, send a 200 or 204 as expected from a
+%% POST or PATCH request.
+maybe_created(Req, State=#state{method= <<"PUT">>}) ->
+ respond(Req, State, 201);
+maybe_created(Req, State) ->
+ case cowboy_req:has_resp_header(<<"location">>, Req) of
+ true -> respond(Req, State, 201);
+ false -> has_resp_body(Req, State)
+ end.
+
+has_resp_body(Req, State) ->
+ case cowboy_req:has_resp_body(Req) of
+ true -> multiple_choices(Req, State);
+ false -> respond(Req, State, 204)
+ end.
+
+%% Set the Etag header if any for the response provided.
+set_resp_body_etag(Req, State) ->
+ try set_resp_etag(Req, State) of
+ {Req2, State2} ->
+ set_resp_body_last_modified(Req2, State2)
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+%% Set the Last-Modified header if any for the response provided.
+set_resp_body_last_modified(Req, State) ->
+ try last_modified(Req, State) of
+ {LastModified, Req2, State2} ->
+ case LastModified of
+ LastModified when is_atom(LastModified) ->
+ set_resp_body_expires(Req2, State2);
+ LastModified ->
+ LastModifiedBin = cowboy_clock:rfc1123(LastModified),
+ Req3 = cowboy_req:set_resp_header(
+ <<"last-modified">>, LastModifiedBin, Req2),
+ set_resp_body_expires(Req3, State2)
+ end
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+%% Set the Expires header if any for the response provided.
+set_resp_body_expires(Req, State) ->
+ try set_resp_expires(Req, State) of
+ {Req2, State2} ->
+ if_range(Req2, State2)
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req, State, Class, Reason, Stacktrace)
+ end.
+
+%% When both the if-range and range headers are set, we perform
+%% a strong comparison. If it fails, we send a full response.
+if_range(Req=#{headers := #{<<"if-range">> := _, <<"range">> := _}},
+ State=#state{etag=Etag}) ->
+ try cowboy_req:parse_header(<<"if-range">>, Req) of
+ %% Strong etag comparison is an exact match with the generate_etag result.
+ Etag={strong, _} ->
+ range(Req, State);
+ %% We cannot do a strong date comparison because we have
+ %% no way of knowing whether the representation changed
+ %% twice during the second covered by the presented
+ %% validator. (RFC7232 2.2.2)
+ _ ->
+ set_resp_body(Req, State)
+ catch _:_ ->
+ set_resp_body(Req, State)
+ end;
+if_range(Req, State) ->
+ range(Req, State).
+
+range(Req, State=#state{ranges_a=[]}) ->
+ set_resp_body(Req, State);
+range(Req, State) ->
+ try cowboy_req:parse_header(<<"range">>, Req) of
+ undefined ->
+ set_resp_body(Req, State);
+ %% @todo Maybe change parse_header to return <<"bytes">> in 3.0.
+ {bytes, BytesRange} ->
+ choose_range(Req, State, {<<"bytes">>, BytesRange});
+ Range ->
+ choose_range(Req, State, Range)
+ catch _:_ ->
+ %% We send a 416 response back when we can't parse the
+ %% range header at all. I'm not sure this is the right
+ %% way to go but at least this can help clients identify
+ %% what went wrong when their range requests never work.
+ range_not_satisfiable(Req, State, undefined)
+ end.
+
+choose_range(Req, State=#state{ranges_a=RangesAccepted}, Range={RangeUnit, _}) ->
+ case lists:keyfind(RangeUnit, 1, RangesAccepted) of
+ {_, Callback} ->
+ %% We pass the selected range onward in the Req.
+ range_satisfiable(Req#{range => Range}, State, Callback);
+ false ->
+ set_resp_body(Req, State)
+ end.
+
+range_satisfiable(Req, State, Callback) ->
+ case call(Req, State, range_satisfiable) of
+ no_call ->
+ set_ranged_body(Req, State, Callback);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {true, Req2, State2} ->
+ set_ranged_body(Req2, State2, Callback);
+ {false, Req2, State2} ->
+ range_not_satisfiable(Req2, State2, undefined);
+ {{false, Int}, Req2, State2} when is_integer(Int) ->
+ range_not_satisfiable(Req2, State2, [<<"*/">>, integer_to_binary(Int)]);
+ {{false, Iodata}, Req2, State2} when is_binary(Iodata); is_list(Iodata) ->
+ range_not_satisfiable(Req2, State2, Iodata)
+ end.
+
+%% When the callback selected is 'auto' and the range unit
+%% is bytes, we call the normal provide callback and split
+%% the content automatically.
+set_ranged_body(Req=#{range := {<<"bytes">>, _}}, State, auto) ->
+ set_ranged_body_auto(Req, State);
+set_ranged_body(Req, State, Callback) ->
+ set_ranged_body_callback(Req, State, Callback).
+
+set_ranged_body_auto(Req, State=#state{handler=Handler, content_type_a={_, Callback}}) ->
+ try case call(Req, State, Callback) of
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {Body, Req2, State2} ->
+ maybe_set_ranged_body_auto(Req2, State2, Body)
+ end catch Class:{case_clause, no_call}:Stacktrace ->
+ error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
+ 'A callback specified in content_types_provided/2 is not exported.'},
+ Stacktrace)
+ end.
+
+maybe_set_ranged_body_auto(Req=#{range := {_, Ranges}}, State, Body) ->
+ Size = case Body of
+ {sendfile, _, Bytes, _} -> Bytes;
+ _ -> iolist_size(Body)
+ end,
+ Checks = [case Range of
+ {From, infinity} -> From < Size;
+ {From, To} -> (From < Size) andalso (From =< To) andalso (To =< Size);
+ Neg -> (Neg =/= 0) andalso (-Neg < Size)
+ end || Range <- Ranges],
+ case lists:usort(Checks) of
+ [true] -> set_ranged_body_auto(Req, State, Body);
+ _ -> range_not_satisfiable(Req, State, [<<"*/">>, integer_to_binary(Size)])
+ end.
+
+%% We might also want to have some checks about range order,
+%% number of ranges, and perhaps also join ranges that are
+%% too close into one contiguous range. Some of these can
+%% be done before calling the ProvideCallback.
+
+set_ranged_body_auto(Req=#{range := {_, Ranges}}, State, Body) ->
+ Parts = [ranged_partition(Range, Body) || Range <- Ranges],
+ case Parts of
+ [OnePart] -> set_one_ranged_body(Req, State, OnePart);
+ _ when is_tuple(Body) -> send_multipart_ranged_body(Req, State, Parts);
+ _ -> set_multipart_ranged_body(Req, State, Parts)
+ end.
+
+ranged_partition(Range, {sendfile, Offset0, Bytes0, Path}) ->
+ {From, To, Offset, Bytes} = case Range of
+ {From0, infinity} -> {From0, Bytes0 - 1, Offset0 + From0, Bytes0 - From0};
+ {From0, To0} -> {From0, To0, Offset0 + From0, 1 + To0 - From0};
+ Neg -> {Bytes0 + Neg, Bytes0 - 1, Offset0 + Bytes0 + Neg, -Neg}
+ end,
+ {{From, To, Bytes0}, {sendfile, Offset, Bytes, Path}};
+ranged_partition(Range, Data0) ->
+ Total = iolist_size(Data0),
+ {From, To, Data} = case Range of
+ {From0, infinity} ->
+ {_, Data1} = cow_iolists:split(From0, Data0),
+ {From0, Total - 1, Data1};
+ {From0, To0} ->
+ {_, Data1} = cow_iolists:split(From0, Data0),
+ {Data2, _} = cow_iolists:split(To0 - From0 + 1, Data1),
+ {From0, To0, Data2};
+ Neg ->
+ {_, Data1} = cow_iolists:split(Total + Neg, Data0),
+ {Total + Neg, Total - 1, Data1}
+ end,
+ {{From, To, Total}, Data}.
+
+-ifdef(TEST).
+ranged_partition_test_() ->
+ Tests = [
+ %% Sendfile with open-ended range.
+ {{0, infinity}, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}},
+ {{6, infinity}, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}},
+ {{11, infinity}, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}},
+ %% Sendfile with open-ended range. Sendfile tuple has an offset originally.
+ {{0, infinity}, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}},
+ {{6, infinity}, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}},
+ {{11, infinity}, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}},
+ %% Sendfile with a specific range.
+ {{0, 11}, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}},
+ {{6, 11}, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}},
+ {{11, 11}, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}},
+ {{1, 10}, {sendfile, 0, 12, "t"}, {{1, 10, 12}, {sendfile, 1, 10, "t"}}},
+ %% Sendfile with a specific range. Sendfile tuple has an offset originally.
+ {{0, 11}, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}},
+ {{6, 11}, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}},
+ {{11, 11}, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}},
+ {{1, 10}, {sendfile, 3, 12, "t"}, {{1, 10, 12}, {sendfile, 4, 10, "t"}}},
+ %% Sendfile with negative range.
+ {-12, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}},
+ {-6, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}},
+ {-1, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}},
+ %% Sendfile with negative range. Sendfile tuple has an offset originally.
+ {-12, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}},
+ {-6, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}},
+ {-1, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}},
+ %% Iodata with open-ended range.
+ {{0, infinity}, <<"Hello world!">>, {{0, 11, 12}, <<"Hello world!">>}},
+ {{6, infinity}, <<"Hello world!">>, {{6, 11, 12}, <<"world!">>}},
+ {{11, infinity}, <<"Hello world!">>, {{11, 11, 12}, <<"!">>}},
+ %% Iodata with a specific range. The resulting data is
+ %% wrapped in a list because of how cow_iolists:split/2 works.
+ {{0, 11}, <<"Hello world!">>, {{0, 11, 12}, [<<"Hello world!">>]}},
+ {{6, 11}, <<"Hello world!">>, {{6, 11, 12}, [<<"world!">>]}},
+ {{11, 11}, <<"Hello world!">>, {{11, 11, 12}, [<<"!">>]}},
+ {{1, 10}, <<"Hello world!">>, {{1, 10, 12}, [<<"ello world">>]}},
+ %% Iodata with negative range.
+ {-12, <<"Hello world!">>, {{0, 11, 12}, <<"Hello world!">>}},
+ {-6, <<"Hello world!">>, {{6, 11, 12}, <<"world!">>}},
+ {-1, <<"Hello world!">>, {{11, 11, 12}, <<"!">>}}
+ ],
+ [{iolist_to_binary(io_lib:format("range ~p data ~p", [VR, VD])),
+ fun() -> R = ranged_partition(VR, VD) end} || {VR, VD, R} <- Tests].
+-endif.
+
+set_ranged_body_callback(Req, State=#state{handler=Handler}, Callback) ->
+ try case call(Req, State, Callback) of
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ %% When we receive a single range, we send it directly.
+ {[OneRange], Req2, State2} ->
+ set_one_ranged_body(Req2, State2, OneRange);
+ %% When we receive multiple ranges we have to send them as multipart/byteranges.
+ %% This also applies to non-bytes units. (RFC7233 A) If users don't want to use
+ %% this for non-bytes units they can always return a single range with a binary
+ %% content-range information.
+ {Ranges, Req2, State2} when length(Ranges) > 1 ->
+ %% We have to check whether there are sendfile tuples in the
+ %% ranges to be sent. If there are we must use stream_reply.
+ HasSendfile = [] =/= [true || {_, {sendfile, _, _, _}} <- Ranges],
+ case HasSendfile of
+ true -> send_multipart_ranged_body(Req2, State2, Ranges);
+ false -> set_multipart_ranged_body(Req2, State2, Ranges)
+ end
+ end catch Class:{case_clause, no_call}:Stacktrace ->
+ error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
+ 'A callback specified in ranges_provided/2 is not exported.'},
+ Stacktrace)
+ end.
+
+set_one_ranged_body(Req0, State, OneRange) ->
+ {ContentRange, Body} = prepare_range(Req0, OneRange),
+ Req1 = cowboy_req:set_resp_header(<<"content-range">>, ContentRange, Req0),
+ Req = cowboy_req:set_resp_body(Body, Req1),
+ respond(Req, State, 206).
+
+set_multipart_ranged_body(Req, State, [FirstRange|MoreRanges]) ->
+ Boundary = cow_multipart:boundary(),
+ ContentType = cowboy_req:resp_header(<<"content-type">>, Req),
+ {FirstContentRange, FirstPartBody} = prepare_range(Req, FirstRange),
+ FirstPartHead = cow_multipart:first_part(Boundary, [
+ {<<"content-type">>, ContentType},
+ {<<"content-range">>, FirstContentRange}
+ ]),
+ MoreParts = [begin
+ {NextContentRange, NextPartBody} = prepare_range(Req, NextRange),
+ NextPartHead = cow_multipart:part(Boundary, [
+ {<<"content-type">>, ContentType},
+ {<<"content-range">>, NextContentRange}
+ ]),
+ [NextPartHead, NextPartBody]
+ end || NextRange <- MoreRanges],
+ Body = [FirstPartHead, FirstPartBody, MoreParts, cow_multipart:close(Boundary)],
+ Req2 = cowboy_req:set_resp_header(<<"content-type">>,
+ [<<"multipart/byteranges; boundary=">>, Boundary], Req),
+ Req3 = cowboy_req:set_resp_body(Body, Req2),
+ respond(Req3, State, 206).
+
+%% Similar to set_multipart_ranged_body except we have to stream
+%% the data because the parts contain sendfile tuples.
+send_multipart_ranged_body(Req, State, [FirstRange|MoreRanges]) ->
+ Boundary = cow_multipart:boundary(),
+ ContentType = cowboy_req:resp_header(<<"content-type">>, Req),
+ Req2 = cowboy_req:set_resp_header(<<"content-type">>,
+ [<<"multipart/byteranges; boundary=">>, Boundary], Req),
+ Req3 = cowboy_req:stream_reply(206, Req2),
+ {FirstContentRange, FirstPartBody} = prepare_range(Req, FirstRange),
+ FirstPartHead = cow_multipart:first_part(Boundary, [
+ {<<"content-type">>, ContentType},
+ {<<"content-range">>, FirstContentRange}
+ ]),
+ cowboy_req:stream_body(FirstPartHead, nofin, Req3),
+ cowboy_req:stream_body(FirstPartBody, nofin, Req3),
+ _ = [begin
+ {NextContentRange, NextPartBody} = prepare_range(Req, NextRange),
+ NextPartHead = cow_multipart:part(Boundary, [
+ {<<"content-type">>, ContentType},
+ {<<"content-range">>, NextContentRange}
+ ]),
+ cowboy_req:stream_body(NextPartHead, nofin, Req3),
+ cowboy_req:stream_body(NextPartBody, nofin, Req3),
+ [NextPartHead, NextPartBody]
+ end || NextRange <- MoreRanges],
+ cowboy_req:stream_body(cow_multipart:close(Boundary), fin, Req3),
+ terminate(Req3, State).
+
+prepare_range(#{range := {RangeUnit, _}}, {{From, To, Total0}, Body}) ->
+ Total = case Total0 of
+ '*' -> <<"*">>;
+ _ -> integer_to_binary(Total0)
+ end,
+ ContentRange = [RangeUnit, $\s, integer_to_binary(From),
+ $-, integer_to_binary(To), $/, Total],
+ {ContentRange, Body};
+prepare_range(#{range := {RangeUnit, _}}, {RangeData, Body}) ->
+ {[RangeUnit, $\s, RangeData], Body}.
+
+%% We send the content-range header when we can on error.
+range_not_satisfiable(Req, State, undefined) ->
+ respond(Req, State, 416);
+range_not_satisfiable(Req0=#{range := {RangeUnit, _}}, State, RangeData) ->
+ Req = cowboy_req:set_resp_header(<<"content-range">>,
+ [RangeUnit, $\s, RangeData], Req0),
+ respond(Req, State, 416).
+
+%% Set the response headers and call the callback found using
+%% content_types_provided/2 to obtain the request body and add
+%% it to the response.
+set_resp_body(Req, State=#state{handler=Handler, content_type_a={_, Callback}}) ->
+ try case call(Req, State, Callback) of
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {Body, Req2, State2} ->
+ Req3 = cowboy_req:set_resp_body(Body, Req2),
+ multiple_choices(Req3, State2)
+ end catch Class:{case_clause, no_call}:Stacktrace ->
+ error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},
+ 'A callback specified in content_types_provided/2 is not exported.'},
+ Stacktrace)
+ end.
+
+multiple_choices(Req, State) ->
+ expect(Req, State, multiple_choices, false, 200, 300).
+
+%% Response utility functions.
+
+set_resp_etag(Req, State) ->
+ {Etag, Req2, State2} = generate_etag(Req, State),
+ case Etag of
+ undefined ->
+ {Req2, State2};
+ Etag ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"etag">>, encode_etag(Etag), Req2),
+ {Req3, State2}
+ end.
+
+-spec encode_etag({strong | weak, binary()}) -> iolist().
+encode_etag({strong, Etag}) -> [$",Etag,$"];
+encode_etag({weak, Etag}) -> ["W/\"",Etag,$"].
+
+set_resp_expires(Req, State) ->
+ {Expires, Req2, State2} = expires(Req, State),
+ case Expires of
+ Expires when is_atom(Expires) ->
+ {Req2, State2};
+ Expires when is_binary(Expires) ->
+ Req3 = cowboy_req:set_resp_header(
+ <<"expires">>, Expires, Req2),
+ {Req3, State2};
+ Expires ->
+ ExpiresBin = cowboy_clock:rfc1123(Expires),
+ Req3 = cowboy_req:set_resp_header(
+ <<"expires">>, ExpiresBin, Req2),
+ {Req3, State2}
+ end.
+
+%% Info retrieval. No logic.
+
+generate_etag(Req, State=#state{etag=no_call}) ->
+ {undefined, Req, State};
+generate_etag(Req, State=#state{etag=undefined}) ->
+ case unsafe_call(Req, State, generate_etag) of
+ no_call ->
+ {undefined, Req, State#state{etag=no_call}};
+ {Etag, Req2, State2} when is_binary(Etag) ->
+ Etag2 = cow_http_hd:parse_etag(Etag),
+ {Etag2, Req2, State2#state{etag=Etag2}};
+ {Etag, Req2, State2} ->
+ {Etag, Req2, State2#state{etag=Etag}}
+ end;
+generate_etag(Req, State=#state{etag=Etag}) ->
+ {Etag, Req, State}.
+
+last_modified(Req, State=#state{last_modified=no_call}) ->
+ {undefined, Req, State};
+last_modified(Req, State=#state{last_modified=undefined}) ->
+ case unsafe_call(Req, State, last_modified) of
+ no_call ->
+ {undefined, Req, State#state{last_modified=no_call}};
+ {LastModified, Req2, State2} ->
+ {LastModified, Req2, State2#state{last_modified=LastModified}}
+ end;
+last_modified(Req, State=#state{last_modified=LastModified}) ->
+ {LastModified, Req, State}.
+
+expires(Req, State=#state{expires=no_call}) ->
+ {undefined, Req, State};
+expires(Req, State=#state{expires=undefined}) ->
+ case unsafe_call(Req, State, expires) of
+ no_call ->
+ {undefined, Req, State#state{expires=no_call}};
+ {Expires, Req2, State2} ->
+ {Expires, Req2, State2#state{expires=Expires}}
+ end;
+expires(Req, State=#state{expires=Expires}) ->
+ {Expires, Req, State}.
+
+%% REST primitives.
+
+expect(Req, State, Callback, Expected, OnTrue, OnFalse) ->
+ case call(Req, State, Callback) of
+ no_call ->
+ next(Req, State, OnTrue);
+ {stop, Req2, State2} ->
+ terminate(Req2, State2);
+ {Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->
+ switch_handler(Switch, Req2, State2);
+ {Expected, Req2, State2} ->
+ next(Req2, State2, OnTrue);
+ {_Unexpected, Req2, State2} ->
+ next(Req2, State2, OnFalse)
+ end.
+
+call(Req0, State=#state{handler=Handler,
+ handler_state=HandlerState0}, Callback) ->
+ case erlang:function_exported(Handler, Callback, 2) of
+ true ->
+ try Handler:Callback(Req0, HandlerState0) of
+ no_call ->
+ no_call;
+ {Result, Req, HandlerState} ->
+ {Result, Req, State#state{handler_state=HandlerState}}
+ catch Class:Reason:Stacktrace ->
+ error_terminate(Req0, State, Class, Reason, Stacktrace)
+ end;
+ false ->
+ no_call
+ end.
+
+unsafe_call(Req0, State=#state{handler=Handler,
+ handler_state=HandlerState0}, Callback) ->
+ case erlang:function_exported(Handler, Callback, 2) of
+ false ->
+ no_call;
+ true ->
+ case Handler:Callback(Req0, HandlerState0) of
+ no_call ->
+ no_call;
+ {Result, Req, HandlerState} ->
+ {Result, Req, State#state{handler_state=HandlerState}}
+ end
+ end.
+
+next(Req, State, Next) when is_function(Next) ->
+ Next(Req, State);
+next(Req, State, StatusCode) when is_integer(StatusCode) ->
+ respond(Req, State, StatusCode).
+
+respond(Req0, State, StatusCode) ->
+ %% We remove the content-type header when there is no body,
+ %% except when the status code is 200 because it might have
+ %% been intended (for example sending an empty file).
+ Req = case cowboy_req:has_resp_body(Req0) of
+ true when StatusCode =:= 200 -> Req0;
+ true -> Req0;
+ false -> cowboy_req:delete_resp_header(<<"content-type">>, Req0)
+ end,
+ terminate(cowboy_req:reply(StatusCode, Req), State).
+
+switch_handler({switch_handler, Mod}, Req, #state{handler_state=HandlerState}) ->
+ {Mod, Req, HandlerState};
+switch_handler({switch_handler, Mod, Opts}, Req, #state{handler_state=HandlerState}) ->
+ {Mod, Req, HandlerState, Opts}.
+
+-spec error_terminate(cowboy_req:req(), #state{}, atom(), any(), any()) -> no_return().
+error_terminate(Req, #state{handler=Handler, handler_state=HandlerState}, Class, Reason, Stacktrace) ->
+ cowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler),
+ erlang:raise(Class, Reason, Stacktrace).
+
+terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
+ Result = cowboy_handler:terminate(normal, Req, HandlerState, Handler),
+ {ok, Req, Result}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_router.erl b/server/_build/default/lib/cowboy/src/cowboy_router.erl
new file mode 100644
index 0000000..0b7fe41
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_router.erl
@@ -0,0 +1,603 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% Routing middleware.
+%%
+%% Resolve the handler to be used for the request based on the
+%% routing information found in the <em>dispatch</em> environment value.
+%% When found, the handler module and associated data are added to
+%% the environment as the <em>handler</em> and <em>handler_opts</em> values
+%% respectively.
+%%
+%% If the route cannot be found, processing stops with either
+%% a 400 or a 404 reply.
+-module(cowboy_router).
+-behaviour(cowboy_middleware).
+
+-export([compile/1]).
+-export([execute/2]).
+
+-type bindings() :: #{atom() => any()}.
+-type tokens() :: [binary()].
+-export_type([bindings/0]).
+-export_type([tokens/0]).
+
+-type route_match() :: '_' | iodata().
+-type route_path() :: {Path::route_match(), Handler::module(), Opts::any()}
+ | {Path::route_match(), cowboy:fields(), Handler::module(), Opts::any()}.
+-type route_rule() :: {Host::route_match(), Paths::[route_path()]}
+ | {Host::route_match(), cowboy:fields(), Paths::[route_path()]}.
+-type routes() :: [route_rule()].
+-export_type([routes/0]).
+
+-type dispatch_match() :: '_' | <<_:8>> | [binary() | '_' | '...' | atom()].
+-type dispatch_path() :: {dispatch_match(), cowboy:fields(), module(), any()}.
+-type dispatch_rule() :: {Host::dispatch_match(), cowboy:fields(), Paths::[dispatch_path()]}.
+-opaque dispatch_rules() :: [dispatch_rule()].
+-export_type([dispatch_rules/0]).
+
+-spec compile(routes()) -> dispatch_rules().
+compile(Routes) ->
+ compile(Routes, []).
+
+compile([], Acc) ->
+ lists:reverse(Acc);
+compile([{Host, Paths}|Tail], Acc) ->
+ compile([{Host, [], Paths}|Tail], Acc);
+compile([{HostMatch, Fields, Paths}|Tail], Acc) ->
+ HostRules = case HostMatch of
+ '_' -> '_';
+ _ -> compile_host(HostMatch)
+ end,
+ PathRules = compile_paths(Paths, []),
+ Hosts = case HostRules of
+ '_' -> [{'_', Fields, PathRules}];
+ _ -> [{R, Fields, PathRules} || R <- HostRules]
+ end,
+ compile(Tail, Hosts ++ Acc).
+
+compile_host(HostMatch) when is_list(HostMatch) ->
+ compile_host(list_to_binary(HostMatch));
+compile_host(HostMatch) when is_binary(HostMatch) ->
+ compile_rules(HostMatch, $., [], [], <<>>).
+
+compile_paths([], Acc) ->
+ lists:reverse(Acc);
+compile_paths([{PathMatch, Handler, Opts}|Tail], Acc) ->
+ compile_paths([{PathMatch, [], Handler, Opts}|Tail], Acc);
+compile_paths([{PathMatch, Fields, Handler, Opts}|Tail], Acc)
+ when is_list(PathMatch) ->
+ compile_paths([{iolist_to_binary(PathMatch),
+ Fields, Handler, Opts}|Tail], Acc);
+compile_paths([{'_', Fields, Handler, Opts}|Tail], Acc) ->
+ compile_paths(Tail, [{'_', Fields, Handler, Opts}] ++ Acc);
+compile_paths([{<<"*">>, Fields, Handler, Opts}|Tail], Acc) ->
+ compile_paths(Tail, [{<<"*">>, Fields, Handler, Opts}|Acc]);
+compile_paths([{<< $/, PathMatch/bits >>, Fields, Handler, Opts}|Tail],
+ Acc) ->
+ PathRules = compile_rules(PathMatch, $/, [], [], <<>>),
+ Paths = [{lists:reverse(R), Fields, Handler, Opts} || R <- PathRules],
+ compile_paths(Tail, Paths ++ Acc);
+compile_paths([{PathMatch, _, _, _}|_], _) ->
+ error({badarg, "The following route MUST begin with a slash: "
+ ++ binary_to_list(PathMatch)}).
+
+compile_rules(<<>>, _, Segments, Rules, <<>>) ->
+ [Segments|Rules];
+compile_rules(<<>>, _, Segments, Rules, Acc) ->
+ [[Acc|Segments]|Rules];
+compile_rules(<< S, Rest/bits >>, S, Segments, Rules, <<>>) ->
+ compile_rules(Rest, S, Segments, Rules, <<>>);
+compile_rules(<< S, Rest/bits >>, S, Segments, Rules, Acc) ->
+ compile_rules(Rest, S, [Acc|Segments], Rules, <<>>);
+%% Colon on path segment start is special, otherwise allow.
+compile_rules(<< $:, Rest/bits >>, S, Segments, Rules, <<>>) ->
+ {NameBin, Rest2} = compile_binding(Rest, S, <<>>),
+ Name = binary_to_atom(NameBin, utf8),
+ compile_rules(Rest2, S, Segments, Rules, Name);
+compile_rules(<< $[, $., $., $., $], Rest/bits >>, S, Segments, Rules, Acc)
+ when Acc =:= <<>> ->
+ compile_rules(Rest, S, ['...'|Segments], Rules, Acc);
+compile_rules(<< $[, $., $., $., $], Rest/bits >>, S, Segments, Rules, Acc) ->
+ compile_rules(Rest, S, ['...', Acc|Segments], Rules, Acc);
+compile_rules(<< $[, S, Rest/bits >>, S, Segments, Rules, Acc) ->
+ compile_brackets(Rest, S, [Acc|Segments], Rules);
+compile_rules(<< $[, Rest/bits >>, S, Segments, Rules, <<>>) ->
+ compile_brackets(Rest, S, Segments, Rules);
+%% Open bracket in the middle of a segment.
+compile_rules(<< $[, _/bits >>, _, _, _, _) ->
+ error(badarg);
+%% Missing an open bracket.
+compile_rules(<< $], _/bits >>, _, _, _, _) ->
+ error(badarg);
+compile_rules(<< C, Rest/bits >>, S, Segments, Rules, Acc) ->
+ compile_rules(Rest, S, Segments, Rules, << Acc/binary, C >>).
+
+%% Everything past $: until the segment separator ($. for hosts,
+%% $/ for paths) or $[ or $] or end of binary is the binding name.
+compile_binding(<<>>, _, <<>>) ->
+ error(badarg);
+compile_binding(Rest = <<>>, _, Acc) ->
+ {Acc, Rest};
+compile_binding(Rest = << C, _/bits >>, S, Acc)
+ when C =:= S; C =:= $[; C =:= $] ->
+ {Acc, Rest};
+compile_binding(<< C, Rest/bits >>, S, Acc) ->
+ compile_binding(Rest, S, << Acc/binary, C >>).
+
+compile_brackets(Rest, S, Segments, Rules) ->
+ {Bracket, Rest2} = compile_brackets_split(Rest, <<>>, 0),
+ Rules1 = compile_rules(Rest2, S, Segments, [], <<>>),
+ Rules2 = compile_rules(<< Bracket/binary, Rest2/binary >>,
+ S, Segments, [], <<>>),
+ Rules ++ Rules2 ++ Rules1.
+
+%% Missing a close bracket.
+compile_brackets_split(<<>>, _, _) ->
+ error(badarg);
+%% Make sure we don't confuse the closing bracket we're looking for.
+compile_brackets_split(<< C, Rest/bits >>, Acc, N) when C =:= $[ ->
+ compile_brackets_split(Rest, << Acc/binary, C >>, N + 1);
+compile_brackets_split(<< C, Rest/bits >>, Acc, N) when C =:= $], N > 0 ->
+ compile_brackets_split(Rest, << Acc/binary, C >>, N - 1);
+%% That's the right one.
+compile_brackets_split(<< $], Rest/bits >>, Acc, 0) ->
+ {Acc, Rest};
+compile_brackets_split(<< C, Rest/bits >>, Acc, N) ->
+ compile_brackets_split(Rest, << Acc/binary, C >>, N).
+
+-spec execute(Req, Env)
+ -> {ok, Req, Env} | {stop, Req}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+execute(Req=#{host := Host, path := Path}, Env=#{dispatch := Dispatch0}) ->
+ Dispatch = case Dispatch0 of
+ {persistent_term, Key} -> persistent_term:get(Key);
+ _ -> Dispatch0
+ end,
+ case match(Dispatch, Host, Path) of
+ {ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} ->
+ {ok, Req#{
+ host_info => HostInfo,
+ path_info => PathInfo,
+ bindings => Bindings
+ }, Env#{
+ handler => Handler,
+ handler_opts => HandlerOpts
+ }};
+ {error, notfound, host} ->
+ {stop, cowboy_req:reply(400, Req)};
+ {error, badrequest, path} ->
+ {stop, cowboy_req:reply(400, Req)};
+ {error, notfound, path} ->
+ {stop, cowboy_req:reply(404, Req)}
+ end.
+
+%% Internal.
+
+%% Match hostname tokens and path tokens against dispatch rules.
+%%
+%% It is typically used for matching tokens for the hostname and path of
+%% the request against a global dispatch rule for your listener.
+%%
+%% Dispatch rules are a list of <em>{Hostname, PathRules}</em> tuples, with
+%% <em>PathRules</em> being a list of <em>{Path, HandlerMod, HandlerOpts}</em>.
+%%
+%% <em>Hostname</em> and <em>Path</em> are match rules and can be either the
+%% atom <em>'_'</em>, which matches everything, `<<"*">>', which match the
+%% wildcard path, or a list of tokens.
+%%
+%% Each token can be either a binary, the atom <em>'_'</em>,
+%% the atom '...' or a named atom. A binary token must match exactly,
+%% <em>'_'</em> matches everything for a single token, <em>'...'</em> matches
+%% everything for the rest of the tokens and a named atom will bind the
+%% corresponding token value and return it.
+%%
+%% The list of hostname tokens is reversed before matching. For example, if
+%% we were to match "www.ninenines.eu", we would first match "eu", then
+%% "ninenines", then "www". This means that in the context of hostnames,
+%% the <em>'...'</em> atom matches properly the lower levels of the domain
+%% as would be expected.
+%%
+%% When a result is found, this function will return the handler module and
+%% options found in the dispatch list, a key-value list of bindings and
+%% the tokens that were matched by the <em>'...'</em> atom for both the
+%% hostname and path.
+-spec match(dispatch_rules(), Host::binary() | tokens(), Path::binary())
+ -> {ok, module(), any(), bindings(),
+ HostInfo::undefined | tokens(),
+ PathInfo::undefined | tokens()}
+ | {error, notfound, host} | {error, notfound, path}
+ | {error, badrequest, path}.
+match([], _, _) ->
+ {error, notfound, host};
+%% If the host is '_' then there can be no constraints.
+match([{'_', [], PathMatchs}|_Tail], _, Path) ->
+ match_path(PathMatchs, undefined, Path, #{});
+match([{HostMatch, Fields, PathMatchs}|Tail], Tokens, Path)
+ when is_list(Tokens) ->
+ case list_match(Tokens, HostMatch, #{}) of
+ false ->
+ match(Tail, Tokens, Path);
+ {true, Bindings, HostInfo} ->
+ HostInfo2 = case HostInfo of
+ undefined -> undefined;
+ _ -> lists:reverse(HostInfo)
+ end,
+ case check_constraints(Fields, Bindings) of
+ {ok, Bindings2} ->
+ match_path(PathMatchs, HostInfo2, Path, Bindings2);
+ nomatch ->
+ match(Tail, Tokens, Path)
+ end
+ end;
+match(Dispatch, Host, Path) ->
+ match(Dispatch, split_host(Host), Path).
+
+-spec match_path([dispatch_path()],
+ HostInfo::undefined | tokens(), binary() | tokens(), bindings())
+ -> {ok, module(), any(), bindings(),
+ HostInfo::undefined | tokens(),
+ PathInfo::undefined | tokens()}
+ | {error, notfound, path} | {error, badrequest, path}.
+match_path([], _, _, _) ->
+ {error, notfound, path};
+%% If the path is '_' then there can be no constraints.
+match_path([{'_', [], Handler, Opts}|_Tail], HostInfo, _, Bindings) ->
+ {ok, Handler, Opts, Bindings, HostInfo, undefined};
+match_path([{<<"*">>, _, Handler, Opts}|_Tail], HostInfo, <<"*">>, Bindings) ->
+ {ok, Handler, Opts, Bindings, HostInfo, undefined};
+match_path([_|Tail], HostInfo, <<"*">>, Bindings) ->
+ match_path(Tail, HostInfo, <<"*">>, Bindings);
+match_path([{PathMatch, Fields, Handler, Opts}|Tail], HostInfo, Tokens,
+ Bindings) when is_list(Tokens) ->
+ case list_match(Tokens, PathMatch, Bindings) of
+ false ->
+ match_path(Tail, HostInfo, Tokens, Bindings);
+ {true, PathBinds, PathInfo} ->
+ case check_constraints(Fields, PathBinds) of
+ {ok, PathBinds2} ->
+ {ok, Handler, Opts, PathBinds2, HostInfo, PathInfo};
+ nomatch ->
+ match_path(Tail, HostInfo, Tokens, Bindings)
+ end
+ end;
+match_path(_Dispatch, _HostInfo, badrequest, _Bindings) ->
+ {error, badrequest, path};
+match_path(Dispatch, HostInfo, Path, Bindings) ->
+ match_path(Dispatch, HostInfo, split_path(Path), Bindings).
+
+check_constraints([], Bindings) ->
+ {ok, Bindings};
+check_constraints([Field|Tail], Bindings) when is_atom(Field) ->
+ check_constraints(Tail, Bindings);
+check_constraints([Field|Tail], Bindings) ->
+ Name = element(1, Field),
+ case Bindings of
+ #{Name := Value0} ->
+ Constraints = element(2, Field),
+ case cowboy_constraints:validate(Value0, Constraints) of
+ {ok, Value} ->
+ check_constraints(Tail, Bindings#{Name => Value});
+ {error, _} ->
+ nomatch
+ end;
+ _ ->
+ check_constraints(Tail, Bindings)
+ end.
+
+-spec split_host(binary()) -> tokens().
+split_host(Host) ->
+ split_host(Host, []).
+
+split_host(Host, Acc) ->
+ case binary:match(Host, <<".">>) of
+ nomatch when Host =:= <<>> ->
+ Acc;
+ nomatch ->
+ [Host|Acc];
+ {Pos, _} ->
+ << Segment:Pos/binary, _:8, Rest/bits >> = Host,
+ false = byte_size(Segment) == 0,
+ split_host(Rest, [Segment|Acc])
+ end.
+
+%% Following RFC2396, this function may return path segments containing any
+%% character, including <em>/</em> if, and only if, a <em>/</em> was escaped
+%% and part of a path segment.
+-spec split_path(binary()) -> tokens() | badrequest.
+split_path(<< $/, Path/bits >>) ->
+ split_path(Path, []);
+split_path(_) ->
+ badrequest.
+
+split_path(Path, Acc) ->
+ try
+ case binary:match(Path, <<"/">>) of
+ nomatch when Path =:= <<>> ->
+ remove_dot_segments(lists:reverse([cow_uri:urldecode(S) || S <- Acc]), []);
+ nomatch ->
+ remove_dot_segments(lists:reverse([cow_uri:urldecode(S) || S <- [Path|Acc]]), []);
+ {Pos, _} ->
+ << Segment:Pos/binary, _:8, Rest/bits >> = Path,
+ split_path(Rest, [Segment|Acc])
+ end
+ catch error:_ ->
+ badrequest
+ end.
+
+remove_dot_segments([], Acc) ->
+ lists:reverse(Acc);
+remove_dot_segments([<<".">>|Segments], Acc) ->
+ remove_dot_segments(Segments, Acc);
+remove_dot_segments([<<"..">>|Segments], Acc=[]) ->
+ remove_dot_segments(Segments, Acc);
+remove_dot_segments([<<"..">>|Segments], [_|Acc]) ->
+ remove_dot_segments(Segments, Acc);
+remove_dot_segments([S|Segments], Acc) ->
+ remove_dot_segments(Segments, [S|Acc]).
+
+-ifdef(TEST).
+remove_dot_segments_test_() ->
+ Tests = [
+ {[<<"a">>, <<"b">>, <<"c">>, <<".">>, <<"..">>, <<"..">>, <<"g">>], [<<"a">>, <<"g">>]},
+ {[<<"mid">>, <<"content=5">>, <<"..">>, <<"6">>], [<<"mid">>, <<"6">>]},
+ {[<<"..">>, <<"a">>], [<<"a">>]}
+ ],
+ [fun() -> R = remove_dot_segments(S, []) end || {S, R} <- Tests].
+-endif.
+
+-spec list_match(tokens(), dispatch_match(), bindings())
+ -> {true, bindings(), undefined | tokens()} | false.
+%% Atom '...' matches any trailing path, stop right now.
+list_match(List, ['...'], Binds) ->
+ {true, Binds, List};
+%% Atom '_' matches anything, continue.
+list_match([_E|Tail], ['_'|TailMatch], Binds) ->
+ list_match(Tail, TailMatch, Binds);
+%% Both values match, continue.
+list_match([E|Tail], [E|TailMatch], Binds) ->
+ list_match(Tail, TailMatch, Binds);
+%% Bind E to the variable name V and continue,
+%% unless V was already defined and E isn't identical to the previous value.
+list_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) ->
+ case Binds of
+ %% @todo This isn't right, the constraint must be applied FIRST
+ %% otherwise we can't check for example ints in both host/path.
+ #{V := E} ->
+ list_match(Tail, TailMatch, Binds);
+ #{V := _} ->
+ false;
+ _ ->
+ list_match(Tail, TailMatch, Binds#{V => E})
+ end;
+%% Match complete.
+list_match([], [], Binds) ->
+ {true, Binds, undefined};
+%% Values don't match, stop.
+list_match(_List, _Match, _Binds) ->
+ false.
+
+%% Tests.
+
+-ifdef(TEST).
+compile_test_() ->
+ Tests = [
+ %% Match any host and path.
+ {[{'_', [{'_', h, o}]}],
+ [{'_', [], [{'_', [], h, o}]}]},
+ {[{"cowboy.example.org",
+ [{"/", ha, oa}, {"/path/to/resource", hb, ob}]}],
+ [{[<<"org">>, <<"example">>, <<"cowboy">>], [], [
+ {[], [], ha, oa},
+ {[<<"path">>, <<"to">>, <<"resource">>], [], hb, ob}]}]},
+ {[{'_', [{"/path/to/resource/", h, o}]}],
+ [{'_', [], [{[<<"path">>, <<"to">>, <<"resource">>], [], h, o}]}]},
+ % Cyrillic from a latin1 encoded file.
+ {[{'_', [{[47,208,191,209,131,209,130,209,140,47,208,186,47,209,128,
+ 208,181,209,129,209,131,209,128,209,129,209,131,47], h, o}]}],
+ [{'_', [], [{[<<208,191,209,131,209,130,209,140>>, <<208,186>>,
+ <<209,128,208,181,209,129,209,131,209,128,209,129,209,131>>],
+ [], h, o}]}]},
+ {[{"cowboy.example.org.", [{'_', h, o}]}],
+ [{[<<"org">>, <<"example">>, <<"cowboy">>], [], [{'_', [], h, o}]}]},
+ {[{".cowboy.example.org", [{'_', h, o}]}],
+ [{[<<"org">>, <<"example">>, <<"cowboy">>], [], [{'_', [], h, o}]}]},
+ % Cyrillic from a latin1 encoded file.
+ {[{[208,189,208,181,208,186,208,184,208,185,46,209,129,208,176,
+ 208,185,209,130,46,209,128,209,132,46], [{'_', h, o}]}],
+ [{[<<209,128,209,132>>, <<209,129,208,176,208,185,209,130>>,
+ <<208,189,208,181,208,186,208,184,208,185>>],
+ [], [{'_', [], h, o}]}]},
+ {[{":subdomain.example.org", [{"/hats/:name/prices", h, o}]}],
+ [{[<<"org">>, <<"example">>, subdomain], [], [
+ {[<<"hats">>, name, <<"prices">>], [], h, o}]}]},
+ {[{"ninenines.:_", [{"/hats/:_", h, o}]}],
+ [{['_', <<"ninenines">>], [], [{[<<"hats">>, '_'], [], h, o}]}]},
+ {[{"[www.]ninenines.eu",
+ [{"/horses", h, o}, {"/hats/[page/:number]", h, o}]}], [
+ {[<<"eu">>, <<"ninenines">>], [], [
+ {[<<"horses">>], [], h, o},
+ {[<<"hats">>], [], h, o},
+ {[<<"hats">>, <<"page">>, number], [], h, o}]},
+ {[<<"eu">>, <<"ninenines">>, <<"www">>], [], [
+ {[<<"horses">>], [], h, o},
+ {[<<"hats">>], [], h, o},
+ {[<<"hats">>, <<"page">>, number], [], h, o}]}]},
+ {[{'_', [{"/hats/:page/:number", h, o}]}], [{'_', [], [
+ {[<<"hats">>, page, number], [], h, o}]}]},
+ {[{'_', [{"/hats/[page/[:number]]", h, o}]}], [{'_', [], [
+ {[<<"hats">>], [], h, o},
+ {[<<"hats">>, <<"page">>], [], h, o},
+ {[<<"hats">>, <<"page">>, number], [], h, o}]}]},
+ {[{"[...]ninenines.eu", [{"/hats/[...]", h, o}]}],
+ [{[<<"eu">>, <<"ninenines">>, '...'], [], [
+ {[<<"hats">>, '...'], [], h, o}]}]},
+ %% Path segment containing a colon.
+ {[{'_', [{"/foo/bar:blah", h, o}]}], [{'_', [], [
+ {[<<"foo">>, <<"bar:blah">>], [], h, o}]}]}
+ ],
+ [{lists:flatten(io_lib:format("~p", [Rt])),
+ fun() -> Rs = compile(Rt) end} || {Rt, Rs} <- Tests].
+
+split_host_test_() ->
+ Tests = [
+ {<<"">>, []},
+ {<<"*">>, [<<"*">>]},
+ {<<"cowboy.ninenines.eu">>,
+ [<<"eu">>, <<"ninenines">>, <<"cowboy">>]},
+ {<<"ninenines.eu">>,
+ [<<"eu">>, <<"ninenines">>]},
+ {<<"ninenines.eu.">>,
+ [<<"eu">>, <<"ninenines">>]},
+ {<<"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">>,
+ [<<"z">>, <<"y">>, <<"x">>, <<"w">>, <<"v">>, <<"u">>, <<"t">>,
+ <<"s">>, <<"r">>, <<"q">>, <<"p">>, <<"o">>, <<"n">>, <<"m">>,
+ <<"l">>, <<"k">>, <<"j">>, <<"i">>, <<"h">>, <<"g">>, <<"f">>,
+ <<"e">>, <<"d">>, <<"c">>, <<"b">>, <<"a">>]}
+ ],
+ [{H, fun() -> R = split_host(H) end} || {H, R} <- Tests].
+
+split_path_test_() ->
+ Tests = [
+ {<<"/">>, []},
+ {<<"/extend//cowboy">>, [<<"extend">>, <<>>, <<"cowboy">>]},
+ {<<"/users">>, [<<"users">>]},
+ {<<"/users/42/friends">>, [<<"users">>, <<"42">>, <<"friends">>]},
+ {<<"/users/a%20b/c%21d">>, [<<"users">>, <<"a b">>, <<"c!d">>]}
+ ],
+ [{P, fun() -> R = split_path(P) end} || {P, R} <- Tests].
+
+match_test_() ->
+ Dispatch = [
+ {[<<"eu">>, <<"ninenines">>, '_', <<"www">>], [], [
+ {[<<"users">>, '_', <<"mails">>], [], match_any_subdomain_users, []}
+ ]},
+ {[<<"eu">>, <<"ninenines">>], [], [
+ {[<<"users">>, id, <<"friends">>], [], match_extend_users_friends, []},
+ {'_', [], match_extend, []}
+ ]},
+ {[var, <<"ninenines">>], [], [
+ {[<<"threads">>, var], [], match_duplicate_vars,
+ [we, {expect, two}, var, here]}
+ ]},
+ {[ext, <<"erlang">>], [], [
+ {'_', [], match_erlang_ext, []}
+ ]},
+ {'_', [], [
+ {[<<"users">>, id, <<"friends">>], [], match_users_friends, []},
+ {'_', [], match_any, []}
+ ]}
+ ],
+ Tests = [
+ {<<"any">>, <<"/">>, {ok, match_any, [], #{}}},
+ {<<"www.any.ninenines.eu">>, <<"/users/42/mails">>,
+ {ok, match_any_subdomain_users, [], #{}}},
+ {<<"www.ninenines.eu">>, <<"/users/42/mails">>,
+ {ok, match_any, [], #{}}},
+ {<<"www.ninenines.eu">>, <<"/">>,
+ {ok, match_any, [], #{}}},
+ {<<"www.any.ninenines.eu">>, <<"/not_users/42/mails">>,
+ {error, notfound, path}},
+ {<<"ninenines.eu">>, <<"/">>,
+ {ok, match_extend, [], #{}}},
+ {<<"ninenines.eu">>, <<"/users/42/friends">>,
+ {ok, match_extend_users_friends, [], #{id => <<"42">>}}},
+ {<<"erlang.fr">>, '_',
+ {ok, match_erlang_ext, [], #{ext => <<"fr">>}}},
+ {<<"any">>, <<"/users/444/friends">>,
+ {ok, match_users_friends, [], #{id => <<"444">>}}},
+ {<<"any">>, <<"/users//friends">>,
+ {ok, match_users_friends, [], #{id => <<>>}}}
+ ],
+ [{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
+ {ok, Handler, Opts, Binds, undefined, undefined}
+ = match(Dispatch, H, P)
+ end} || {H, P, {ok, Handler, Opts, Binds}} <- Tests].
+
+match_info_test_() ->
+ Dispatch = [
+ {[<<"eu">>, <<"ninenines">>, <<"www">>], [], [
+ {[<<"pathinfo">>, <<"is">>, <<"next">>, '...'], [], match_path, []}
+ ]},
+ {[<<"eu">>, <<"ninenines">>, '...'], [], [
+ {'_', [], match_any, []}
+ ]}
+ ],
+ Tests = [
+ {<<"ninenines.eu">>, <<"/">>,
+ {ok, match_any, [], #{}, [], undefined}},
+ {<<"bugs.ninenines.eu">>, <<"/">>,
+ {ok, match_any, [], #{}, [<<"bugs">>], undefined}},
+ {<<"cowboy.bugs.ninenines.eu">>, <<"/">>,
+ {ok, match_any, [], #{}, [<<"cowboy">>, <<"bugs">>], undefined}},
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next">>,
+ {ok, match_path, [], #{}, undefined, []}},
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/path_info">>,
+ {ok, match_path, [], #{}, undefined, [<<"path_info">>]}},
+ {<<"www.ninenines.eu">>, <<"/pathinfo/is/next/foo/bar">>,
+ {ok, match_path, [], #{}, undefined, [<<"foo">>, <<"bar">>]}}
+ ],
+ [{lists:flatten(io_lib:format("~p, ~p", [H, P])), fun() ->
+ R = match(Dispatch, H, P)
+ end} || {H, P, R} <- Tests].
+
+match_constraints_test() ->
+ Dispatch0 = [{'_', [],
+ [{[<<"path">>, value], [{value, int}], match, []}]}],
+ {ok, _, [], #{value := 123}, _, _} = match(Dispatch0,
+ <<"ninenines.eu">>, <<"/path/123">>),
+ {ok, _, [], #{value := 123}, _, _} = match(Dispatch0,
+ <<"ninenines.eu">>, <<"/path/123/">>),
+ {error, notfound, path} = match(Dispatch0,
+ <<"ninenines.eu">>, <<"/path/NaN/">>),
+ Dispatch1 = [{'_', [],
+ [{[<<"path">>, value, <<"more">>], [{value, nonempty}], match, []}]}],
+ {ok, _, [], #{value := <<"something">>}, _, _} = match(Dispatch1,
+ <<"ninenines.eu">>, <<"/path/something/more">>),
+ {error, notfound, path} = match(Dispatch1,
+ <<"ninenines.eu">>, <<"/path//more">>),
+ Dispatch2 = [{'_', [], [{[<<"path">>, username],
+ [{username, fun(_, Value) ->
+ case cowboy_bstr:to_lower(Value) of
+ Value -> {ok, Value};
+ _ -> {error, not_lowercase}
+ end end}],
+ match, []}]}],
+ {ok, _, [], #{username := <<"essen">>}, _, _} = match(Dispatch2,
+ <<"ninenines.eu">>, <<"/path/essen">>),
+ {error, notfound, path} = match(Dispatch2,
+ <<"ninenines.eu">>, <<"/path/ESSEN">>),
+ ok.
+
+match_same_bindings_test() ->
+ Dispatch = [{[same, same], [], [{'_', [], match, []}]}],
+ {ok, _, [], #{same := <<"eu">>}, _, _} = match(Dispatch,
+ <<"eu.eu">>, <<"/">>),
+ {error, notfound, host} = match(Dispatch,
+ <<"ninenines.eu">>, <<"/">>),
+ Dispatch2 = [{[<<"eu">>, <<"ninenines">>, user], [],
+ [{[<<"path">>, user], [], match, []}]}],
+ {ok, _, [], #{user := <<"essen">>}, _, _} = match(Dispatch2,
+ <<"essen.ninenines.eu">>, <<"/path/essen">>),
+ {ok, _, [], #{user := <<"essen">>}, _, _} = match(Dispatch2,
+ <<"essen.ninenines.eu">>, <<"/path/essen/">>),
+ {error, notfound, path} = match(Dispatch2,
+ <<"essen.ninenines.eu">>, <<"/path/notessen">>),
+ Dispatch3 = [{'_', [], [{[same, same], [], match, []}]}],
+ {ok, _, [], #{same := <<"path">>}, _, _} = match(Dispatch3,
+ <<"ninenines.eu">>, <<"/path/path">>),
+ {error, notfound, path} = match(Dispatch3,
+ <<"ninenines.eu">>, <<"/path/to">>),
+ ok.
+-endif.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_static.erl b/server/_build/default/lib/cowboy/src/cowboy_static.erl
new file mode 100644
index 0000000..b0cf146
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_static.erl
@@ -0,0 +1,418 @@
+%% Copyright (c) 2013-2017, Loïc Hoguin <essen@ninenines.eu>
+%% Copyright (c) 2011, Magnus Klaar <magnus.klaar@gmail.com>
+%%
+%% 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(cowboy_static).
+
+-export([init/2]).
+-export([malformed_request/2]).
+-export([forbidden/2]).
+-export([content_types_provided/2]).
+-export([charsets_provided/2]).
+-export([ranges_provided/2]).
+-export([resource_exists/2]).
+-export([last_modified/2]).
+-export([generate_etag/2]).
+-export([get_file/2]).
+
+-type extra_charset() :: {charset, module(), function()} | {charset, binary()}.
+-type extra_etag() :: {etag, module(), function()} | {etag, false}.
+-type extra_mimetypes() :: {mimetypes, module(), function()}
+ | {mimetypes, binary() | {binary(), binary(), [{binary(), binary()}]}}.
+-type extra() :: [extra_charset() | extra_etag() | extra_mimetypes()].
+-type opts() :: {file | dir, string() | binary()}
+ | {file | dir, string() | binary(), extra()}
+ | {priv_file | priv_dir, atom(), string() | binary()}
+ | {priv_file | priv_dir, atom(), string() | binary(), extra()}.
+-export_type([opts/0]).
+
+-include_lib("kernel/include/file.hrl").
+
+-type state() :: {binary(), {direct | archive, #file_info{}}
+ | {error, atom()}, extra()}.
+
+%% Resolve the file that will be sent and get its file information.
+%% If the handler is configured to manage a directory, check that the
+%% requested file is inside the configured directory.
+
+-spec init(Req, opts()) -> {cowboy_rest, Req, error | state()} when Req::cowboy_req:req().
+init(Req, {Name, Path}) ->
+ init_opts(Req, {Name, Path, []});
+init(Req, {Name, App, Path})
+ when Name =:= priv_file; Name =:= priv_dir ->
+ init_opts(Req, {Name, App, Path, []});
+init(Req, Opts) ->
+ init_opts(Req, Opts).
+
+init_opts(Req, {priv_file, App, Path, Extra}) ->
+ {PrivPath, HowToAccess} = priv_path(App, Path),
+ init_info(Req, absname(PrivPath), HowToAccess, Extra);
+init_opts(Req, {file, Path, Extra}) ->
+ init_info(Req, absname(Path), direct, Extra);
+init_opts(Req, {priv_dir, App, Path, Extra}) ->
+ {PrivPath, HowToAccess} = priv_path(App, Path),
+ init_dir(Req, PrivPath, HowToAccess, Extra);
+init_opts(Req, {dir, Path, Extra}) ->
+ init_dir(Req, Path, direct, Extra).
+
+priv_path(App, Path) ->
+ case code:priv_dir(App) of
+ {error, bad_name} ->
+ error({badarg, "Can't resolve the priv_dir of application "
+ ++ atom_to_list(App)});
+ PrivDir when is_list(Path) ->
+ {
+ PrivDir ++ "/" ++ Path,
+ how_to_access_app_priv(PrivDir)
+ };
+ PrivDir when is_binary(Path) ->
+ {
+ << (list_to_binary(PrivDir))/binary, $/, Path/binary >>,
+ how_to_access_app_priv(PrivDir)
+ }
+ end.
+
+how_to_access_app_priv(PrivDir) ->
+ %% If the priv directory is not a directory, it must be
+ %% inside an Erlang application .ez archive. We call
+ %% how_to_access_app_priv1() to find the corresponding archive.
+ case filelib:is_dir(PrivDir) of
+ true -> direct;
+ false -> how_to_access_app_priv1(PrivDir)
+ end.
+
+how_to_access_app_priv1(Dir) ->
+ %% We go "up" by one path component at a time and look for a
+ %% regular file.
+ Archive = filename:dirname(Dir),
+ case Archive of
+ Dir ->
+ %% filename:dirname() returned its argument:
+ %% we reach the root directory. We found no
+ %% archive so we return 'direct': the given priv
+ %% directory doesn't exist.
+ direct;
+ _ ->
+ case filelib:is_regular(Archive) of
+ true -> {archive, Archive};
+ false -> how_to_access_app_priv1(Archive)
+ end
+ end.
+
+absname(Path) when is_list(Path) ->
+ filename:absname(list_to_binary(Path));
+absname(Path) when is_binary(Path) ->
+ filename:absname(Path).
+
+init_dir(Req, Path, HowToAccess, Extra) when is_list(Path) ->
+ init_dir(Req, list_to_binary(Path), HowToAccess, Extra);
+init_dir(Req, Path, HowToAccess, Extra) ->
+ Dir = fullpath(filename:absname(Path)),
+ case cowboy_req:path_info(Req) of
+ %% When dir/priv_dir are used and there is no path_info
+ %% this is a configuration error and we abort immediately.
+ undefined ->
+ {ok, cowboy_req:reply(500, Req), error};
+ PathInfo ->
+ case validate_reserved(PathInfo) of
+ error ->
+ {cowboy_rest, Req, error};
+ ok ->
+ Filepath = filename:join([Dir|PathInfo]),
+ Len = byte_size(Dir),
+ case fullpath(Filepath) of
+ << Dir:Len/binary, $/, _/binary >> ->
+ init_info(Req, Filepath, HowToAccess, Extra);
+ << Dir:Len/binary >> ->
+ init_info(Req, Filepath, HowToAccess, Extra);
+ _ ->
+ {cowboy_rest, Req, error}
+ end
+ end
+ end.
+
+validate_reserved([]) ->
+ ok;
+validate_reserved([P|Tail]) ->
+ case validate_reserved1(P) of
+ ok -> validate_reserved(Tail);
+ error -> error
+ end.
+
+%% We always reject forward slash, backward slash and NUL as
+%% those have special meanings across the supported platforms.
+%% We could support the backward slash on some platforms but
+%% for the sake of consistency and simplicity we don't.
+validate_reserved1(<<>>) ->
+ ok;
+validate_reserved1(<<$/, _/bits>>) ->
+ error;
+validate_reserved1(<<$\\, _/bits>>) ->
+ error;
+validate_reserved1(<<0, _/bits>>) ->
+ error;
+validate_reserved1(<<_, Rest/bits>>) ->
+ validate_reserved1(Rest).
+
+fullpath(Path) ->
+ fullpath(filename:split(Path), []).
+fullpath([], Acc) ->
+ filename:join(lists:reverse(Acc));
+fullpath([<<".">>|Tail], Acc) ->
+ fullpath(Tail, Acc);
+fullpath([<<"..">>|Tail], Acc=[_]) ->
+ fullpath(Tail, Acc);
+fullpath([<<"..">>|Tail], [_|Acc]) ->
+ fullpath(Tail, Acc);
+fullpath([Segment|Tail], Acc) ->
+ fullpath(Tail, [Segment|Acc]).
+
+init_info(Req, Path, HowToAccess, Extra) ->
+ Info = read_file_info(Path, HowToAccess),
+ {cowboy_rest, Req, {Path, Info, Extra}}.
+
+read_file_info(Path, direct) ->
+ case file:read_file_info(Path, [{time, universal}]) of
+ {ok, Info} -> {direct, Info};
+ Error -> Error
+ end;
+read_file_info(Path, {archive, Archive}) ->
+ case file:read_file_info(Archive, [{time, universal}]) of
+ {ok, ArchiveInfo} ->
+ %% The Erlang application archive is fine.
+ %% Now check if the requested file is in that
+ %% archive. We also need the file_info to merge
+ %% them with the archive's one.
+ PathS = binary_to_list(Path),
+ case erl_prim_loader:read_file_info(PathS) of
+ {ok, ContainedFileInfo} ->
+ Info = fix_archived_file_info(
+ ArchiveInfo,
+ ContainedFileInfo),
+ {archive, Info};
+ error ->
+ {error, enoent}
+ end;
+ Error ->
+ Error
+ end.
+
+fix_archived_file_info(ArchiveInfo, ContainedFileInfo) ->
+ %% We merge the archive and content #file_info because we are
+ %% interested by the timestamps of the archive, but the type and
+ %% size of the contained file/directory.
+ %%
+ %% We reset the access to 'read', because we won't rewrite the
+ %% archive.
+ ArchiveInfo#file_info{
+ size = ContainedFileInfo#file_info.size,
+ type = ContainedFileInfo#file_info.type,
+ access = read
+ }.
+
+-ifdef(TEST).
+fullpath_test_() ->
+ Tests = [
+ {<<"/home/cowboy">>, <<"/home/cowboy">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/./">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/./././././.">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/abc/..">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/abc/../">>},
+ {<<"/home/cowboy">>, <<"/home/cowboy/abc/./../.">>},
+ {<<"/">>, <<"/home/cowboy/../../../../../..">>},
+ {<<"/etc/passwd">>, <<"/home/cowboy/../../etc/passwd">>}
+ ],
+ [{P, fun() -> R = fullpath(P) end} || {R, P} <- Tests].
+
+good_path_check_test_() ->
+ Tests = [
+ <<"/home/cowboy/file">>,
+ <<"/home/cowboy/file/">>,
+ <<"/home/cowboy/./file">>,
+ <<"/home/cowboy/././././././file">>,
+ <<"/home/cowboy/abc/../file">>,
+ <<"/home/cowboy/abc/../file">>,
+ <<"/home/cowboy/abc/./.././file">>
+ ],
+ [{P, fun() ->
+ case fullpath(P) of
+ << "/home/cowboy/", _/bits >> -> ok
+ end
+ end} || P <- Tests].
+
+bad_path_check_test_() ->
+ Tests = [
+ <<"/home/cowboy/../../../../../../file">>,
+ <<"/home/cowboy/../../etc/passwd">>
+ ],
+ [{P, fun() ->
+ error = case fullpath(P) of
+ << "/home/cowboy/", _/bits >> -> ok;
+ _ -> error
+ end
+ end} || P <- Tests].
+
+good_path_win32_check_test_() ->
+ Tests = case os:type() of
+ {unix, _} ->
+ [];
+ {win32, _} ->
+ [
+ <<"c:/home/cowboy/file">>,
+ <<"c:/home/cowboy/file/">>,
+ <<"c:/home/cowboy/./file">>,
+ <<"c:/home/cowboy/././././././file">>,
+ <<"c:/home/cowboy/abc/../file">>,
+ <<"c:/home/cowboy/abc/../file">>,
+ <<"c:/home/cowboy/abc/./.././file">>
+ ]
+ end,
+ [{P, fun() ->
+ case fullpath(P) of
+ << "c:/home/cowboy/", _/bits >> -> ok
+ end
+ end} || P <- Tests].
+
+bad_path_win32_check_test_() ->
+ Tests = case os:type() of
+ {unix, _} ->
+ [];
+ {win32, _} ->
+ [
+ <<"c:/home/cowboy/../../secretfile.bat">>,
+ <<"c:/home/cowboy/c:/secretfile.bat">>,
+ <<"c:/home/cowboy/..\\..\\secretfile.bat">>,
+ <<"c:/home/cowboy/c:\\secretfile.bat">>
+ ]
+ end,
+ [{P, fun() ->
+ error = case fullpath(P) of
+ << "c:/home/cowboy/", _/bits >> -> ok;
+ _ -> error
+ end
+ end} || P <- Tests].
+-endif.
+
+%% Reject requests that tried to access a file outside
+%% the target directory, or used reserved characters.
+
+-spec malformed_request(Req, State)
+ -> {boolean(), Req, State}.
+malformed_request(Req, State) ->
+ {State =:= error, Req, State}.
+
+%% Directories, files that can't be accessed at all and
+%% files with no read flag are forbidden.
+
+-spec forbidden(Req, State)
+ -> {boolean(), Req, State}
+ when State::state().
+forbidden(Req, State={_, {_, #file_info{type=directory}}, _}) ->
+ {true, Req, State};
+forbidden(Req, State={_, {error, eacces}, _}) ->
+ {true, Req, State};
+forbidden(Req, State={_, {_, #file_info{access=Access}}, _})
+ when Access =:= write; Access =:= none ->
+ {true, Req, State};
+forbidden(Req, State) ->
+ {false, Req, State}.
+
+%% Detect the mimetype of the file.
+
+-spec content_types_provided(Req, State)
+ -> {[{binary(), get_file}], Req, State}
+ when State::state().
+content_types_provided(Req, State={Path, _, Extra}) when is_list(Extra) ->
+ case lists:keyfind(mimetypes, 1, Extra) of
+ false ->
+ {[{cow_mimetypes:web(Path), get_file}], Req, State};
+ {mimetypes, Module, Function} ->
+ {[{Module:Function(Path), get_file}], Req, State};
+ {mimetypes, Type} ->
+ {[{Type, get_file}], Req, State}
+ end.
+
+%% Detect the charset of the file.
+
+-spec charsets_provided(Req, State)
+ -> {[binary()], Req, State}
+ when State::state().
+charsets_provided(Req, State={Path, _, Extra}) ->
+ case lists:keyfind(charset, 1, Extra) of
+ %% We simulate the callback not being exported.
+ false ->
+ no_call;
+ {charset, Module, Function} ->
+ {[Module:Function(Path)], Req, State};
+ {charset, Charset} when is_binary(Charset) ->
+ {[Charset], Req, State}
+ end.
+
+%% Enable support for range requests.
+
+-spec ranges_provided(Req, State)
+ -> {[{binary(), auto}], Req, State}
+ when State::state().
+ranges_provided(Req, State) ->
+ {[{<<"bytes">>, auto}], Req, State}.
+
+%% Assume the resource doesn't exist if it's not a regular file.
+
+-spec resource_exists(Req, State)
+ -> {boolean(), Req, State}
+ when State::state().
+resource_exists(Req, State={_, {_, #file_info{type=regular}}, _}) ->
+ {true, Req, State};
+resource_exists(Req, State) ->
+ {false, Req, State}.
+
+%% Generate an etag for the file.
+
+-spec generate_etag(Req, State)
+ -> {{strong | weak, binary()}, Req, State}
+ when State::state().
+generate_etag(Req, State={Path, {_, #file_info{size=Size, mtime=Mtime}},
+ Extra}) ->
+ case lists:keyfind(etag, 1, Extra) of
+ false ->
+ {generate_default_etag(Size, Mtime), Req, State};
+ {etag, Module, Function} ->
+ {Module:Function(Path, Size, Mtime), Req, State};
+ {etag, false} ->
+ {undefined, Req, State}
+ end.
+
+generate_default_etag(Size, Mtime) ->
+ {strong, integer_to_binary(erlang:phash2({Size, Mtime}, 16#ffffffff))}.
+
+%% Return the time of last modification of the file.
+
+-spec last_modified(Req, State)
+ -> {calendar:datetime(), Req, State}
+ when State::state().
+last_modified(Req, State={_, {_, #file_info{mtime=Modified}}, _}) ->
+ {Modified, Req, State}.
+
+%% Stream the file.
+
+-spec get_file(Req, State)
+ -> {{sendfile, 0, non_neg_integer(), binary()}, Req, State}
+ when State::state().
+get_file(Req, State={Path, {direct, #file_info{size=Size}}, _}) ->
+ {{sendfile, 0, Size, Path}, Req, State};
+get_file(Req, State={Path, {archive, _}, _}) ->
+ PathS = binary_to_list(Path),
+ {ok, Bin, _} = erl_prim_loader:get_file(PathS),
+ {Bin, Req, State}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_stream.erl b/server/_build/default/lib/cowboy/src/cowboy_stream.erl
new file mode 100644
index 0000000..2dad6d0
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_stream.erl
@@ -0,0 +1,193 @@
+%% Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_stream).
+
+-type state() :: any().
+-type human_reason() :: atom().
+
+-type streamid() :: any().
+-export_type([streamid/0]).
+
+-type fin() :: fin | nofin.
+-export_type([fin/0]).
+
+%% @todo Perhaps it makes more sense to have resp_body in this module?
+
+-type resp_command()
+ :: {response, cowboy:http_status(), cowboy:http_headers(), cowboy_req:resp_body()}.
+-export_type([resp_command/0]).
+
+-type commands() :: [{inform, cowboy:http_status(), cowboy:http_headers()}
+ | resp_command()
+ | {headers, cowboy:http_status(), cowboy:http_headers()}
+ | {data, fin(), cowboy_req:resp_body()}
+ | {trailers, cowboy:http_headers()}
+ | {push, binary(), binary(), binary(), inet:port_number(),
+ binary(), binary(), cowboy:http_headers()}
+ | {flow, pos_integer()}
+ | {spawn, pid(), timeout()}
+ | {error_response, cowboy:http_status(), cowboy:http_headers(), iodata()}
+ | {switch_protocol, cowboy:http_headers(), module(), state()}
+ | {internal_error, any(), human_reason()}
+ | {set_options, map()}
+ | {log, logger:level(), io:format(), list()}
+ | stop].
+-export_type([commands/0]).
+
+-type reason() :: normal | switch_protocol
+ | {internal_error, timeout | {error | exit | throw, any()}, human_reason()}
+ | {socket_error, closed | atom(), human_reason()}
+ | {stream_error, cow_http2:error(), human_reason()}
+ | {connection_error, cow_http2:error(), human_reason()}
+ | {stop, cow_http2:frame() | {exit, any()}, human_reason()}.
+-export_type([reason/0]).
+
+-type partial_req() :: map(). %% @todo Take what's in cowboy_req with everything? optional.
+-export_type([partial_req/0]).
+
+-callback init(streamid(), cowboy_req:req(), cowboy:opts()) -> {commands(), state()}.
+-callback data(streamid(), fin(), binary(), State) -> {commands(), State} when State::state().
+-callback info(streamid(), any(), State) -> {commands(), State} when State::state().
+-callback terminate(streamid(), reason(), state()) -> any().
+-callback early_error(streamid(), reason(), partial_req(), Resp, cowboy:opts())
+ -> Resp when Resp::resp_command().
+
+%% @todo To optimize the number of active timers we could have a command
+%% that enables a timeout that is called in the absence of any other call,
+%% similar to what gen_server does. However the nice thing about this is
+%% that the connection process can keep a single timer around (the same
+%% one that would be used to detect half-closed sockets) and use this
+%% timer and other events to trigger the timeout in streams at their
+%% intended time.
+%%
+%% This same timer can be used to try and send PING frames to help detect
+%% that the connection is indeed unresponsive.
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+-export([make_error_log/5]).
+
+%% Note that this and other functions in this module do NOT catch
+%% exceptions. We want the exception to go all the way down to the
+%% protocol code.
+%%
+%% OK the failure scenario is not so clear. The problem is
+%% that the failure at any point in init/3 will result in the
+%% corresponding state being lost. I am unfortunately not
+%% confident we can do anything about this. If the crashing
+%% handler just created a process, we'll never know about it.
+%% Therefore at this time I choose to leave all failure handling
+%% to the protocol process.
+%%
+%% Note that a failure in init/3 will result in terminate/3
+%% NOT being called. This is because the state is not available.
+
+-spec init(streamid(), cowboy_req:req(), cowboy:opts())
+ -> {commands(), {module(), state()} | undefined}.
+init(StreamID, Req, Opts) ->
+ case maps:get(stream_handlers, Opts, [cowboy_stream_h]) of
+ [] ->
+ {[], undefined};
+ [Handler|Tail] ->
+ %% We call the next handler and remove it from the list of
+ %% stream handlers. This means that handlers that run after
+ %% it have no knowledge it exists. Should user require this
+ %% knowledge they can just define a separate option that will
+ %% be left untouched.
+ {Commands, State} = Handler:init(StreamID, Req, Opts#{stream_handlers => Tail}),
+ {Commands, {Handler, State}}
+ end.
+
+-spec data(streamid(), fin(), binary(), {Handler, State} | undefined)
+ -> {commands(), {Handler, State} | undefined}
+ when Handler::module(), State::state().
+data(_, _, _, undefined) ->
+ {[], undefined};
+data(StreamID, IsFin, Data, {Handler, State0}) ->
+ {Commands, State} = Handler:data(StreamID, IsFin, Data, State0),
+ {Commands, {Handler, State}}.
+
+-spec info(streamid(), any(), {Handler, State} | undefined)
+ -> {commands(), {Handler, State} | undefined}
+ when Handler::module(), State::state().
+info(_, _, undefined) ->
+ {[], undefined};
+info(StreamID, Info, {Handler, State0}) ->
+ {Commands, State} = Handler:info(StreamID, Info, State0),
+ {Commands, {Handler, State}}.
+
+-spec terminate(streamid(), reason(), {module(), state()} | undefined) -> ok.
+terminate(_, _, undefined) ->
+ ok;
+terminate(StreamID, Reason, {Handler, State}) ->
+ _ = Handler:terminate(StreamID, Reason, State),
+ ok.
+
+-spec early_error(streamid(), reason(), partial_req(), Resp, cowboy:opts())
+ -> Resp when Resp::resp_command().
+early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
+ case maps:get(stream_handlers, Opts, [cowboy_stream_h]) of
+ [] ->
+ Resp;
+ [Handler|Tail] ->
+ %% This is the same behavior as in init/3.
+ Handler:early_error(StreamID, Reason,
+ PartialReq, Resp, Opts#{stream_handlers => Tail})
+ end.
+
+-spec make_error_log(init | data | info | terminate | early_error,
+ list(), error | exit | throw, any(), list())
+ -> {log, error, string(), list()}.
+make_error_log(init, [StreamID, Req, Opts], Class, Exception, Stacktrace) ->
+ {log, error,
+ "Unhandled exception ~p:~p in cowboy_stream:init(~p, Req, Opts)~n"
+ "Stacktrace: ~p~n"
+ "Req: ~p~n"
+ "Opts: ~p~n",
+ [Class, Exception, StreamID, Stacktrace, Req, Opts]};
+make_error_log(data, [StreamID, IsFin, Data, State], Class, Exception, Stacktrace) ->
+ {log, error,
+ "Unhandled exception ~p:~p in cowboy_stream:data(~p, ~p, Data, State)~n"
+ "Stacktrace: ~p~n"
+ "Data: ~p~n"
+ "State: ~p~n",
+ [Class, Exception, StreamID, IsFin, Stacktrace, Data, State]};
+make_error_log(info, [StreamID, Msg, State], Class, Exception, Stacktrace) ->
+ {log, error,
+ "Unhandled exception ~p:~p in cowboy_stream:info(~p, Msg, State)~n"
+ "Stacktrace: ~p~n"
+ "Msg: ~p~n"
+ "State: ~p~n",
+ [Class, Exception, StreamID, Stacktrace, Msg, State]};
+make_error_log(terminate, [StreamID, Reason, State], Class, Exception, Stacktrace) ->
+ {log, error,
+ "Unhandled exception ~p:~p in cowboy_stream:terminate(~p, Reason, State)~n"
+ "Stacktrace: ~p~n"
+ "Reason: ~p~n"
+ "State: ~p~n",
+ [Class, Exception, StreamID, Stacktrace, Reason, State]};
+make_error_log(early_error, [StreamID, Reason, PartialReq, Resp, Opts],
+ Class, Exception, Stacktrace) ->
+ {log, error,
+ "Unhandled exception ~p:~p in cowboy_stream:early_error(~p, Reason, PartialReq, Resp, Opts)~n"
+ "Stacktrace: ~p~n"
+ "Reason: ~p~n"
+ "PartialReq: ~p~n"
+ "Resp: ~p~n"
+ "Opts: ~p~n",
+ [Class, Exception, StreamID, Stacktrace, Reason, PartialReq, Resp, Opts]}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_stream_h.erl b/server/_build/default/lib/cowboy/src/cowboy_stream_h.erl
new file mode 100644
index 0000000..f516f3d
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_stream_h.erl
@@ -0,0 +1,324 @@
+%% Copyright (c) 2016-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_stream_h).
+-behavior(cowboy_stream).
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+
+-export([request_process/3]).
+-export([resume/5]).
+
+-record(state, {
+ next :: any(),
+ ref = undefined :: ranch:ref(),
+ pid = undefined :: pid(),
+ expect = undefined :: undefined | continue,
+ read_body_pid = undefined :: pid() | undefined,
+ read_body_ref = undefined :: reference() | undefined,
+ read_body_timer_ref = undefined :: reference() | undefined,
+ read_body_length = 0 :: non_neg_integer() | infinity | auto,
+ read_body_is_fin = nofin :: nofin | {fin, non_neg_integer()},
+ read_body_buffer = <<>> :: binary(),
+ body_length = 0 :: non_neg_integer(),
+ stream_body_pid = undefined :: pid() | undefined,
+ stream_body_status = normal :: normal | blocking | blocked
+}).
+
+-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
+ -> {[{spawn, pid(), timeout()}], #state{}}.
+init(StreamID, Req=#{ref := Ref}, Opts) ->
+ Env = maps:get(env, Opts, #{}),
+ Middlewares = maps:get(middlewares, Opts, [cowboy_router, cowboy_handler]),
+ Shutdown = maps:get(shutdown_timeout, Opts, 5000),
+ Pid = proc_lib:spawn_link(?MODULE, request_process, [Req, Env, Middlewares]),
+ Expect = expect(Req),
+ {Commands, Next} = cowboy_stream:init(StreamID, Req, Opts),
+ {[{spawn, Pid, Shutdown}|Commands],
+ #state{next=Next, ref=Ref, pid=Pid, expect=Expect}}.
+
+%% Ignore the expect header in HTTP/1.0.
+expect(#{version := 'HTTP/1.0'}) ->
+ undefined;
+expect(Req) ->
+ try cowboy_req:parse_header(<<"expect">>, Req) of
+ Expect ->
+ Expect
+ catch _:_ ->
+ undefined
+ end.
+
+%% If we receive data and stream is waiting for data:
+%% If we accumulated enough data or IsFin=fin, send it.
+%% If we are in auto mode, send it and update flow control.
+%% If not, buffer it.
+%% If not, buffer it.
+%%
+%% We always reset the expect field when we receive data,
+%% since the client started sending the request body before
+%% we could send a 100 continue response.
+
+-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+%% Stream isn't waiting for data.
+data(StreamID, IsFin, Data, State=#state{
+ read_body_ref=undefined, read_body_buffer=Buffer, body_length=BodyLen}) ->
+ do_data(StreamID, IsFin, Data, [], State#state{
+ expect=undefined,
+ read_body_is_fin=IsFin,
+ read_body_buffer= << Buffer/binary, Data/binary >>,
+ body_length=BodyLen + byte_size(Data)
+ });
+%% Stream is waiting for data using auto mode.
+%%
+%% There is no buffering done in auto mode.
+data(StreamID, IsFin, Data, State=#state{read_body_pid=Pid, read_body_ref=Ref,
+ read_body_length=auto, body_length=BodyLen}) ->
+ send_request_body(Pid, Ref, IsFin, BodyLen, Data),
+ do_data(StreamID, IsFin, Data, [{flow, byte_size(Data)}], State#state{
+ read_body_ref=undefined,
+ %% @todo This is wrong, it's missing byte_size(Data).
+ body_length=BodyLen
+ });
+%% Stream is waiting for data but we didn't receive enough to send yet.
+data(StreamID, IsFin=nofin, Data, State=#state{
+ read_body_length=ReadLen, read_body_buffer=Buffer, body_length=BodyLen})
+ when byte_size(Data) + byte_size(Buffer) < ReadLen ->
+ do_data(StreamID, IsFin, Data, [], State#state{
+ expect=undefined,
+ read_body_buffer= << Buffer/binary, Data/binary >>,
+ body_length=BodyLen + byte_size(Data)
+ });
+%% Stream is waiting for data and we received enough to send.
+data(StreamID, IsFin, Data, State=#state{read_body_pid=Pid, read_body_ref=Ref,
+ read_body_timer_ref=TRef, read_body_buffer=Buffer, body_length=BodyLen0}) ->
+ BodyLen = BodyLen0 + byte_size(Data),
+ ok = erlang:cancel_timer(TRef, [{async, true}, {info, false}]),
+ send_request_body(Pid, Ref, IsFin, BodyLen, <<Buffer/binary, Data/binary>>),
+ do_data(StreamID, IsFin, Data, [], State#state{
+ expect=undefined,
+ read_body_ref=undefined,
+ read_body_timer_ref=undefined,
+ read_body_buffer= <<>>,
+ body_length=BodyLen
+ }).
+
+do_data(StreamID, IsFin, Data, Commands1, State=#state{next=Next0}) ->
+ {Commands2, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),
+ {Commands1 ++ Commands2, State#state{next=Next}}.
+
+-spec info(cowboy_stream:streamid(), any(), State)
+ -> {cowboy_stream:commands(), State} when State::#state{}.
+info(StreamID, Info={'EXIT', Pid, normal}, State=#state{pid=Pid}) ->
+ do_info(StreamID, Info, [stop], State);
+info(StreamID, Info={'EXIT', Pid, {{request_error, Reason, _HumanReadable}, _}},
+ State=#state{pid=Pid}) ->
+ Status = case Reason of
+ timeout -> 408;
+ payload_too_large -> 413;
+ _ -> 400
+ end,
+ %% @todo Headers? Details in body? Log the crash? More stuff in debug only?
+ do_info(StreamID, Info, [
+ {error_response, Status, #{<<"content-length">> => <<"0">>}, <<>>},
+ stop
+ ], State);
+info(StreamID, Exit={'EXIT', Pid, {Reason, Stacktrace}}, State=#state{ref=Ref, pid=Pid}) ->
+ Commands0 = [{internal_error, Exit, 'Stream process crashed.'}],
+ Commands = case Reason of
+ normal -> Commands0;
+ shutdown -> Commands0;
+ {shutdown, _} -> Commands0;
+ _ -> [{log, error,
+ "Ranch listener ~p, connection process ~p, stream ~p "
+ "had its request process ~p exit with reason "
+ "~999999p and stacktrace ~999999p~n",
+ [Ref, self(), StreamID, Pid, Reason, Stacktrace]}
+ |Commands0]
+ end,
+ do_info(StreamID, Exit, [
+ {error_response, 500, #{<<"content-length">> => <<"0">>}, <<>>}
+ |Commands], State);
+%% Request body, auto mode, no body buffered.
+info(StreamID, Info={read_body, Pid, Ref, auto, infinity}, State=#state{read_body_buffer= <<>>}) ->
+ do_info(StreamID, Info, [], State#state{
+ read_body_pid=Pid,
+ read_body_ref=Ref,
+ read_body_length=auto
+ });
+%% Request body, auto mode, body buffered or complete.
+info(StreamID, Info={read_body, Pid, Ref, auto, infinity}, State=#state{
+ read_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen}) ->
+ send_request_body(Pid, Ref, IsFin, BodyLen, Buffer),
+ do_info(StreamID, Info, [{flow, byte_size(Buffer)}],
+ State#state{read_body_buffer= <<>>});
+%% Request body, body buffered large enough or complete.
+%%
+%% We do not send a 100 continue response if the client
+%% already started sending the body.
+info(StreamID, Info={read_body, Pid, Ref, Length, _}, State=#state{
+ read_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen})
+ when IsFin =:= fin; byte_size(Buffer) >= Length ->
+ send_request_body(Pid, Ref, IsFin, BodyLen, Buffer),
+ do_info(StreamID, Info, [], State#state{read_body_buffer= <<>>});
+%% Request body, not enough to send yet.
+info(StreamID, Info={read_body, Pid, Ref, Length, Period}, State=#state{expect=Expect}) ->
+ Commands = case Expect of
+ continue -> [{inform, 100, #{}}, {flow, Length}];
+ undefined -> [{flow, Length}]
+ end,
+ TRef = erlang:send_after(Period, self(), {{self(), StreamID}, {read_body_timeout, Ref}}),
+ do_info(StreamID, Info, Commands, State#state{
+ read_body_pid=Pid,
+ read_body_ref=Ref,
+ read_body_timer_ref=TRef,
+ read_body_length=Length
+ });
+%% Request body reading timeout; send what we got.
+info(StreamID, Info={read_body_timeout, Ref}, State=#state{read_body_pid=Pid, read_body_ref=Ref,
+ read_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen}) ->
+ send_request_body(Pid, Ref, IsFin, BodyLen, Buffer),
+ do_info(StreamID, Info, [], State#state{
+ read_body_ref=undefined,
+ read_body_timer_ref=undefined,
+ read_body_buffer= <<>>
+ });
+info(StreamID, Info={read_body_timeout, _}, State) ->
+ do_info(StreamID, Info, [], State);
+%% Response.
+%%
+%% We reset the expect field when a 100 continue response
+%% is sent or when any final response is sent.
+info(StreamID, Inform={inform, Status, _}, State0) ->
+ State = case cow_http:status_to_integer(Status) of
+ 100 -> State0#state{expect=undefined};
+ _ -> State0
+ end,
+ do_info(StreamID, Inform, [Inform], State);
+info(StreamID, Response={response, _, _, _}, State) ->
+ do_info(StreamID, Response, [Response], State#state{expect=undefined});
+info(StreamID, Headers={headers, _, _}, State) ->
+ do_info(StreamID, Headers, [Headers], State#state{expect=undefined});
+%% Sending data involves the data message, the stream_buffer_full alarm
+%% and the connection_buffer_full alarm. We stop sending acks when an alarm is on.
+%%
+%% We only apply backpressure when the message includes a pid. Otherwise
+%% it is a message from Cowboy, or the user circumventing the backpressure.
+%%
+%% We currently do not support sending data from multiple processes concurrently.
+info(StreamID, Data={data, _, _}, State) ->
+ do_info(StreamID, Data, [Data], State);
+info(StreamID, Data0={data, Pid, _, _}, State0=#state{stream_body_status=Status}) ->
+ State = case Status of
+ normal ->
+ Pid ! {data_ack, self()},
+ State0;
+ blocking ->
+ State0#state{stream_body_pid=Pid, stream_body_status=blocked};
+ blocked ->
+ State0
+ end,
+ Data = erlang:delete_element(2, Data0),
+ do_info(StreamID, Data, [Data], State);
+info(StreamID, Alarm={alarm, Name, on}, State0=#state{stream_body_status=Status})
+ when Name =:= connection_buffer_full; Name =:= stream_buffer_full ->
+ State = case Status of
+ normal -> State0#state{stream_body_status=blocking};
+ _ -> State0
+ end,
+ do_info(StreamID, Alarm, [], State);
+info(StreamID, Alarm={alarm, Name, off}, State=#state{stream_body_pid=Pid, stream_body_status=Status})
+ when Name =:= connection_buffer_full; Name =:= stream_buffer_full ->
+ _ = case Status of
+ normal -> ok;
+ blocking -> ok;
+ blocked -> Pid ! {data_ack, self()}
+ end,
+ do_info(StreamID, Alarm, [], State#state{stream_body_pid=undefined, stream_body_status=normal});
+info(StreamID, Trailers={trailers, _}, State) ->
+ do_info(StreamID, Trailers, [Trailers], State);
+info(StreamID, Push={push, _, _, _, _, _, _, _}, State) ->
+ do_info(StreamID, Push, [Push], State);
+info(StreamID, SwitchProtocol={switch_protocol, _, _, _}, State) ->
+ do_info(StreamID, SwitchProtocol, [SwitchProtocol], State#state{expect=undefined});
+%% Convert the set_options message to a command.
+info(StreamID, SetOptions={set_options, _}, State) ->
+ do_info(StreamID, SetOptions, [SetOptions], State);
+%% Unknown message, either stray or meant for a handler down the line.
+info(StreamID, Info, State) ->
+ do_info(StreamID, Info, [], State).
+
+do_info(StreamID, Info, Commands1, State0=#state{next=Next0}) ->
+ {Commands2, Next} = cowboy_stream:info(StreamID, Info, Next0),
+ {Commands1 ++ Commands2, State0#state{next=Next}}.
+
+-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> ok.
+terminate(StreamID, Reason, #state{next=Next}) ->
+ cowboy_stream:terminate(StreamID, Reason, Next).
+
+-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
+ cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
+ when Resp::cowboy_stream:resp_command().
+early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
+ cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).
+
+send_request_body(Pid, Ref, nofin, _, Data) ->
+ Pid ! {request_body, Ref, nofin, Data},
+ ok;
+send_request_body(Pid, Ref, fin, BodyLen, Data) ->
+ Pid ! {request_body, Ref, fin, BodyLen, Data},
+ ok.
+
+%% Request process.
+
+%% We add the stacktrace to exit exceptions here in order
+%% to simplify the debugging of errors. The proc_lib library
+%% already adds the stacktrace to other types of exceptions.
+-spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok.
+request_process(Req, Env, Middlewares) ->
+ try
+ execute(Req, Env, Middlewares)
+ catch
+ exit:Reason={shutdown, _}:Stacktrace ->
+ erlang:raise(exit, Reason, Stacktrace);
+ exit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown ->
+ erlang:raise(exit, {Reason, Stacktrace}, Stacktrace)
+ end.
+
+execute(_, _, []) ->
+ ok;
+execute(Req, Env, [Middleware|Tail]) ->
+ case Middleware:execute(Req, Env) of
+ {ok, Req2, Env2} ->
+ execute(Req2, Env2, Tail);
+ {suspend, Module, Function, Args} ->
+ proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]);
+ {stop, _Req2} ->
+ ok
+ end.
+
+-spec resume(cowboy_middleware:env(), [module()], module(), atom(), [any()]) -> ok.
+resume(Env, Tail, Module, Function, Args) ->
+ case apply(Module, Function, Args) of
+ {ok, Req2, Env2} ->
+ execute(Req2, Env2, Tail);
+ {suspend, Module2, Function2, Args2} ->
+ proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module2, Function2, Args2]);
+ {stop, _Req2} ->
+ ok
+ end.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_sub_protocol.erl b/server/_build/default/lib/cowboy/src/cowboy_sub_protocol.erl
new file mode 100644
index 0000000..6714289
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_sub_protocol.erl
@@ -0,0 +1,24 @@
+%% Copyright (c) 2013-2017, Loïc Hoguin <essen@ninenines.eu>
+%% Copyright (c) 2013, James Fish <james@fishcakez.com>
+%%
+%% 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(cowboy_sub_protocol).
+
+-callback upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {stop, Req}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+
+-callback upgrade(Req, Env, module(), any(), any())
+ -> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {stop, Req}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
diff --git a/server/_build/default/lib/cowboy/src/cowboy_sup.erl b/server/_build/default/lib/cowboy/src/cowboy_sup.erl
new file mode 100644
index 0000000..d3ac3b0
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_sup.erl
@@ -0,0 +1,30 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_sup).
+-behaviour(supervisor).
+
+-export([start_link/0]).
+-export([init/1]).
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+-spec init([])
+ -> {ok, {{supervisor:strategy(), 10, 10}, [supervisor:child_spec()]}}.
+init([]) ->
+ Procs = [{cowboy_clock, {cowboy_clock, start_link, []},
+ permanent, 5000, worker, [cowboy_clock]}],
+ {ok, {{one_for_one, 10, 10}, Procs}}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_tls.erl b/server/_build/default/lib/cowboy/src/cowboy_tls.erl
new file mode 100644
index 0000000..c049ecb
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_tls.erl
@@ -0,0 +1,56 @@
+%% Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_tls).
+-behavior(ranch_protocol).
+
+-export([start_link/3]).
+-export([start_link/4]).
+-export([connection_process/4]).
+
+%% Ranch 1.
+-spec start_link(ranch:ref(), ssl:sslsocket(), module(), cowboy:opts()) -> {ok, pid()}.
+start_link(Ref, _Socket, Transport, Opts) ->
+ start_link(Ref, Transport, Opts).
+
+%% Ranch 2.
+-spec start_link(ranch:ref(), module(), cowboy:opts()) -> {ok, pid()}.
+start_link(Ref, Transport, Opts) ->
+ Pid = proc_lib:spawn_link(?MODULE, connection_process,
+ [self(), Ref, Transport, Opts]),
+ {ok, Pid}.
+
+-spec connection_process(pid(), ranch:ref(), module(), cowboy:opts()) -> ok.
+connection_process(Parent, Ref, Transport, Opts) ->
+ ProxyInfo = case maps:get(proxy_header, Opts, false) of
+ true ->
+ {ok, ProxyInfo0} = ranch:recv_proxy_header(Ref, 1000),
+ ProxyInfo0;
+ false ->
+ undefined
+ end,
+ {ok, Socket} = ranch:handshake(Ref),
+ case ssl:negotiated_protocol(Socket) of
+ {ok, <<"h2">>} ->
+ init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, cowboy_http2);
+ _ -> %% http/1.1 or no protocol negotiated.
+ init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, cowboy_http)
+ end.
+
+init(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol) ->
+ _ = case maps:get(connection_type, Opts, supervisor) of
+ worker -> ok;
+ supervisor -> process_flag(trap_exit, true)
+ end,
+ Protocol:init(Parent, Ref, Socket, Transport, ProxyInfo, Opts).
diff --git a/server/_build/default/lib/cowboy/src/cowboy_tracer_h.erl b/server/_build/default/lib/cowboy/src/cowboy_tracer_h.erl
new file mode 100644
index 0000000..9a19ae1
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_tracer_h.erl
@@ -0,0 +1,192 @@
+%% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cowboy_tracer_h).
+-behavior(cowboy_stream).
+
+-export([init/3]).
+-export([data/4]).
+-export([info/3]).
+-export([terminate/3]).
+-export([early_error/5]).
+
+-export([set_trace_patterns/0]).
+
+-export([tracer_process/3]).
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-type match_predicate()
+ :: fun((cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts()) -> boolean()).
+
+-type tracer_match_specs() :: [match_predicate()
+ | {method, binary()}
+ | {host, binary()}
+ | {path, binary()}
+ | {path_start, binary()}
+ | {header, binary()}
+ | {header, binary(), binary()}
+ | {peer_ip, inet:ip_address()}
+].
+-export_type([tracer_match_specs/0]).
+
+-type tracer_callback() :: fun((init | terminate | tuple(), any()) -> any()).
+-export_type([tracer_callback/0]).
+
+-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
+ -> {cowboy_stream:commands(), any()}.
+init(StreamID, Req, Opts) ->
+ init_tracer(StreamID, Req, Opts),
+ cowboy_stream:init(StreamID, Req, Opts).
+
+-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
+ -> {cowboy_stream:commands(), State} when State::any().
+data(StreamID, IsFin, Data, Next) ->
+ cowboy_stream:data(StreamID, IsFin, Data, Next).
+
+-spec info(cowboy_stream:streamid(), any(), State)
+ -> {cowboy_stream:commands(), State} when State::any().
+info(StreamID, Info, Next) ->
+ cowboy_stream:info(StreamID, Info, Next).
+
+-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), any()) -> any().
+terminate(StreamID, Reason, Next) ->
+ cowboy_stream:terminate(StreamID, Reason, Next).
+
+-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
+ cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
+ when Resp::cowboy_stream:resp_command().
+early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
+ cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).
+
+%% API.
+
+%% These trace patterns are most likely not suitable for production.
+-spec set_trace_patterns() -> ok.
+set_trace_patterns() ->
+ erlang:trace_pattern({'_', '_', '_'}, [{'_', [], [{return_trace}]}], [local]),
+ erlang:trace_pattern(on_load, [{'_', [], [{return_trace}]}], [local]),
+ ok.
+
+%% Internal.
+
+init_tracer(StreamID, Req, Opts=#{tracer_match_specs := List, tracer_callback := _}) ->
+ case match(List, StreamID, Req, Opts) of
+ false ->
+ ok;
+ true ->
+ start_tracer(StreamID, Req, Opts)
+ end;
+%% When the options tracer_match_specs or tracer_callback
+%% are not provided we do not enable tracing.
+init_tracer(_, _, _) ->
+ ok.
+
+match([], _, _, _) ->
+ true;
+match([Predicate|Tail], StreamID, Req, Opts) when is_function(Predicate) ->
+ case Predicate(StreamID, Req, Opts) of
+ true -> match(Tail, StreamID, Req, Opts);
+ false -> false
+ end;
+match([{method, Value}|Tail], StreamID, Req=#{method := Value}, Opts) ->
+ match(Tail, StreamID, Req, Opts);
+match([{host, Value}|Tail], StreamID, Req=#{host := Value}, Opts) ->
+ match(Tail, StreamID, Req, Opts);
+match([{path, Value}|Tail], StreamID, Req=#{path := Value}, Opts) ->
+ match(Tail, StreamID, Req, Opts);
+match([{path_start, PathStart}|Tail], StreamID, Req=#{path := Path}, Opts) ->
+ Len = byte_size(PathStart),
+ case Path of
+ <<PathStart:Len/binary, _/bits>> -> match(Tail, StreamID, Req, Opts);
+ _ -> false
+ end;
+match([{header, Name}|Tail], StreamID, Req=#{headers := Headers}, Opts) ->
+ case Headers of
+ #{Name := _} -> match(Tail, StreamID, Req, Opts);
+ _ -> false
+ end;
+match([{header, Name, Value}|Tail], StreamID, Req=#{headers := Headers}, Opts) ->
+ case Headers of
+ #{Name := Value} -> match(Tail, StreamID, Req, Opts);
+ _ -> false
+ end;
+match([{peer_ip, IP}|Tail], StreamID, Req=#{peer := {IP, _}}, Opts) ->
+ match(Tail, StreamID, Req, Opts);
+match(_, _, _, _) ->
+ false.
+
+%% We only start the tracer if one wasn't started before.
+start_tracer(StreamID, Req, Opts) ->
+ case erlang:trace_info(self(), tracer) of
+ {tracer, []} ->
+ TracerPid = proc_lib:spawn_link(?MODULE, tracer_process, [StreamID, Req, Opts]),
+ %% The default flags are probably not suitable for production.
+ Flags = maps:get(tracer_flags, Opts, [
+ send, 'receive', call, return_to,
+ procs, ports, monotonic_timestamp,
+ %% The set_on_spawn flag is necessary to catch events
+ %% from request processes.
+ set_on_spawn
+ ]),
+ erlang:trace(self(), true, [{tracer, TracerPid}|Flags]),
+ ok;
+ _ ->
+ ok
+ end.
+
+%% Tracer process.
+
+-spec tracer_process(_, _, _) -> no_return().
+tracer_process(StreamID, Req=#{pid := Parent}, Opts=#{tracer_callback := Fun}) ->
+ %% This is necessary because otherwise the tracer could stop
+ %% before it has finished processing the events in its queue.
+ process_flag(trap_exit, true),
+ State = Fun(init, {StreamID, Req, Opts}),
+ tracer_loop(Parent, Opts, State).
+
+tracer_loop(Parent, Opts=#{tracer_callback := Fun}, State0) ->
+ receive
+ Msg when element(1, Msg) =:= trace; element(1, Msg) =:= trace_ts ->
+ State = Fun(Msg, State0),
+ tracer_loop(Parent, Opts, State);
+ {'EXIT', Parent, Reason} ->
+ tracer_terminate(Reason, Opts, State0);
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {Opts, State0});
+ Msg ->
+ cowboy:log(warning, "~p: Tracer process received stray message ~9999p~n",
+ [?MODULE, Msg], Opts),
+ tracer_loop(Parent, Opts, State0)
+ end.
+
+-spec tracer_terminate(_, _, _) -> no_return().
+tracer_terminate(Reason, #{tracer_callback := Fun}, State) ->
+ _ = Fun(terminate, State),
+ exit(Reason).
+
+%% System callbacks.
+
+-spec system_continue(pid(), _, {cowboy:opts(), any()}) -> no_return().
+system_continue(Parent, _, {Opts, State}) ->
+ tracer_loop(Parent, Opts, State).
+
+-spec system_terminate(any(), _, _, _) -> no_return().
+system_terminate(Reason, _, _, {Opts, State}) ->
+ tracer_terminate(Reason, Opts, State).
+
+-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::any().
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
diff --git a/server/_build/default/lib/cowboy/src/cowboy_websocket.erl b/server/_build/default/lib/cowboy/src/cowboy_websocket.erl
new file mode 100644
index 0000000..e7d8f31
--- /dev/null
+++ b/server/_build/default/lib/cowboy/src/cowboy_websocket.erl
@@ -0,0 +1,707 @@
+%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% Cowboy supports versions 7 through 17 of the Websocket drafts.
+%% It also supports RFC6455, the proposed standard for Websocket.
+-module(cowboy_websocket).
+-behaviour(cowboy_sub_protocol).
+
+-export([is_upgrade_request/1]).
+-export([upgrade/4]).
+-export([upgrade/5]).
+-export([takeover/7]).
+-export([loop/3]).
+
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-type commands() :: [cow_ws:frame()
+ | {active, boolean()}
+ | {deflate, boolean()}
+ | {set_options, map()}
+ | {shutdown_reason, any()}
+].
+-export_type([commands/0]).
+
+-type call_result(State) :: {commands(), State} | {commands(), State, hibernate}.
+
+-type deprecated_call_result(State) :: {ok, State}
+ | {ok, State, hibernate}
+ | {reply, cow_ws:frame() | [cow_ws:frame()], State}
+ | {reply, cow_ws:frame() | [cow_ws:frame()], State, hibernate}
+ | {stop, State}.
+
+-type terminate_reason() :: normal | stop | timeout
+ | remote | {remote, cow_ws:close_code(), binary()}
+ | {error, badencoding | badframe | closed | atom()}
+ | {crash, error | exit | throw, any()}.
+
+-callback init(Req, any())
+ -> {ok | module(), Req, any()}
+ | {module(), Req, any(), any()}
+ when Req::cowboy_req:req().
+
+-callback websocket_init(State)
+ -> call_result(State) | deprecated_call_result(State) when State::any().
+-optional_callbacks([websocket_init/1]).
+
+-callback websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State)
+ -> call_result(State) | deprecated_call_result(State) when State::any().
+-callback websocket_info(any(), State)
+ -> call_result(State) | deprecated_call_result(State) when State::any().
+
+-callback terminate(any(), cowboy_req:req(), any()) -> ok.
+-optional_callbacks([terminate/3]).
+
+-type opts() :: #{
+ active_n => pos_integer(),
+ compress => boolean(),
+ deflate_opts => cow_ws:deflate_opts(),
+ idle_timeout => timeout(),
+ max_frame_size => non_neg_integer() | infinity,
+ req_filter => fun((cowboy_req:req()) -> map()),
+ validate_utf8 => boolean()
+}.
+-export_type([opts/0]).
+
+-record(state, {
+ parent :: undefined | pid(),
+ ref :: ranch:ref(),
+ socket = undefined :: inet:socket() | {pid(), cowboy_stream:streamid()} | undefined,
+ transport = undefined :: module() | undefined,
+ opts = #{} :: opts(),
+ active = true :: boolean(),
+ handler :: module(),
+ key = undefined :: undefined | binary(),
+ timeout_ref = undefined :: undefined | reference(),
+ messages = undefined :: undefined | {atom(), atom(), atom()}
+ | {atom(), atom(), atom(), atom()},
+ hibernate = false :: boolean(),
+ frag_state = undefined :: cow_ws:frag_state(),
+ frag_buffer = <<>> :: binary(),
+ utf8_state :: cow_ws:utf8_state(),
+ deflate = true :: boolean(),
+ extensions = #{} :: map(),
+ req = #{} :: map(),
+ shutdown_reason = normal :: any()
+}).
+
+%% Because the HTTP/1.1 and HTTP/2 handshakes are so different,
+%% this function is necessary to figure out whether a request
+%% is trying to upgrade to the Websocket protocol.
+
+-spec is_upgrade_request(cowboy_req:req()) -> boolean().
+is_upgrade_request(#{version := 'HTTP/2', method := <<"CONNECT">>, protocol := Protocol}) ->
+ <<"websocket">> =:= cowboy_bstr:to_lower(Protocol);
+is_upgrade_request(Req=#{version := 'HTTP/1.1', method := <<"GET">>}) ->
+ ConnTokens = cowboy_req:parse_header(<<"connection">>, Req, []),
+ case lists:member(<<"upgrade">>, ConnTokens) of
+ false ->
+ false;
+ true ->
+ UpgradeTokens = cowboy_req:parse_header(<<"upgrade">>, Req),
+ lists:member(<<"websocket">>, UpgradeTokens)
+ end;
+is_upgrade_request(_) ->
+ false.
+
+%% Stream process.
+
+-spec upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req, Env, Handler, HandlerState) ->
+ upgrade(Req, Env, Handler, HandlerState, #{}).
+
+-spec upgrade(Req, Env, module(), any(), opts())
+ -> {ok, Req, Env}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+%% @todo Immediately crash if a response has already been sent.
+upgrade(Req0=#{version := Version}, Env, Handler, HandlerState, Opts) ->
+ FilteredReq = case maps:get(req_filter, Opts, undefined) of
+ undefined -> maps:with([method, version, scheme, host, port, path, qs, peer], Req0);
+ FilterFun -> FilterFun(Req0)
+ end,
+ Utf8State = case maps:get(validate_utf8, Opts, true) of
+ true -> 0;
+ false -> undefined
+ end,
+ State0 = #state{opts=Opts, handler=Handler, utf8_state=Utf8State, req=FilteredReq},
+ try websocket_upgrade(State0, Req0) of
+ {ok, State, Req} ->
+ websocket_handshake(State, Req, HandlerState, Env);
+ %% The status code 426 is specific to HTTP/1.1 connections.
+ {error, upgrade_required} when Version =:= 'HTTP/1.1' ->
+ {ok, cowboy_req:reply(426, #{
+ <<"connection">> => <<"upgrade">>,
+ <<"upgrade">> => <<"websocket">>
+ }, Req0), Env};
+ %% Use a generic 400 error for HTTP/2.
+ {error, upgrade_required} ->
+ {ok, cowboy_req:reply(400, Req0), Env}
+ catch _:_ ->
+ %% @todo Probably log something here?
+ %% @todo Test that we can have 2 /ws 400 status code in a row on the same connection.
+ %% @todo Does this even work?
+ {ok, cowboy_req:reply(400, Req0), Env}
+ end.
+
+websocket_upgrade(State, Req=#{version := Version}) ->
+ case is_upgrade_request(Req) of
+ false ->
+ {error, upgrade_required};
+ true when Version =:= 'HTTP/1.1' ->
+ Key = cowboy_req:header(<<"sec-websocket-key">>, Req),
+ false = Key =:= undefined,
+ websocket_version(State#state{key=Key}, Req);
+ true ->
+ websocket_version(State, Req)
+ end.
+
+websocket_version(State, Req) ->
+ WsVersion = cowboy_req:parse_header(<<"sec-websocket-version">>, Req),
+ case WsVersion of
+ 7 -> ok;
+ 8 -> ok;
+ 13 -> ok
+ end,
+ websocket_extensions(State, Req#{websocket_version => WsVersion}).
+
+websocket_extensions(State=#state{opts=Opts}, Req) ->
+ %% @todo We want different options for this. For example
+ %% * compress everything auto
+ %% * compress only text auto
+ %% * compress only binary auto
+ %% * compress nothing auto (but still enabled it)
+ %% * disable compression
+ Compress = maps:get(compress, Opts, false),
+ case {Compress, cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req)} of
+ {true, Extensions} when Extensions =/= undefined ->
+ websocket_extensions(State, Req, Extensions, []);
+ _ ->
+ {ok, State, Req}
+ end.
+
+websocket_extensions(State, Req, [], []) ->
+ {ok, State, Req};
+websocket_extensions(State, Req, [], [<<", ">>|RespHeader]) ->
+ {ok, State, cowboy_req:set_resp_header(<<"sec-websocket-extensions">>, lists:reverse(RespHeader), Req)};
+%% For HTTP/2 we ARE on the controlling process and do NOT want to update the owner.
+websocket_extensions(State=#state{opts=Opts, extensions=Extensions},
+ Req=#{pid := Pid, version := Version},
+ [{<<"permessage-deflate">>, Params}|Tail], RespHeader) ->
+ DeflateOpts0 = maps:get(deflate_opts, Opts, #{}),
+ DeflateOpts = case Version of
+ 'HTTP/1.1' -> DeflateOpts0#{owner => Pid};
+ _ -> DeflateOpts0
+ end,
+ try cow_ws:negotiate_permessage_deflate(Params, Extensions, DeflateOpts) of
+ {ok, RespExt, Extensions2} ->
+ websocket_extensions(State#state{extensions=Extensions2},
+ Req, Tail, [<<", ">>, RespExt|RespHeader]);
+ ignore ->
+ websocket_extensions(State, Req, Tail, RespHeader)
+ catch exit:{error, incompatible_zlib_version, _} ->
+ websocket_extensions(State, Req, Tail, RespHeader)
+ end;
+websocket_extensions(State=#state{opts=Opts, extensions=Extensions},
+ Req=#{pid := Pid, version := Version},
+ [{<<"x-webkit-deflate-frame">>, Params}|Tail], RespHeader) ->
+ DeflateOpts0 = maps:get(deflate_opts, Opts, #{}),
+ DeflateOpts = case Version of
+ 'HTTP/1.1' -> DeflateOpts0#{owner => Pid};
+ _ -> DeflateOpts0
+ end,
+ try cow_ws:negotiate_x_webkit_deflate_frame(Params, Extensions, DeflateOpts) of
+ {ok, RespExt, Extensions2} ->
+ websocket_extensions(State#state{extensions=Extensions2},
+ Req, Tail, [<<", ">>, RespExt|RespHeader]);
+ ignore ->
+ websocket_extensions(State, Req, Tail, RespHeader)
+ catch exit:{error, incompatible_zlib_version, _} ->
+ websocket_extensions(State, Req, Tail, RespHeader)
+ end;
+websocket_extensions(State, Req, [_|Tail], RespHeader) ->
+ websocket_extensions(State, Req, Tail, RespHeader).
+
+-spec websocket_handshake(#state{}, Req, any(), Env)
+ -> {ok, Req, Env}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+websocket_handshake(State=#state{key=Key},
+ Req=#{version := 'HTTP/1.1', pid := Pid, streamid := StreamID},
+ HandlerState, Env) ->
+ Challenge = base64:encode(crypto:hash(sha,
+ << Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)),
+ %% @todo We don't want date and server headers.
+ Headers = cowboy_req:response_headers(#{
+ <<"connection">> => <<"Upgrade">>,
+ <<"upgrade">> => <<"websocket">>,
+ <<"sec-websocket-accept">> => Challenge
+ }, Req),
+ Pid ! {{Pid, StreamID}, {switch_protocol, Headers, ?MODULE, {State, HandlerState}}},
+ {ok, Req, Env};
+%% For HTTP/2 we do not let the process die, we instead keep it
+%% for the Websocket stream. This is because in HTTP/2 we only
+%% have a stream, it doesn't take over the whole connection.
+websocket_handshake(State, Req=#{ref := Ref, pid := Pid, streamid := StreamID},
+ HandlerState, _Env) ->
+ %% @todo We don't want date and server headers.
+ Headers = cowboy_req:response_headers(#{}, Req),
+ Pid ! {{Pid, StreamID}, {switch_protocol, Headers, ?MODULE, {State, HandlerState}}},
+ takeover(Pid, Ref, {Pid, StreamID}, undefined, undefined, <<>>,
+ {State, HandlerState}).
+
+%% Connection process.
+
+-record(ps_header, {
+ buffer = <<>> :: binary()
+}).
+
+-record(ps_payload, {
+ type :: cow_ws:frame_type(),
+ len :: non_neg_integer(),
+ mask_key :: cow_ws:mask_key(),
+ rsv :: cow_ws:rsv(),
+ close_code = undefined :: undefined | cow_ws:close_code(),
+ unmasked = <<>> :: binary(),
+ unmasked_len = 0 :: non_neg_integer(),
+ buffer = <<>> :: binary()
+}).
+
+-type parse_state() :: #ps_header{} | #ps_payload{}.
+
+-spec takeover(pid(), ranch:ref(), inet:socket() | {pid(), cowboy_stream:streamid()},
+ module() | undefined, any(), binary(),
+ {#state{}, any()}) -> no_return().
+takeover(Parent, Ref, Socket, Transport, _Opts, Buffer,
+ {State0=#state{handler=Handler}, HandlerState}) ->
+ %% @todo We should have an option to disable this behavior.
+ ranch:remove_connection(Ref),
+ Messages = case Transport of
+ undefined -> undefined;
+ _ -> Transport:messages()
+ end,
+ State = loop_timeout(State0#state{parent=Parent,
+ ref=Ref, socket=Socket, transport=Transport,
+ key=undefined, messages=Messages}),
+ %% We call parse_header/3 immediately because there might be
+ %% some data in the buffer that was sent along with the handshake.
+ %% While it is not allowed by the protocol to send frames immediately,
+ %% we still want to process that data if any.
+ case erlang:function_exported(Handler, websocket_init, 1) of
+ true -> handler_call(State, HandlerState, #ps_header{buffer=Buffer},
+ websocket_init, undefined, fun after_init/3);
+ false -> after_init(State, HandlerState, #ps_header{buffer=Buffer})
+ end.
+
+after_init(State=#state{active=true}, HandlerState, ParseState) ->
+ %% Enable active,N for HTTP/1.1, and auto read_body for HTTP/2.
+ %% We must do this only after calling websocket_init/1 (if any)
+ %% to give the handler a chance to disable active mode immediately.
+ setopts_active(State),
+ maybe_read_body(State),
+ parse_header(State, HandlerState, ParseState);
+after_init(State, HandlerState, ParseState) ->
+ parse_header(State, HandlerState, ParseState).
+
+%% We have two ways of reading the body for Websocket. For HTTP/1.1
+%% we have full control of the socket and can therefore use active,N.
+%% For HTTP/2 we are just a stream, and are instead using read_body
+%% (automatic mode). Technically HTTP/2 will only go passive after
+%% receiving the next data message, while HTTP/1.1 goes passive
+%% immediately but there might still be data to be processed in
+%% the message queue.
+
+setopts_active(#state{transport=undefined}) ->
+ ok;
+setopts_active(#state{socket=Socket, transport=Transport, opts=Opts}) ->
+ N = maps:get(active_n, Opts, 100),
+ Transport:setopts(Socket, [{active, N}]).
+
+maybe_read_body(#state{socket=Stream={Pid, _}, transport=undefined, active=true}) ->
+ %% @todo Keep Ref around.
+ ReadBodyRef = make_ref(),
+ Pid ! {Stream, {read_body, self(), ReadBodyRef, auto, infinity}},
+ ok;
+maybe_read_body(_) ->
+ ok.
+
+active(State) ->
+ setopts_active(State),
+ maybe_read_body(State),
+ State#state{active=true}.
+
+passive(State=#state{transport=undefined}) ->
+ %% Unfortunately we cannot currently cancel read_body.
+ %% But that's OK, we will just stop reading the body
+ %% after the next message.
+ State#state{active=false};
+passive(State=#state{socket=Socket, transport=Transport, messages=Messages}) ->
+ Transport:setopts(Socket, [{active, false}]),
+ flush_passive(Socket, Messages),
+ State#state{active=false}.
+
+flush_passive(Socket, Messages) ->
+ receive
+ {Passive, Socket} when Passive =:= element(4, Messages);
+ %% Hardcoded for compatibility with Ranch 1.x.
+ Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ flush_passive(Socket, Messages)
+ after 0 ->
+ ok
+ end.
+
+before_loop(State=#state{hibernate=true}, HandlerState, ParseState) ->
+ proc_lib:hibernate(?MODULE, loop,
+ [State#state{hibernate=false}, HandlerState, ParseState]);
+before_loop(State, HandlerState, ParseState) ->
+ loop(State, HandlerState, ParseState).
+
+-spec loop_timeout(#state{}) -> #state{}.
+loop_timeout(State=#state{opts=Opts, timeout_ref=PrevRef}) ->
+ _ = case PrevRef of
+ undefined -> ignore;
+ PrevRef -> erlang:cancel_timer(PrevRef)
+ end,
+ case maps:get(idle_timeout, Opts, 60000) of
+ infinity ->
+ State#state{timeout_ref=undefined};
+ Timeout ->
+ TRef = erlang:start_timer(Timeout, self(), ?MODULE),
+ State#state{timeout_ref=TRef}
+ end.
+
+-spec loop(#state{}, any(), parse_state()) -> no_return().
+loop(State=#state{parent=Parent, socket=Socket, messages=Messages,
+ timeout_ref=TRef}, HandlerState, ParseState) ->
+ receive
+ %% Socket messages. (HTTP/1.1)
+ {OK, Socket, Data} when OK =:= element(1, Messages) ->
+ State2 = loop_timeout(State),
+ parse(State2, HandlerState, ParseState, Data);
+ {Closed, Socket} when Closed =:= element(2, Messages) ->
+ terminate(State, HandlerState, {error, closed});
+ {Error, Socket, Reason} when Error =:= element(3, Messages) ->
+ terminate(State, HandlerState, {error, Reason});
+ {Passive, Socket} when Passive =:= element(4, Messages);
+ %% Hardcoded for compatibility with Ranch 1.x.
+ Passive =:= tcp_passive; Passive =:= ssl_passive ->
+ setopts_active(State),
+ loop(State, HandlerState, ParseState);
+ %% Body reading messages. (HTTP/2)
+ {request_body, _Ref, nofin, Data} ->
+ maybe_read_body(State),
+ State2 = loop_timeout(State),
+ parse(State2, HandlerState, ParseState, Data);
+ %% @todo We need to handle this case as if it was an {error, closed}
+ %% but not before we finish processing frames. We probably should have
+ %% a check in before_loop to let us stop looping if a flag is set.
+ {request_body, _Ref, fin, _, Data} ->
+ maybe_read_body(State),
+ State2 = loop_timeout(State),
+ parse(State2, HandlerState, ParseState, Data);
+ %% Timeouts.
+ {timeout, TRef, ?MODULE} ->
+ websocket_close(State, HandlerState, timeout);
+ {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
+ before_loop(State, HandlerState, ParseState);
+ %% System messages.
+ {'EXIT', Parent, Reason} ->
+ %% @todo We should exit gracefully.
+ exit(Reason);
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
+ {State, HandlerState, ParseState});
+ %% Calls from supervisor module.
+ {'$gen_call', From, Call} ->
+ cowboy_children:handle_supervisor_call(Call, From, [], ?MODULE),
+ before_loop(State, HandlerState, ParseState);
+ Message ->
+ handler_call(State, HandlerState, ParseState,
+ websocket_info, Message, fun before_loop/3)
+ end.
+
+parse(State, HandlerState, PS=#ps_header{buffer=Buffer}, Data) ->
+ parse_header(State, HandlerState, PS#ps_header{
+ buffer= <<Buffer/binary, Data/binary>>});
+parse(State, HandlerState, PS=#ps_payload{buffer=Buffer}, Data) ->
+ parse_payload(State, HandlerState, PS#ps_payload{buffer= <<>>},
+ <<Buffer/binary, Data/binary>>).
+
+parse_header(State=#state{opts=Opts, frag_state=FragState, extensions=Extensions},
+ HandlerState, ParseState=#ps_header{buffer=Data}) ->
+ MaxFrameSize = maps:get(max_frame_size, Opts, infinity),
+ case cow_ws:parse_header(Data, Extensions, FragState) of
+ %% All frames sent from the client to the server are masked.
+ {_, _, _, _, undefined, _} ->
+ websocket_close(State, HandlerState, {error, badframe});
+ {_, _, _, Len, _, _} when Len > MaxFrameSize ->
+ websocket_close(State, HandlerState, {error, badsize});
+ {Type, FragState2, Rsv, Len, MaskKey, Rest} ->
+ parse_payload(State#state{frag_state=FragState2}, HandlerState,
+ #ps_payload{type=Type, len=Len, mask_key=MaskKey, rsv=Rsv}, Rest);
+ more ->
+ before_loop(State, HandlerState, ParseState);
+ error ->
+ websocket_close(State, HandlerState, {error, badframe})
+ end.
+
+parse_payload(State=#state{frag_state=FragState, utf8_state=Incomplete, extensions=Extensions},
+ HandlerState, ParseState=#ps_payload{
+ type=Type, len=Len, mask_key=MaskKey, rsv=Rsv,
+ unmasked=Unmasked, unmasked_len=UnmaskedLen}, Data) ->
+ case cow_ws:parse_payload(Data, MaskKey, Incomplete, UnmaskedLen,
+ Type, Len, FragState, Extensions, Rsv) of
+ {ok, CloseCode, Payload, Utf8State, Rest} ->
+ dispatch_frame(State#state{utf8_state=Utf8State}, HandlerState,
+ ParseState#ps_payload{unmasked= <<Unmasked/binary, Payload/binary>>,
+ close_code=CloseCode}, Rest);
+ {ok, Payload, Utf8State, Rest} ->
+ dispatch_frame(State#state{utf8_state=Utf8State}, HandlerState,
+ ParseState#ps_payload{unmasked= <<Unmasked/binary, Payload/binary>>},
+ Rest);
+ {more, CloseCode, Payload, Utf8State} ->
+ before_loop(State#state{utf8_state=Utf8State}, HandlerState,
+ ParseState#ps_payload{len=Len - byte_size(Data), close_code=CloseCode,
+ unmasked= <<Unmasked/binary, Payload/binary>>,
+ unmasked_len=UnmaskedLen + byte_size(Data)});
+ {more, Payload, Utf8State} ->
+ before_loop(State#state{utf8_state=Utf8State}, HandlerState,
+ ParseState#ps_payload{len=Len - byte_size(Data),
+ unmasked= <<Unmasked/binary, Payload/binary>>,
+ unmasked_len=UnmaskedLen + byte_size(Data)});
+ Error = {error, _Reason} ->
+ websocket_close(State, HandlerState, Error)
+ end.
+
+dispatch_frame(State=#state{opts=Opts, frag_state=FragState, frag_buffer=SoFar}, HandlerState,
+ #ps_payload{type=Type0, unmasked=Payload0, close_code=CloseCode0}, RemainingData) ->
+ MaxFrameSize = maps:get(max_frame_size, Opts, infinity),
+ case cow_ws:make_frame(Type0, Payload0, CloseCode0, FragState) of
+ %% @todo Allow receiving fragments.
+ {fragment, _, _, Payload} when byte_size(Payload) + byte_size(SoFar) > MaxFrameSize ->
+ websocket_close(State, HandlerState, {error, badsize});
+ {fragment, nofin, _, Payload} ->
+ parse_header(State#state{frag_buffer= << SoFar/binary, Payload/binary >>},
+ HandlerState, #ps_header{buffer=RemainingData});
+ {fragment, fin, Type, Payload} ->
+ handler_call(State#state{frag_state=undefined, frag_buffer= <<>>}, HandlerState,
+ #ps_header{buffer=RemainingData},
+ websocket_handle, {Type, << SoFar/binary, Payload/binary >>},
+ fun parse_header/3);
+ close ->
+ websocket_close(State, HandlerState, remote);
+ {close, CloseCode, Payload} ->
+ websocket_close(State, HandlerState, {remote, CloseCode, Payload});
+ Frame = ping ->
+ transport_send(State, nofin, frame(pong, State)),
+ handler_call(State, HandlerState,
+ #ps_header{buffer=RemainingData},
+ websocket_handle, Frame, fun parse_header/3);
+ Frame = {ping, Payload} ->
+ transport_send(State, nofin, frame({pong, Payload}, State)),
+ handler_call(State, HandlerState,
+ #ps_header{buffer=RemainingData},
+ websocket_handle, Frame, fun parse_header/3);
+ Frame ->
+ handler_call(State, HandlerState,
+ #ps_header{buffer=RemainingData},
+ websocket_handle, Frame, fun parse_header/3)
+ end.
+
+handler_call(State=#state{handler=Handler}, HandlerState,
+ ParseState, Callback, Message, NextState) ->
+ try case Callback of
+ websocket_init -> Handler:websocket_init(HandlerState);
+ _ -> Handler:Callback(Message, HandlerState)
+ end of
+ {Commands, HandlerState2} when is_list(Commands) ->
+ handler_call_result(State,
+ HandlerState2, ParseState, NextState, Commands);
+ {Commands, HandlerState2, hibernate} when is_list(Commands) ->
+ handler_call_result(State#state{hibernate=true},
+ HandlerState2, ParseState, NextState, Commands);
+ %% The following call results are deprecated.
+ {ok, HandlerState2} ->
+ NextState(State, HandlerState2, ParseState);
+ {ok, HandlerState2, hibernate} ->
+ NextState(State#state{hibernate=true}, HandlerState2, ParseState);
+ {reply, Payload, HandlerState2} ->
+ case websocket_send(Payload, State) of
+ ok ->
+ NextState(State, HandlerState2, ParseState);
+ stop ->
+ terminate(State, HandlerState2, stop);
+ Error = {error, _} ->
+ terminate(State, HandlerState2, Error)
+ end;
+ {reply, Payload, HandlerState2, hibernate} ->
+ case websocket_send(Payload, State) of
+ ok ->
+ NextState(State#state{hibernate=true},
+ HandlerState2, ParseState);
+ stop ->
+ terminate(State, HandlerState2, stop);
+ Error = {error, _} ->
+ terminate(State, HandlerState2, Error)
+ end;
+ {stop, HandlerState2} ->
+ websocket_close(State, HandlerState2, stop)
+ catch Class:Reason:Stacktrace ->
+ websocket_send_close(State, {crash, Class, Reason}),
+ handler_terminate(State, HandlerState, {crash, Class, Reason}),
+ erlang:raise(Class, Reason, Stacktrace)
+ end.
+
+-spec handler_call_result(#state{}, any(), parse_state(), fun(), commands()) -> no_return().
+handler_call_result(State0, HandlerState, ParseState, NextState, Commands) ->
+ case commands(Commands, State0, []) of
+ {ok, State} ->
+ NextState(State, HandlerState, ParseState);
+ {stop, State} ->
+ terminate(State, HandlerState, stop);
+ {Error = {error, _}, State} ->
+ terminate(State, HandlerState, Error)
+ end.
+
+commands([], State, []) ->
+ {ok, State};
+commands([], State, Data) ->
+ Result = transport_send(State, nofin, lists:reverse(Data)),
+ {Result, State};
+commands([{active, Active}|Tail], State0=#state{active=Active0}, Data) when is_boolean(Active) ->
+ State = if
+ Active, not Active0 ->
+ active(State0);
+ Active0, not Active ->
+ passive(State0);
+ true ->
+ State0
+ end,
+ commands(Tail, State#state{active=Active}, Data);
+commands([{deflate, Deflate}|Tail], State, Data) when is_boolean(Deflate) ->
+ commands(Tail, State#state{deflate=Deflate}, Data);
+commands([{set_options, SetOpts}|Tail], State0=#state{opts=Opts}, Data) ->
+ State = case SetOpts of
+ #{idle_timeout := IdleTimeout} ->
+ loop_timeout(State0#state{opts=Opts#{idle_timeout => IdleTimeout}});
+ _ ->
+ State0
+ end,
+ commands(Tail, State, Data);
+commands([{shutdown_reason, ShutdownReason}|Tail], State, Data) ->
+ commands(Tail, State#state{shutdown_reason=ShutdownReason}, Data);
+commands([Frame|Tail], State, Data0) ->
+ Data = [frame(Frame, State)|Data0],
+ case is_close_frame(Frame) of
+ true ->
+ _ = transport_send(State, fin, lists:reverse(Data)),
+ {stop, State};
+ false ->
+ commands(Tail, State, Data)
+ end.
+
+transport_send(#state{socket=Stream={Pid, _}, transport=undefined}, IsFin, Data) ->
+ Pid ! {Stream, {data, IsFin, Data}},
+ ok;
+transport_send(#state{socket=Socket, transport=Transport}, _, Data) ->
+ Transport:send(Socket, Data).
+
+-spec websocket_send(cow_ws:frame(), #state{}) -> ok | stop | {error, atom()}.
+websocket_send(Frames, State) when is_list(Frames) ->
+ websocket_send_many(Frames, State, []);
+websocket_send(Frame, State) ->
+ Data = frame(Frame, State),
+ case is_close_frame(Frame) of
+ true ->
+ _ = transport_send(State, fin, Data),
+ stop;
+ false ->
+ transport_send(State, nofin, Data)
+ end.
+
+websocket_send_many([], State, Acc) ->
+ transport_send(State, nofin, lists:reverse(Acc));
+websocket_send_many([Frame|Tail], State, Acc0) ->
+ Acc = [frame(Frame, State)|Acc0],
+ case is_close_frame(Frame) of
+ true ->
+ _ = transport_send(State, fin, lists:reverse(Acc)),
+ stop;
+ false ->
+ websocket_send_many(Tail, State, Acc)
+ end.
+
+is_close_frame(close) -> true;
+is_close_frame({close, _}) -> true;
+is_close_frame({close, _, _}) -> true;
+is_close_frame(_) -> false.
+
+-spec websocket_close(#state{}, any(), terminate_reason()) -> no_return().
+websocket_close(State, HandlerState, Reason) ->
+ websocket_send_close(State, Reason),
+ terminate(State, HandlerState, Reason).
+
+websocket_send_close(State, Reason) ->
+ _ = case Reason of
+ Normal when Normal =:= stop; Normal =:= timeout ->
+ transport_send(State, fin, frame({close, 1000, <<>>}, State));
+ {error, badframe} ->
+ transport_send(State, fin, frame({close, 1002, <<>>}, State));
+ {error, badencoding} ->
+ transport_send(State, fin, frame({close, 1007, <<>>}, State));
+ {error, badsize} ->
+ transport_send(State, fin, frame({close, 1009, <<>>}, State));
+ {crash, _, _} ->
+ transport_send(State, fin, frame({close, 1011, <<>>}, State));
+ remote ->
+ transport_send(State, fin, frame(close, State));
+ {remote, Code, _} ->
+ transport_send(State, fin, frame({close, Code, <<>>}, State))
+ end,
+ ok.
+
+%% Don't compress frames while deflate is disabled.
+frame(Frame, #state{deflate=false, extensions=Extensions}) ->
+ cow_ws:frame(Frame, Extensions#{deflate => false});
+frame(Frame, #state{extensions=Extensions}) ->
+ cow_ws:frame(Frame, Extensions).
+
+-spec terminate(#state{}, any(), terminate_reason()) -> no_return().
+terminate(State=#state{shutdown_reason=Shutdown}, HandlerState, Reason) ->
+ handler_terminate(State, HandlerState, Reason),
+ case Shutdown of
+ normal -> exit(normal);
+ _ -> exit({shutdown, Shutdown})
+ end.
+
+handler_terminate(#state{handler=Handler, req=Req}, HandlerState, Reason) ->
+ cowboy_handler:terminate(Reason, Req, HandlerState, Handler).
+
+%% System callbacks.
+
+-spec system_continue(_, _, {#state{}, any(), parse_state()}) -> no_return().
+system_continue(_, _, {State, HandlerState, ParseState}) ->
+ loop(State, HandlerState, ParseState).
+
+-spec system_terminate(any(), _, _, {#state{}, any(), parse_state()}) -> no_return().
+system_terminate(Reason, _, _, {State, HandlerState, _}) ->
+ %% @todo We should exit gracefully, if possible.
+ terminate(State, HandlerState, Reason).
+
+-spec system_code_change(Misc, _, _, _)
+ -> {ok, Misc} when Misc::{#state{}, any(), parse_state()}.
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.