aboutsummaryrefslogtreecommitdiff
path: root/server/_build/default/lib
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
i vibe coded itHEADmaster
Diffstat (limited to 'server/_build/default/lib')
-rw-r--r--server/_build/default/lib/.rebar3/rebar_compiler_erl/source_apps.dagbin0 -> 8316 bytes
-rw-r--r--server/_build/default/lib/.rebar3/rebar_compiler_erl/source_project_apps.dagbin0 -> 1885 bytes
-rw-r--r--server/_build/default/lib/base64url/LICENSE.txt18
-rw-r--r--server/_build/default/lib/base64url/README.md58
-rw-r--r--server/_build/default/lib/base64url/ebin/base64url.app11
-rw-r--r--server/_build/default/lib/base64url/ebin/base64url.beambin0 -> 3664 bytes
-rw-r--r--server/_build/default/lib/base64url/hex_metadata.config12
-rw-r--r--server/_build/default/lib/base64url/rebar.config11
-rw-r--r--server/_build/default/lib/base64url/src/base64url.app.src14
-rw-r--r--server/_build/default/lib/base64url/src/base64url.erl98
-rw-r--r--server/_build/default/lib/bcrypt/LICENSE116
-rw-r--r--server/_build/default/lib/bcrypt/README.md186
-rw-r--r--server/_build/default/lib/bcrypt/c_src/Makefile89
-rw-r--r--server/_build/default/lib/bcrypt/c_src/async_queue.c141
-rw-r--r--server/_build/default/lib/bcrypt/c_src/async_queue.h83
-rw-r--r--server/_build/default/lib/bcrypt/c_src/async_queue.obin0 -> 3472 bytes
-rw-r--r--server/_build/default/lib/bcrypt/c_src/bcrypt.c281
-rw-r--r--server/_build/default/lib/bcrypt/c_src/bcrypt.obin0 -> 5928 bytes
-rw-r--r--server/_build/default/lib/bcrypt/c_src/bcrypt_nif.c244
-rw-r--r--server/_build/default/lib/bcrypt/c_src/bcrypt_nif.h40
-rw-r--r--server/_build/default/lib/bcrypt/c_src/bcrypt_nif.obin0 -> 8072 bytes
-rw-r--r--server/_build/default/lib/bcrypt/c_src/bcrypt_port.c266
-rw-r--r--server/_build/default/lib/bcrypt/c_src/bcrypt_port.obin0 -> 5904 bytes
-rw-r--r--server/_build/default/lib/bcrypt/c_src/blowfish.c686
-rw-r--r--server/_build/default/lib/bcrypt/c_src/blowfish.obin0 -> 12504 bytes
-rw-r--r--server/_build/default/lib/bcrypt/c_src/erl_blf.h94
-rw-r--r--server/_build/default/lib/bcrypt/ebin/bcrypt.app18
-rw-r--r--server/_build/default/lib/bcrypt/ebin/bcrypt.beambin0 -> 3712 bytes
-rw-r--r--server/_build/default/lib/bcrypt/ebin/bcrypt_app.beambin0 -> 2004 bytes
-rw-r--r--server/_build/default/lib/bcrypt/ebin/bcrypt_nif.beambin0 -> 3480 bytes
-rw-r--r--server/_build/default/lib/bcrypt/ebin/bcrypt_nif_pool_sup.beambin0 -> 3052 bytes
-rw-r--r--server/_build/default/lib/bcrypt/ebin/bcrypt_nif_worker.beambin0 -> 6308 bytes
-rw-r--r--server/_build/default/lib/bcrypt/ebin/bcrypt_pool.beambin0 -> 6916 bytes
-rw-r--r--server/_build/default/lib/bcrypt/ebin/bcrypt_port.beambin0 -> 8268 bytes
-rw-r--r--server/_build/default/lib/bcrypt/ebin/bcrypt_port_sup.beambin0 -> 2004 bytes
-rw-r--r--server/_build/default/lib/bcrypt/ebin/bcrypt_sup.beambin0 -> 2460 bytes
-rw-r--r--server/_build/default/lib/bcrypt/hex_metadata.config24
-rw-r--r--server/_build/default/lib/bcrypt/priv/.gitignore0
-rwxr-xr-xserver/_build/default/lib/bcrypt/priv/bcryptbin0 -> 116432 bytes
-rwxr-xr-xserver/_build/default/lib/bcrypt/priv/bcrypt_nif.sobin0 -> 140056 bytes
-rw-r--r--server/_build/default/lib/bcrypt/rebar.config62
-rw-r--r--server/_build/default/lib/bcrypt/rebar.lock8
-rw-r--r--server/_build/default/lib/bcrypt/src/bcrypt.app.src15
-rw-r--r--server/_build/default/lib/bcrypt/src/bcrypt.erl91
-rw-r--r--server/_build/default/lib/bcrypt/src/bcrypt_app.erl27
-rw-r--r--server/_build/default/lib/bcrypt/src/bcrypt_nif.erl99
-rw-r--r--server/_build/default/lib/bcrypt/src/bcrypt_nif_pool_sup.erl50
-rw-r--r--server/_build/default/lib/bcrypt/src/bcrypt_nif_worker.erl144
-rw-r--r--server/_build/default/lib/bcrypt/src/bcrypt_pool.erl140
-rw-r--r--server/_build/default/lib/bcrypt/src/bcrypt_port.erl157
-rw-r--r--server/_build/default/lib/bcrypt/src/bcrypt_port_sup.erl21
-rw-r--r--server/_build/default/lib/bcrypt/src/bcrypt_sup.erl28
-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
-rw-r--r--server/_build/default/lib/cowlib/LICENSE13
-rw-r--r--server/_build/default/lib/cowlib/Makefile119
-rw-r--r--server/_build/default/lib/cowlib/README.asciidoc18
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_base64url.beambin0 -> 3144 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_cookie.beambin0 -> 15952 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_date.beambin0 -> 37508 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_hpack.beambin0 -> 219776 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_http.beambin0 -> 15096 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_http2.beambin0 -> 28928 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_http2_machine.beambin0 -> 69128 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_http_hd.beambin0 -> 452584 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_http_struct_hd.beambin0 -> 21048 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_http_te.beambin0 -> 9532 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_iolists.beambin0 -> 2968 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_link.beambin0 -> 17372 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_mimetypes.beambin0 -> 86424 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_multipart.beambin0 -> 21312 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_qs.beambin0 -> 13112 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_spdy.beambin0 -> 20992 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_sse.beambin0 -> 8368 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_uri.beambin0 -> 17900 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_uri_template.beambin0 -> 19300 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cow_ws.beambin0 -> 34116 bytes
-rw-r--r--server/_build/default/lib/cowlib/ebin/cowlib.app8
-rw-r--r--server/_build/default/lib/cowlib/erlang.mk8373
-rw-r--r--server/_build/default/lib/cowlib/hex_metadata.config25
-rw-r--r--server/_build/default/lib/cowlib/include/cow_inline.hrl447
-rw-r--r--server/_build/default/lib/cowlib/include/cow_parse.hrl83
-rw-r--r--server/_build/default/lib/cowlib/src/cow_base64url.erl81
-rw-r--r--server/_build/default/lib/cowlib/src/cow_cookie.erl456
-rw-r--r--server/_build/default/lib/cowlib/src/cow_date.erl434
-rw-r--r--server/_build/default/lib/cowlib/src/cow_hpack.erl1449
-rw-r--r--server/_build/default/lib/cowlib/src/cow_hpack_dec_huffman_lookup.hrl4132
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http.erl426
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http2.erl482
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http2_machine.erl1647
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http_hd.erl3642
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http_struct_hd.erl522
-rw-r--r--server/_build/default/lib/cowlib/src/cow_http_te.erl373
-rw-r--r--server/_build/default/lib/cowlib/src/cow_iolists.erl95
-rw-r--r--server/_build/default/lib/cowlib/src/cow_link.erl445
-rw-r--r--server/_build/default/lib/cowlib/src/cow_mimetypes.erl1045
-rw-r--r--server/_build/default/lib/cowlib/src/cow_mimetypes.erl.src61
-rw-r--r--server/_build/default/lib/cowlib/src/cow_multipart.erl775
-rw-r--r--server/_build/default/lib/cowlib/src/cow_qs.erl563
-rw-r--r--server/_build/default/lib/cowlib/src/cow_spdy.erl313
-rw-r--r--server/_build/default/lib/cowlib/src/cow_spdy.hrl181
-rw-r--r--server/_build/default/lib/cowlib/src/cow_sse.erl349
-rw-r--r--server/_build/default/lib/cowlib/src/cow_uri.erl339
-rw-r--r--server/_build/default/lib/cowlib/src/cow_uri_template.erl360
-rw-r--r--server/_build/default/lib/cowlib/src/cow_ws.erl741
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat.app16
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_app.beambin0 -> 1408 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_auth.beambin0 -> 17208 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_config.beambin0 -> 2580 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_db.beambin0 -> 16716 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_dev.beambin0 -> 5512 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_http.beambin0 -> 12388 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_http_404.beambin0 -> 2012 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_http_auth.beambin0 -> 9604 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_http_download.beambin0 -> 1544 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_http_eventsource.beambin0 -> 1564 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_http_health.beambin0 -> 2352 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_http_redirect.beambin0 -> 2304 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_http_static.beambin0 -> 6064 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_http_upload.beambin0 -> 1532 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_methods.beambin0 -> 17876 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_presence.beambin0 -> 1852 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_push.beambin0 -> 1836 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_sup.beambin0 -> 2256 bytes
-rw-r--r--server/_build/default/lib/jchat/ebin/jchat_utils.beambin0 -> 8152 bytes
l---------server/_build/default/lib/jchat/include1
l---------server/_build/default/lib/jchat/priv1
l---------server/_build/default/lib/jchat/src1
-rw-r--r--server/_build/default/lib/jsx/LICENSE21
-rw-r--r--server/_build/default/lib/jsx/README.md696
-rw-r--r--server/_build/default/lib/jsx/ebin/jsx.app10
-rw-r--r--server/_build/default/lib/jsx/ebin/jsx.beambin0 -> 5536 bytes
-rw-r--r--server/_build/default/lib/jsx/ebin/jsx_config.beambin0 -> 9096 bytes
-rw-r--r--server/_build/default/lib/jsx/ebin/jsx_consult.beambin0 -> 3320 bytes
-rw-r--r--server/_build/default/lib/jsx/ebin/jsx_decoder.beambin0 -> 65684 bytes
-rw-r--r--server/_build/default/lib/jsx/ebin/jsx_encoder.beambin0 -> 4316 bytes
-rw-r--r--server/_build/default/lib/jsx/ebin/jsx_parser.beambin0 -> 32784 bytes
-rw-r--r--server/_build/default/lib/jsx/ebin/jsx_to_json.beambin0 -> 10160 bytes
-rw-r--r--server/_build/default/lib/jsx/ebin/jsx_to_term.beambin0 -> 7544 bytes
-rw-r--r--server/_build/default/lib/jsx/ebin/jsx_verify.beambin0 -> 3556 bytes
-rw-r--r--server/_build/default/lib/jsx/hex_metadata.config15
-rw-r--r--server/_build/default/lib/jsx/rebar.config17
-rw-r--r--server/_build/default/lib/jsx/rebar.lock1
-rw-r--r--server/_build/default/lib/jsx/src/jsx.app.src10
-rw-r--r--server/_build/default/lib/jsx/src/jsx.erl506
-rw-r--r--server/_build/default/lib/jsx/src/jsx_config.erl393
-rw-r--r--server/_build/default/lib/jsx/src/jsx_config.hrl18
-rw-r--r--server/_build/default/lib/jsx/src/jsx_consult.erl81
-rw-r--r--server/_build/default/lib/jsx/src/jsx_decoder.erl1909
-rw-r--r--server/_build/default/lib/jsx/src/jsx_encoder.erl116
-rw-r--r--server/_build/default/lib/jsx/src/jsx_parser.erl1214
-rw-r--r--server/_build/default/lib/jsx/src/jsx_to_json.erl408
-rw-r--r--server/_build/default/lib/jsx/src/jsx_to_term.erl389
-rw-r--r--server/_build/default/lib/jsx/src/jsx_verify.erl121
-rw-r--r--server/_build/default/lib/jwt/LICENSE20
-rw-r--r--server/_build/default/lib/jwt/README.md101
-rw-r--r--server/_build/default/lib/jwt/ebin/jwk.beambin0 -> 10844 bytes
-rw-r--r--server/_build/default/lib/jwt/ebin/jwt.app9
-rw-r--r--server/_build/default/lib/jwt/ebin/jwt.beambin0 -> 12536 bytes
-rw-r--r--server/_build/default/lib/jwt/ebin/jwt_ecdsa.beambin0 -> 13516 bytes
-rw-r--r--server/_build/default/lib/jwt/hex_metadata.config21
-rw-r--r--server/_build/default/lib/jwt/include/jwt_ecdsa.hrl49
-rw-r--r--server/_build/default/lib/jwt/rebar.config22
-rw-r--r--server/_build/default/lib/jwt/rebar.config.script35
-rw-r--r--server/_build/default/lib/jwt/rebar.lock11
-rw-r--r--server/_build/default/lib/jwt/src/jwk.erl69
-rw-r--r--server/_build/default/lib/jwt/src/jwt.app.src8
-rw-r--r--server/_build/default/lib/jwt/src/jwt.erl340
-rw-r--r--server/_build/default/lib/jwt/src/jwt_ecdsa.erl76
-rw-r--r--server/_build/default/lib/poolboy/LICENSE15
-rw-r--r--server/_build/default/lib/poolboy/README.md171
-rw-r--r--server/_build/default/lib/poolboy/ebin/poolboy.app9
-rw-r--r--server/_build/default/lib/poolboy/ebin/poolboy.beambin0 -> 17524 bytes
-rw-r--r--server/_build/default/lib/poolboy/ebin/poolboy_sup.beambin0 -> 1804 bytes
-rw-r--r--server/_build/default/lib/poolboy/ebin/poolboy_worker.beambin0 -> 1560 bytes
-rw-r--r--server/_build/default/lib/poolboy/hex_metadata.config15
-rw-r--r--server/_build/default/lib/poolboy/rebar.config15
-rw-r--r--server/_build/default/lib/poolboy/rebar.lock1
-rw-r--r--server/_build/default/lib/poolboy/src/poolboy.app.src8
-rw-r--r--server/_build/default/lib/poolboy/src/poolboy.erl357
-rw-r--r--server/_build/default/lib/poolboy/src/poolboy_sup.erl14
-rw-r--r--server/_build/default/lib/poolboy/src/poolboy_worker.erl10
-rw-r--r--server/_build/default/lib/ranch/LICENSE13
-rw-r--r--server/_build/default/lib/ranch/Makefile85
-rw-r--r--server/_build/default/lib/ranch/README.asciidoc38
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch.app9
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch.beambin0 -> 21108 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_acceptor.beambin0 -> 3088 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_acceptors_sup.beambin0 -> 4692 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_app.beambin0 -> 2380 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_conns_sup.beambin0 -> 14568 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_crc32c.beambin0 -> 5128 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_listener_sup.beambin0 -> 2704 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_protocol.beambin0 -> 1548 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_proxy_header.beambin0 -> 24316 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_server.beambin0 -> 11152 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_ssl.beambin0 -> 9480 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_sup.beambin0 -> 2452 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_tcp.beambin0 -> 9276 bytes
-rw-r--r--server/_build/default/lib/ranch/ebin/ranch_transport.beambin0 -> 6736 bytes
-rw-r--r--server/_build/default/lib/ranch/erlang.mk8156
-rw-r--r--server/_build/default/lib/ranch/hex_metadata.config22
-rw-r--r--server/_build/default/lib/ranch/src/ranch.erl504
-rw-r--r--server/_build/default/lib/ranch/src/ranch_acceptor.erl64
-rw-r--r--server/_build/default/lib/ranch/src/ranch_acceptors_sup.erl71
-rw-r--r--server/_build/default/lib/ranch/src/ranch_app.erl44
-rw-r--r--server/_build/default/lib/ranch/src/ranch_conns_sup.erl325
-rw-r--r--server/_build/default/lib/ranch/src/ranch_crc32c.erl115
-rw-r--r--server/_build/default/lib/ranch/src/ranch_listener_sup.erl41
-rw-r--r--server/_build/default/lib/ranch/src/ranch_protocol.erl24
-rw-r--r--server/_build/default/lib/ranch/src/ranch_proxy_header.erl880
-rw-r--r--server/_build/default/lib/ranch/src/ranch_server.erl233
-rw-r--r--server/_build/default/lib/ranch/src/ranch_ssl.erl243
-rw-r--r--server/_build/default/lib/ranch/src/ranch_sup.erl40
-rw-r--r--server/_build/default/lib/ranch/src/ranch_tcp.erl245
-rw-r--r--server/_build/default/lib/ranch/src/ranch_transport.erl151
272 files changed, 68236 insertions, 0 deletions
diff --git a/server/_build/default/lib/.rebar3/rebar_compiler_erl/source_apps.dag b/server/_build/default/lib/.rebar3/rebar_compiler_erl/source_apps.dag
new file mode 100644
index 0000000..e6c9554
--- /dev/null
+++ b/server/_build/default/lib/.rebar3/rebar_compiler_erl/source_apps.dag
Binary files differ
diff --git a/server/_build/default/lib/.rebar3/rebar_compiler_erl/source_project_apps.dag b/server/_build/default/lib/.rebar3/rebar_compiler_erl/source_project_apps.dag
new file mode 100644
index 0000000..0562672
--- /dev/null
+++ b/server/_build/default/lib/.rebar3/rebar_compiler_erl/source_project_apps.dag
Binary files differ
diff --git a/server/_build/default/lib/base64url/LICENSE.txt b/server/_build/default/lib/base64url/LICENSE.txt
new file mode 100644
index 0000000..c3f0a46
--- /dev/null
+++ b/server/_build/default/lib/base64url/LICENSE.txt
@@ -0,0 +1,18 @@
+Copyright (c) 2013 Vladimir Dronnikov <dronnikov@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/server/_build/default/lib/base64url/README.md b/server/_build/default/lib/base64url/README.md
new file mode 100644
index 0000000..165a5dd
--- /dev/null
+++ b/server/_build/default/lib/base64url/README.md
@@ -0,0 +1,58 @@
+Base64Url
+==============
+
+[![Hex.pm](https://img.shields.io/hexpm/v/base64url.svg)](https://hex.pm/packages/base64url)
+
+Standalone [URL safe](http://tools.ietf.org/html/rfc4648) base64-compatible codec.
+
+Usage
+--------------
+
+URL-Safe base64 encoding:
+```erlang
+base64url:encode(<<255,127,254,252>>).
+<<"_3_-_A">>
+base64url:decode(<<"_3_-_A">>).
+<<255,127,254,252>>
+```
+
+Vanilla base64 encoding:
+```erlang
+base64:encode(<<255,127,254,252>>).
+<<"/3/+/A==">>
+```
+
+Some systems in the wild use base64 URL encoding, but keep the padding for MIME compatibility (base64 Content-Transfer-Encoding). To interact with such systems, use:
+```erlang
+base64url:encode_mime(<<255,127,254,252>>).
+<<"_3_-_A==">>
+base64url:decode(<<"_3_-_A==">>).
+<<255,127,254,252>>
+```
+
+Thanks
+--------------
+
+To authors of [this](https://github.com/basho/riak_control/blob/master/src/base64url.erl) and [this](https://github.com/mochi/mochiweb/blob/master/src/mochiweb_base64url.erl).
+
+[License](base64url/blob/master/LICENSE.txt)
+-------
+
+Copyright (c) 2013 Vladimir Dronnikov <dronnikov@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/server/_build/default/lib/base64url/ebin/base64url.app b/server/_build/default/lib/base64url/ebin/base64url.app
new file mode 100644
index 0000000..2990bf7
--- /dev/null
+++ b/server/_build/default/lib/base64url/ebin/base64url.app
@@ -0,0 +1,11 @@
+{application,base64url,
+ [{description,"URL safe base64-compatible codec"},
+ {vsn,"0.0.1"},
+ {id,"git"},
+ {registered,[]},
+ {applications,[kernel,stdlib]},
+ {env,[]},
+ {contributors,["Vladimir Dronnikov"]},
+ {licenses,["MIT"]},
+ {links,[{"Github","https://github.com/dvv/base64url"}]},
+ {modules,[base64url]}]}.
diff --git a/server/_build/default/lib/base64url/ebin/base64url.beam b/server/_build/default/lib/base64url/ebin/base64url.beam
new file mode 100644
index 0000000..b14b81b
--- /dev/null
+++ b/server/_build/default/lib/base64url/ebin/base64url.beam
Binary files differ
diff --git a/server/_build/default/lib/base64url/hex_metadata.config b/server/_build/default/lib/base64url/hex_metadata.config
new file mode 100644
index 0000000..6eafd23
--- /dev/null
+++ b/server/_build/default/lib/base64url/hex_metadata.config
@@ -0,0 +1,12 @@
+{<<"name">>,<<"base64url">>}.
+{<<"version">>,<<"0.0.1">>}.
+{<<"app">>,<<"base64url">>}.
+{<<"contributors">>,[<<"Vladimir Dronnikov">>]}.
+{<<"precompiled">>,false}.
+{<<"description">>,<<"URL safe base64-compatible codec">>}.
+{<<"files">>,
+ [<<"src/base64url.app.src">>,<<"src/base64url.erl">>,<<"rebar.config">>,
+ <<"README.md">>,<<"LICENSE.txt">>]}.
+{<<"licenses">>,[<<"MIT">>]}.
+{<<"links">>,[{<<"Github">>,<<"https://github.com/dvv/base64url">>}]}.
+{<<"build_tools">>,[<<"rebar">>]}.
diff --git a/server/_build/default/lib/base64url/rebar.config b/server/_build/default/lib/base64url/rebar.config
new file mode 100644
index 0000000..b3028a1
--- /dev/null
+++ b/server/_build/default/lib/base64url/rebar.config
@@ -0,0 +1,11 @@
+{lib_dirs, ["deps"]}.
+
+{erl_opts, [
+ debug_info,
+ warn_format,
+ warn_export_vars,
+ warn_obsolete_guard,
+ warn_bif_clash
+]}.
+
+{cover_enabled, true}.
diff --git a/server/_build/default/lib/base64url/src/base64url.app.src b/server/_build/default/lib/base64url/src/base64url.app.src
new file mode 100644
index 0000000..b914370
--- /dev/null
+++ b/server/_build/default/lib/base64url/src/base64url.app.src
@@ -0,0 +1,14 @@
+{application, base64url, [
+ {description, "URL safe base64-compatible codec"},
+ {vsn, "0.0.1"},
+ {id, "git"},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {env, []},
+ {contributors, ["Vladimir Dronnikov"]},
+ {licenses, ["MIT"]},
+ {links, [{"Github", "https://github.com/dvv/base64url"}]}
+]}.
diff --git a/server/_build/default/lib/base64url/src/base64url.erl b/server/_build/default/lib/base64url/src/base64url.erl
new file mode 100644
index 0000000..fa38269
--- /dev/null
+++ b/server/_build/default/lib/base64url/src/base64url.erl
@@ -0,0 +1,98 @@
+%%
+%% @doc URL safe base64-compatible codec.
+%%
+%% Based heavily on the code extracted from:
+%% https://github.com/basho/riak_control/blob/master/src/base64url.erl and
+%% https://github.com/mochi/mochiweb/blob/master/src/mochiweb_base64url.erl.
+%%
+
+-module(base64url).
+-author('Vladimir Dronnikov <dronnikov@gmail.com>').
+
+-export([
+ decode/1,
+ encode/1,
+ encode_mime/1
+ ]).
+
+-spec encode(
+ binary() | iolist()
+ ) -> binary().
+
+encode(Bin) when is_binary(Bin) ->
+ << << (urlencode_digit(D)) >> || <<D>> <= base64:encode(Bin), D =/= $= >>;
+encode(L) when is_list(L) ->
+ encode(iolist_to_binary(L)).
+
+-spec encode_mime(
+ binary() | iolist()
+ ) -> binary().
+encode_mime(Bin) when is_binary(Bin) ->
+ << << (urlencode_digit(D)) >> || <<D>> <= base64:encode(Bin) >>;
+encode_mime(L) when is_list(L) ->
+ encode_mime(iolist_to_binary(L)).
+
+-spec decode(
+ binary() | iolist()
+ ) -> binary().
+
+decode(Bin) when is_binary(Bin) ->
+ Bin2 = case byte_size(Bin) rem 4 of
+ % 1 -> << Bin/binary, "===" >>;
+ 2 -> << Bin/binary, "==" >>;
+ 3 -> << Bin/binary, "=" >>;
+ _ -> Bin
+ end,
+ base64:decode(<< << (urldecode_digit(D)) >> || <<D>> <= Bin2 >>);
+decode(L) when is_list(L) ->
+ decode(iolist_to_binary(L)).
+
+urlencode_digit($/) -> $_;
+urlencode_digit($+) -> $-;
+urlencode_digit(D) -> D.
+
+urldecode_digit($_) -> $/;
+urldecode_digit($-) -> $+;
+urldecode_digit(D) -> D.
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+aim_test() ->
+ % vanilla base64 produce URL unsafe output
+ ?assertNotEqual(
+ binary:match(base64:encode([255,127,254,252]), [<<"=">>, <<"/">>, <<"+">>]),
+ nomatch),
+ % this codec produce URL safe output
+ ?assertEqual(
+ binary:match(encode([255,127,254,252]), [<<"=">>, <<"/">>, <<"+">>]),
+ nomatch),
+ % the mime codec produces URL unsafe output, but only because of padding
+ ?assertEqual(
+ binary:match(encode_mime([255,127,254,252]), [<<"/">>, <<"+">>]),
+ nomatch),
+ ?assertNotEqual(
+ binary:match(encode_mime([255,127,254,252]), [<<"=">>]),
+ nomatch).
+
+codec_test() ->
+ % codec is lossless with or without padding
+ ?assertEqual(decode(encode(<<"foo">>)), <<"foo">>),
+ ?assertEqual(decode(encode(<<"foo1">>)), <<"foo1">>),
+ ?assertEqual(decode(encode(<<"foo12">>)), <<"foo12">>),
+ ?assertEqual(decode(encode(<<"foo123">>)), <<"foo123">>),
+ ?assertEqual(decode(encode_mime(<<"foo">>)), <<"foo">>),
+ ?assertEqual(decode(encode_mime(<<"foo1">>)), <<"foo1">>),
+ ?assertEqual(decode(encode_mime(<<"foo12">>)), <<"foo12">>),
+ ?assertEqual(decode(encode_mime(<<"foo123">>)), <<"foo123">>).
+
+iolist_test() ->
+ % codec supports iolists
+ ?assertEqual(decode(encode("foo")), <<"foo">>),
+ ?assertEqual(decode(encode(["fo", "o1"])), <<"foo1">>),
+ ?assertEqual(decode(encode([255,127,254,252])), <<255,127,254,252>>),
+ ?assertEqual(decode(encode_mime("foo")), <<"foo">>),
+ ?assertEqual(decode(encode_mime(["fo", "o1"])), <<"foo1">>),
+ ?assertEqual(decode(encode_mime([255,127,254,252])), <<255,127,254,252>>).
+
+-endif.
diff --git a/server/_build/default/lib/bcrypt/LICENSE b/server/_build/default/lib/bcrypt/LICENSE
new file mode 100644
index 0000000..f479304
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/LICENSE
@@ -0,0 +1,116 @@
+The Erlang code is subject to this license:
+
+%% Copyright (c) 2011 Hunter Morris <hunter.morris@smarkets.com>
+
+%% Permission to use, copy, modify, and distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+The underlying blowfish code is derived from OpenBSD libc and is
+subject to the following license:
+
+/*
+ * Blowfish block cipher for OpenBSD
+ * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
+ * All rights reserved.
+ *
+ * Implementation advice by David Mazieres <dm@lcs.mit.edu>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Niels Provos.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+The underlying bcrypt (hashing) code is derived from OpenBSD libc and is
+subject to the following license:
+
+/*
+ * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Niels Provos.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+The asynchronous queue code (c_src/async_queue.c and
+c_src/async_queue.h) is from the esnappy project, copyright 2011
+Konstantin V. Sorokin. It is subject to the following license:
+
+Copyright (c) 2011 Konstantin V. Sorokin
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+3. Neither the name of the copyright holder nor the names of contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGE.
diff --git a/server/_build/default/lib/bcrypt/README.md b/server/_build/default/lib/bcrypt/README.md
new file mode 100644
index 0000000..8b6c481
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/README.md
@@ -0,0 +1,186 @@
+bcrypt
+======
+
+![Test](https://github.com/erlangpack/bcrypt/workflows/Test/badge.svg)
+[![Hex pm](http://img.shields.io/hexpm/v/bcrypt.svg?style=flat)](https://hex.pm/packages/bcrypt)
+
+erlang-bcrypt is a wrapper around the OpenBSD Blowfish password hashing
+algorithm, as described in
+[A Future-Adaptable Password Scheme](http://www.openbsd.org/papers/bcrypt-paper.ps)
+by Niels Provos and David Mazieres.
+
+This bcrypt repository at erlangpack is in active maintainance and used
+as the basis of the Hex package.
+
+
+OTP Compatibility
+-----------------
+
+erlang-bcrypt is compatible with OTP 21.3 to 23.
+
+Use version 1.0.3 on OTP versions before 21.3
+
+In version 1.1.0 support for OTP 21.2 and earlier is removed
+due to the removal of erl_interface in OTP 23.
+
+
+Rebar.config
+------------
+
+erlang-bcrypt is on Hex:
+
+ ```erlang
+ {deps, [
+ {bcrypt, "1.1.3"}
+ ]}.
+ ```
+
+To use the master branch:
+
+ ```erlang
+ {deps, [
+ {bcrypt, {git, ".*", {git, "https://github.com/erlangpack/bcrypt.git", {branch, "master"}}}
+ ]}.
+ ```
+
+
+Basic build instructions
+------------------------
+
+1. Build it (project uses rebar3, a Makefile is included):
+
+ ```shell
+ make
+ ```
+
+2. Run it (simple way, starting sasl, crypto and bcrypt):
+
+ ```shell
+ $ ./rebar3 shell
+ ===> Verifying dependencies...
+ ===> Compiling bcrypt
+ make: Nothing to be done for `all'.
+ Erlang/OTP 23 [erts-11.0] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe]
+
+ Eshell V11.0 (abort with ^G)
+ 1> application:ensure_all_started(bcrypt).
+ {ok,[bcrypt]}
+ 2>
+ ```
+
+Basic usage instructions
+------------------------
+
+Hash a password using a salt with the default number of rounds:
+
+```erlang
+1> {ok, Salt} = bcrypt:gen_salt().
+{ok,"$2a$12$sSS8Eg.ovVzaHzi1nUHYK."}
+2> {ok, Hash} = bcrypt:hashpw("foo", Salt).
+{ok,"$2a$12$sSS8Eg.ovVzaHzi1nUHYK.HbUIOdlQI0iS22Q5rd5z.JVVYH6sfm6"}
+```
+
+Verify the password:
+
+```erlang
+3> {ok, Hash} =:= bcrypt:hashpw("foo", Hash).
+true
+4> {ok, Hash} =:= bcrypt:hashpw("bar", Hash).
+false
+```
+
+Configuration
+-------------
+
+The bcrypt application is configured by changing values in the
+application's environment:
+
+`default_log_rounds`
+ Sets the default number of rounds which define the complexity of the
+ hash function. Defaults to `12`.
+
+`mechanism`
+ Specifies whether to use the NIF implementation (`'nif'`) or a
+ pool of port programs (`'port'`). Defaults to `'nif'`.
+
+ `Note: the NIF implementation no longer blocks the Erlang VM scheduler threads`
+
+`pool_size`
+ Specifies the size of the port program pool. Defaults to `4`.
+
+`nif_pool_size`
+ Specifies the size of the nif program pool. Defaults to `4`.
+
+`nif_pool_max_overflow`
+ Specifies the max workers to overflow of the nif program pool. Defaults to `10`.
+
+Run tests
+---------
+
+To run the eunit and proper tests use:
+
+```shell
+make tests
+```
+
+To test all exported function of a module use:
+
+```shell
+$ ./rebar3 as test shell
+===> Verifying dependencies...
+===> Compiling bcrypt
+make: Nothing to be done for all.
+Erlang/OTP 23 [erts-11.0] [source] [64-bit] [smp:12:12] [ds:12:12:10] [async-threads:1] [hipe]
+
+Eshell V11.0 (abort with ^G)
+1> application:ensure_all_started(bcrypt).
+{ok,[bcrypt]}
+2>proper:check_specs(bcrypt).
+Testing bcrypt:gen_salt/0
+....................................................................................................
+OK: Passed 100 test(s).
+
+Testing bcrypt:hashpw/2
+....................................................................................................
+OK: Passed 100 test(s).
+
+Testing bcrypt:gen_salt/1
+....................................................................................................
+OK: Passed 100 test(s).
+
+Testing bcrypt:mechanism/0
+....................................................................................................
+OK: Passed 100 test(s).
+
+[]
+4>
+```
+
+## Documentation generation
+
+### Edoc
+
+#### Generate public API
+```
+rebar3 edoc
+```
+
+#### Generate private API
+```
+rebar3 as edoc_private edoc
+```
+
+### ExDoc
+
+```
+rebar3 ex_doc --output edoc
+```
+
+
+Both the _port_ and the _NIF_ version of bcrypt are tested.
+All tests should pass.
+
+Original authors
+----------------
+
+Hunter Morris & [Mrinal Wadhwa](https://github.com/mrinalwadhwa).
diff --git a/server/_build/default/lib/bcrypt/c_src/Makefile b/server/_build/default/lib/bcrypt/c_src/Makefile
new file mode 100644
index 0000000..0e4a634
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/Makefile
@@ -0,0 +1,89 @@
+# Based on c_src.mk from erlang.mk by Loic Hoguin <essen@ninenines.eu>
+
+CURDIR := .
+BASEDIR := ..
+
+PROJECT ?= $(notdir $(BASEDIR))
+PROJECT := $(strip $(PROJECT))
+
+ERTS_INCLUDE_DIR ?= $(shell erl -noshell -eval "io:format(\"~s/erts-~s/include/\", [code:root_dir(), erlang:system_info(version)]), halt().")
+ERL_INTERFACE_INCLUDE_DIR ?= $(shell erl -noshell -eval "io:format(\"~s\", [code:lib_dir(erl_interface, include)]), halt().")
+ERL_INTERFACE_LIB_DIR ?= $(shell erl -noshell -eval "io:format(\"~s\", [code:lib_dir(erl_interface, lib)]), halt().")
+
+C_SRC_DIR = $(CURDIR)
+C_SRC_NIF = $(CURDIR)/../priv/bcrypt_nif.so
+C_SRC_PORT = $(CURDIR)/../priv/bcrypt
+
+# DRV_LDFLAGS = -shared $(ERL_LDFLAGS) -lpthread
+DRV_CFLAGS = -Ic_src -Wall -O3 -fPIC $(ERL_CFLAGS)
+
+# System type and C compiler/flags.
+
+UNAME_SYS := $(shell uname -s)
+ifeq ($(UNAME_SYS), Darwin)
+ CC ?= cc
+ CFLAGS ?= -O3 -std=c99 -Wall -Wmissing-prototypes
+ LDFLAGS += -flat_namespace -undefined suppress
+ DRV_LDFLAGS = -flat_namespace -undefined suppress $(ERL_LDFLAGS)
+else ifeq ($(UNAME_SYS), Linux)
+ CC ?= gcc
+ CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes -D_DEFAULT_SOURCE
+ DRV_LDFLAGS = $(ERL_LDFLAGS)
+else # FreeBSD
+ CC ?= cc
+ CFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes
+ DRV_LDFLAGS = $(ERL_LDFLAGS)
+endif
+
+# {"DRV_LDFLAGS","-shared $ERL_LDFLAGS -lpthread"},
+# {"darwin", "DRV_LDFLAGS", "-bundle -flat_namespace -undefined suppress $ERL_LDFLAGS -lpthread"},
+# {"solaris", "ERL_LDFLAGS", "-lxnet -lssp -lnsl $ERL_LDFLAGS"},
+# {"DRV_CFLAGS","-Ic_src -Wall -O3 -fPIC $ERL_CFLAGS"},
+# {"CFLAGS", "$CFLAGS -Ic_src -Wall -O3"},
+# {"LDFLAGS", "$LDFLAGS -lpthread"}
+
+CFLAGS += -fPIC -I $(ERTS_INCLUDE_DIR) -I $(ERL_INTERFACE_INCLUDE_DIR)
+
+LDLIBS += -L $(ERL_INTERFACE_LIB_DIR) -lei -lpthread
+
+# Verbosity.
+
+c_verbose_0 = @echo " C " $(?F);
+c_verbose = $(c_verbose_$(V))
+
+cpp_verbose_0 = @echo " CPP " $(?F);
+cpp_verbose = $(cpp_verbose_$(V))
+
+link_verbose_0 = @echo " LD " $(@F);
+link_verbose = $(link_verbose_$(V))
+
+SOURCES := $(shell find $(C_SRC_DIR) -type f \( -name "*.c" -o -name "*.C" -o -name "*.cc" -o -name "*.cpp" \))
+OBJECTS = $(addsuffix .o, $(basename $(SOURCES)))
+
+COMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c
+COMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c
+
+all: $(C_SRC_NIF) $(C_SRC_PORT)
+
+$(C_SRC_PORT): $(OBJECTS)
+ @mkdir -p $(BASEDIR)/priv/
+ $(CC) $(CFLAGS) bcrypt_port.o bcrypt.o blowfish.o $(DRV_LDFLAGS) $(LDLIBS) -o ../priv/bcrypt
+
+$(C_SRC_NIF): $(OBJECTS)
+ @mkdir -p $(BASEDIR)/priv/
+ $(link_verbose) $(CC) $(OBJECTS) $(LDFLAGS) -shared $(LDLIBS) -o $(C_SRC_NIF)
+
+%.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:
+ @rm -f $(C_SRC_OUTPUT) $(OBJECTS)
diff --git a/server/_build/default/lib/bcrypt/c_src/async_queue.c b/server/_build/default/lib/bcrypt/c_src/async_queue.c
new file mode 100644
index 0000000..fbe30aa
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/async_queue.c
@@ -0,0 +1,141 @@
+/*
+ From https://github.com/thekvs/esnappy:
+ Copyright (c) 2011 Konstantin V. Sorokin
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holder nor the names of contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+*/
+// vim: shiftwidth=4 expandtab
+#include "async_queue.h"
+
+async_queue_t*
+async_queue_create(char* mutex_name, char* condvar_name)
+{
+ async_queue_t *aq;
+
+ aq = ALLOC(sizeof(*aq));
+
+ if (!aq) {
+ errx(1, "enif_alloc() failed");
+ }
+
+ aq->q = ALLOC(sizeof(*(aq->q)));
+
+ if (!(aq->q)) {
+ errx(1, "enif_alloc() failed");
+ }
+
+ TAILQ_INIT(aq->q);
+
+ aq->waiting_threads = aq->len = 0;
+
+ aq->mutex = enif_mutex_create(mutex_name);
+
+ if (!aq->mutex) {
+ errx(1, "enif_mutex_create() failed");
+ }
+
+ aq->cond = enif_cond_create(condvar_name);
+
+ if (!aq->cond) {
+ errx(1, "enif_cond_create() failed");
+ }
+
+ return aq;
+}
+
+int
+async_queue_length(async_queue_t *aq)
+{
+ int length;
+
+ MUTEX_LOCK(aq->mutex);
+ length = aq->len - aq->waiting_threads;
+ MUTEX_UNLOCK(aq->mutex);
+
+ return length;
+}
+
+void *
+async_queue_pop(async_queue_t *aq)
+{
+ struct async_queue_entry *en;
+ void *d;
+
+ MUTEX_LOCK(aq->mutex);
+
+ d = NULL;
+ aq->waiting_threads++;
+ while (TAILQ_EMPTY(aq->q)) {
+ enif_cond_wait(aq->cond, aq->mutex);
+ }
+ aq->waiting_threads--;
+
+ en = TAILQ_FIRST(aq->q);
+ TAILQ_REMOVE(aq->q, en, entries);
+ d = en->data;
+ aq->len--;
+ enif_free(en);
+
+ MUTEX_UNLOCK(aq->mutex);
+
+ return d;
+}
+
+void
+async_queue_push(async_queue_t *aq, void *data)
+{
+ struct async_queue_entry *en;
+
+ MUTEX_LOCK(aq->mutex);
+
+ en = ALLOC(sizeof(*en));
+ en->data = data;
+ TAILQ_INSERT_TAIL(aq->q, en, entries);
+ aq->len++;
+
+ COND_SIGNAL(aq->cond);
+ MUTEX_UNLOCK(aq->mutex);
+}
+
+void
+async_queue_destroy(async_queue_t *aq)
+{
+ struct async_queue_entry *en;
+
+ while (!TAILQ_EMPTY(aq->q)) {
+ en = TAILQ_FIRST(aq->q);
+ TAILQ_REMOVE(aq->q, en, entries);
+ enif_free(en);
+ }
+
+ COND_DESTROY(aq->cond);
+ MUTEX_DESTROY(aq->mutex);
+
+ enif_free(aq->q);
+ enif_free(aq);
+}
+
diff --git a/server/_build/default/lib/bcrypt/c_src/async_queue.h b/server/_build/default/lib/bcrypt/c_src/async_queue.h
new file mode 100644
index 0000000..c7804ea
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/async_queue.h
@@ -0,0 +1,83 @@
+/*
+ From https://github.com/thekvs/esnappy:
+ Copyright (c) 2011 Konstantin V. Sorokin
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+ 3. Neither the name of the copyright holder nor the names of contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTOR(S) ``AS IS'' AND
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTOR(S) BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ SUCH DAMAGE.
+*/
+// vim: shiftwidth=4 expandtab
+#ifndef __ASYNC_QUEUE_H_INCLUDED__
+#define __ASYNC_QUEUE_H_INCLUDED__
+
+#include <sys/queue.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <err.h>
+
+#include <erl_nif.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+TAILQ_HEAD(queue, async_queue_entry);
+
+struct async_queue_entry {
+ TAILQ_ENTRY(async_queue_entry) entries;
+ void *data;
+};
+
+typedef struct __async_queue {
+ struct queue *q;
+ ErlNifMutex *mutex;
+ ErlNifCond *cond;
+ int waiting_threads;
+ int len;
+} async_queue_t;
+
+async_queue_t* async_queue_create(char* mutex_name, char* condvar_name);
+int async_queue_length(async_queue_t *aq);
+void* async_queue_pop(async_queue_t *aq);
+void async_queue_push(async_queue_t *aq, void *data);
+void async_queue_destroy(async_queue_t *aq);
+
+#define ALLOC(size) enif_alloc(size)
+#define MUTEX_LOCK(mutex) enif_mutex_lock(mutex)
+#define MUTEX_UNLOCK(mutex) enif_mutex_unlock(mutex)
+#define MUTEX_DESTROY(mutex) enif_mutex_destroy(mutex)
+#define COND_SIGNAL(condvar) enif_cond_signal(condvar)
+#define COND_DESTROY(condvar) enif_cond_destroy(condvar)
+
+#ifdef __cplusplus
+} // extern "C"
+#endif
+
+#endif
diff --git a/server/_build/default/lib/bcrypt/c_src/async_queue.o b/server/_build/default/lib/bcrypt/c_src/async_queue.o
new file mode 100644
index 0000000..f23a2f2
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/async_queue.o
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/c_src/bcrypt.c b/server/_build/default/lib/bcrypt/c_src/bcrypt.c
new file mode 100644
index 0000000..9875123
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/bcrypt.c
@@ -0,0 +1,281 @@
+/* $OpenBSD: bcrypt.c,v 1.24 2008/04/02 19:54:05 millert Exp $ */
+
+/*
+ * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Niels Provos.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/* This password hashing algorithm was designed by David Mazieres
+ * <dm@lcs.mit.edu> and works as follows:
+ *
+ * 1. state := InitState ()
+ * 2. state := ExpandKey (state, salt, password) 3.
+ * REPEAT rounds:
+ * state := ExpandKey (state, 0, salt)
+ * state := ExpandKey(state, 0, password)
+ * 4. ctext := "OrpheanBeholderScryDoubt"
+ * 5. REPEAT 64:
+ * ctext := Encrypt_ECB (state, ctext);
+ * 6. RETURN Concatenate (salt, ctext);
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <string.h>
+#include <pwd.h>
+
+#include "erl_blf.h"
+
+/* This implementation is adaptable to current computing power.
+ * You can have up to 2^31 rounds which should be enough for some
+ * time to come.
+ */
+
+#define BCRYPT_VERSION '2'
+#define BCRYPT_MAXSALT 16 /* Precomputation is just so nice */
+#define BCRYPT_BLOCKS 6 /* Ciphertext blocks */
+#define BCRYPT_MINROUNDS 16 /* we have log2(rounds) in salt */
+
+int ts_bcrypt(char *, const char *, const char *);
+void encode_salt(char *, u_int8_t *, u_int16_t, u_int8_t);
+
+static void encode_base64(u_int8_t *, u_int8_t *, u_int16_t);
+static void decode_base64(u_int8_t *, u_int16_t, u_int8_t *);
+
+#define ERROR -1
+
+const static u_int8_t Base64Code[] =
+"./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+
+const static u_int8_t index_64[128] = {
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+ 255, 255, 255, 255, 255, 255, 0, 1, 54, 55,
+ 56, 57, 58, 59, 60, 61, 62, 63, 255, 255,
+ 255, 255, 255, 255, 255, 2, 3, 4, 5, 6,
+ 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
+ 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
+ 255, 255, 255, 255, 255, 255, 28, 29, 30,
+ 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+ 51, 52, 53, 255, 255, 255, 255, 255
+};
+#define CHAR64(c) ( (c) > 127 ? 255 : index_64[(c)])
+
+static void
+decode_base64(u_int8_t *buffer, u_int16_t len, u_int8_t *data)
+{
+ u_int8_t *bp = buffer;
+ u_int8_t *p = data;
+ u_int8_t c1, c2, c3, c4;
+ while (bp < buffer + len) {
+ c1 = CHAR64(*p);
+ c2 = CHAR64(*(p + 1));
+
+ /* Invalid data */
+ if (c1 == 255 || c2 == 255)
+ break;
+
+ *bp++ = (c1 << 2) | ((c2 & 0x30) >> 4);
+ if (bp >= buffer + len)
+ break;
+
+ c3 = CHAR64(*(p + 2));
+ if (c3 == 255)
+ break;
+
+ *bp++ = ((c2 & 0x0f) << 4) | ((c3 & 0x3c) >> 2);
+ if (bp >= buffer + len)
+ break;
+
+ c4 = CHAR64(*(p + 3));
+ if (c4 == 255)
+ break;
+ *bp++ = ((c3 & 0x03) << 6) | c4;
+
+ p += 4;
+ }
+}
+
+void
+encode_salt(char *salt, u_int8_t *csalt, u_int16_t clen, u_int8_t logr)
+{
+ salt[0] = '$';
+ salt[1] = BCRYPT_VERSION;
+ salt[2] = 'a';
+ salt[3] = '$';
+
+ snprintf(salt + 4, 4, "%2.2u$", logr);
+
+ encode_base64((u_int8_t *) salt + 7, csalt, clen);
+}
+
+/* We handle $Vers$log2(NumRounds)$salt+passwd$
+ i.e. $2$04$iwouldntknowwhattosayetKdJ6iFtacBqJdKe6aW7ou */
+
+int
+ts_bcrypt(char * encrypted, const char *key, const char *salt)
+{
+ blf_ctx state;
+ u_int32_t rounds, i, k;
+ u_int16_t j;
+ u_int8_t key_len, salt_len, logr, minor;
+ u_int8_t ciphertext[4 * BCRYPT_BLOCKS] = "OrpheanBeholderScryDoubt";
+ u_int8_t csalt[BCRYPT_MAXSALT];
+ u_int32_t cdata[BCRYPT_BLOCKS];
+ int n;
+
+ /* Discard "$" identifier */
+ salt++;
+
+ if (*salt > BCRYPT_VERSION) {
+ /* How do I handle errors ? Return ':' */
+ return ERROR;
+ }
+
+ /* Check for minor versions */
+ if (salt[1] != '$') {
+ switch (salt[1]) {
+ case 'a':
+ /* 'ab' should not yield the same as 'abab' */
+ minor = salt[1];
+ salt++;
+ break;
+ default:
+ return ERROR;
+ }
+ } else
+ minor = 0;
+
+ /* Discard version + "$" identifier */
+ salt += 2;
+
+ if (salt[2] != '$')
+ /* Out of sync with passwd entry */
+ return ERROR;
+
+ /* Computer power doesn't increase linear, 2^x should be fine */
+ n = atoi(salt);
+ if (n > 31 || n < 0)
+ return ERROR;
+ logr = (u_int8_t)n;
+ if ((rounds = (u_int32_t) 1 << logr) < BCRYPT_MINROUNDS)
+ return ERROR;
+
+ /* Discard num rounds + "$" identifier */
+ salt += 3;
+
+ if (strlen(salt) * 3 / 4 < BCRYPT_MAXSALT)
+ return ERROR;
+
+ /* We dont want the base64 salt but the raw data */
+ decode_base64(csalt, BCRYPT_MAXSALT, (u_int8_t *) salt);
+ salt_len = BCRYPT_MAXSALT;
+ key_len = strlen(key) + (minor >= 'a' ? 1 : 0);
+
+ /* Setting up S-Boxes and Subkeys */
+ Blowfish_initstate(&state);
+ Blowfish_expandstate(&state, csalt, salt_len,
+ (u_int8_t *) key, key_len);
+ for (k = 0; k < rounds; k++) {
+ Blowfish_expand0state(&state, (u_int8_t *) key, key_len);
+ Blowfish_expand0state(&state, csalt, salt_len);
+ }
+
+ /* This can be precomputed later */
+ j = 0;
+ for (i = 0; i < BCRYPT_BLOCKS; i++)
+ cdata[i] = Blowfish_stream2word(ciphertext, 4 * BCRYPT_BLOCKS, &j);
+
+ /* Now do the encryption */
+ for (k = 0; k < 64; k++)
+ blf_enc(&state, cdata, BCRYPT_BLOCKS / 2);
+
+ for (i = 0; i < BCRYPT_BLOCKS; i++) {
+ ciphertext[4 * i + 3] = cdata[i] & 0xff;
+ cdata[i] = cdata[i] >> 8;
+ ciphertext[4 * i + 2] = cdata[i] & 0xff;
+ cdata[i] = cdata[i] >> 8;
+ ciphertext[4 * i + 1] = cdata[i] & 0xff;
+ cdata[i] = cdata[i] >> 8;
+ ciphertext[4 * i + 0] = cdata[i] & 0xff;
+ }
+
+
+ i = 0;
+ encrypted[i++] = '$';
+ encrypted[i++] = BCRYPT_VERSION;
+ if (minor)
+ encrypted[i++] = minor;
+ encrypted[i++] = '$';
+
+ snprintf(encrypted + i, 4, "%2.2u$", logr);
+
+ encode_base64((u_int8_t *) encrypted + i + 3, csalt, BCRYPT_MAXSALT);
+ encode_base64((u_int8_t *) encrypted + strlen(encrypted), ciphertext,
+ 4 * BCRYPT_BLOCKS - 1);
+ memset(&state, 0, sizeof(state));
+ memset(ciphertext, 0, sizeof(ciphertext));
+ memset(csalt, 0, sizeof(csalt));
+ memset(cdata, 0, sizeof(cdata));
+ return 0;
+}
+
+static void
+encode_base64(u_int8_t *buffer, u_int8_t *data, u_int16_t len)
+{
+ u_int8_t *bp = buffer;
+ u_int8_t *p = data;
+ u_int8_t c1, c2;
+ while (p < data + len) {
+ c1 = *p++;
+ *bp++ = Base64Code[(c1 >> 2)];
+ c1 = (c1 & 0x03) << 4;
+ if (p >= data + len) {
+ *bp++ = Base64Code[c1];
+ break;
+ }
+ c2 = *p++;
+ c1 |= (c2 >> 4) & 0x0f;
+ *bp++ = Base64Code[c1];
+ c1 = (c2 & 0x0f) << 2;
+ if (p >= data + len) {
+ *bp++ = Base64Code[c1];
+ break;
+ }
+ c2 = *p++;
+ c1 |= (c2 >> 6) & 0x03;
+ *bp++ = Base64Code[c1];
+ *bp++ = Base64Code[c2 & 0x3f];
+ }
+ *bp = '\0';
+}
diff --git a/server/_build/default/lib/bcrypt/c_src/bcrypt.o b/server/_build/default/lib/bcrypt/c_src/bcrypt.o
new file mode 100644
index 0000000..15974d8
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/bcrypt.o
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/c_src/bcrypt_nif.c b/server/_build/default/lib/bcrypt/c_src/bcrypt_nif.c
new file mode 100644
index 0000000..fd3492a
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/bcrypt_nif.c
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2011-2012 Hunter Morris <hunter.morris@smarkets.com>
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "erl_nif.h"
+#include "erl_blf.h"
+#include "bcrypt_nif.h"
+
+static void free_task(task_t* task)
+{
+ if (task->env != NULL)
+ enif_free_env(task->env);
+ enif_free(task);
+}
+
+static task_t* alloc_task(task_type_t type)
+{
+ task_t* task = (task_t*)enif_alloc(sizeof(task_t));
+ if (task == NULL)
+ return NULL;
+ (void)memset(task, 0, sizeof(task_t));
+ task->type = type;
+ return task;
+}
+
+static task_t* alloc_init_task(task_type_t type, ERL_NIF_TERM ref, ErlNifPid pid, int num_orig_terms, const ERL_NIF_TERM orig_terms[])
+{
+ task_t* task = alloc_task(type);
+ task->pid = pid;
+ task->env = enif_alloc_env();
+ if (task->env == NULL) {
+ free_task(task);
+ return NULL;
+ }
+
+ if (type == HASH) {
+ assert(num_orig_terms == 2);
+ if (!enif_inspect_iolist_as_binary(
+ task->env, enif_make_copy(task->env, orig_terms[0]),
+ &task->data.hash.salt)) {
+ free_task(task);
+ return NULL;
+ }
+ if (!enif_inspect_iolist_as_binary(
+ task->env, enif_make_copy(task->env, orig_terms[1]),
+ &task->data.hash.password)) {
+ free_task(task);
+ return NULL;
+ }
+ }
+
+ task->ref = enif_make_copy(task->env, ref);
+ return task;
+}
+
+static ERL_NIF_TERM hashpw(task_t* task)
+{
+ char password[1024] = { 0 };
+ char salt[1024] = { 0 };
+ char encrypted[1024] = { 0 };
+
+ size_t password_sz = 1024;
+ if (password_sz > task->data.hash.password.size)
+ password_sz = task->data.hash.password.size;
+ (void)memcpy(&password, task->data.hash.password.data, password_sz);
+
+ size_t salt_sz = 1024;
+ if (salt_sz > task->data.hash.salt.size)
+ salt_sz = task->data.hash.salt.size;
+ (void)memcpy(&salt, task->data.hash.salt.data, salt_sz);
+
+ if (ts_bcrypt(encrypted, password, salt)) {
+ return enif_make_tuple3(
+ task->env,
+ enif_make_atom(task->env, "error"),
+ task->ref,
+ enif_make_string(task->env, "bcrypt failed", ERL_NIF_LATIN1));
+ }
+
+ return enif_make_tuple3(
+ task->env,
+ enif_make_atom(task->env, "ok"),
+ task->ref,
+ enif_make_string(task->env, encrypted, ERL_NIF_LATIN1));
+}
+
+static void* async_worker(void* arg)
+{
+ ctx_t* ctx;
+ task_t* task;
+
+ ERL_NIF_TERM result;
+
+ ctx = (ctx_t*)arg;
+
+ while (1) {
+ task = (task_t*)async_queue_pop(ctx->queue);
+
+ if (task->type == SHUTDOWN) {
+ free_task(task);
+ break;
+ } else if (task->type == HASH) {
+ result = hashpw(task);
+ } else {
+ errx(1, "Unexpected task type: %i", task->type);
+ }
+
+ enif_send(NULL, &task->pid, task->env, result);
+ free_task(task);
+ }
+
+ return NULL;
+}
+
+static ERL_NIF_TERM bcrypt_encode_salt(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ErlNifBinary csalt, bin;
+ unsigned long log_rounds;
+ ERL_NIF_TERM ret;
+
+ if (!enif_inspect_binary(env, argv[0], &csalt) || 16 != csalt.size) {
+ return enif_make_badarg(env);
+ }
+
+ if (!enif_get_ulong(env, argv[1], &log_rounds)) {
+ enif_release_binary(&csalt);
+ return enif_make_badarg(env);
+ }
+
+ if (!enif_alloc_binary(64, &bin)) {
+ enif_release_binary(&csalt);
+ return enif_make_badarg(env);
+ }
+
+ encode_salt((char *)bin.data, (u_int8_t*)csalt.data, csalt.size, log_rounds);
+ enif_release_binary(&csalt);
+
+ ret = enif_make_string(env, (char *)bin.data, ERL_NIF_LATIN1);
+ enif_release_binary(&bin);
+ return ret;
+}
+
+static ERL_NIF_TERM bcrypt_hashpw(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ctx_t *ctx;
+ task_t *task;
+ ErlNifPid pid;
+
+ if (argc != 5)
+ return enif_make_badarg(env);
+
+ bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_priv_data(env);
+
+ if (!enif_get_resource(env, argv[0], priv->bcrypt_rt, (void**)(&ctx)))
+ return enif_make_badarg(env);
+
+ if (!enif_is_ref(env, argv[1]))
+ return enif_make_badarg(env);
+
+ if (!enif_get_local_pid(env, argv[2], &pid))
+ return enif_make_badarg(env);
+
+ ERL_NIF_TERM orig_terms[] = { argv[4], argv[3] };
+ task = alloc_init_task(HASH, argv[1], pid, 2, orig_terms);
+
+ if (!task)
+ return enif_make_badarg(env);
+
+ async_queue_push(ctx->queue, task);
+
+ return enif_make_atom(env, "ok");
+}
+
+static ERL_NIF_TERM bcrypt_create_ctx(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
+{
+ ERL_NIF_TERM ret;
+ bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_priv_data(env);
+ ctx_t* ctx = (ctx_t*)enif_alloc_resource(priv->bcrypt_rt, sizeof(ctx_t));
+ if (ctx == NULL)
+ return enif_make_badarg(env);
+ ctx->queue = async_queue_create("bcrypt_queue_mutex", "bcrypt_queue_condvar");
+ ctx->topts = enif_thread_opts_create("bcrypt_thread_opts");
+ if (enif_thread_create("bcrypt_worker", &ctx->tid, async_worker, ctx, ctx->topts) != 0) {
+ enif_release_resource(ctx);
+ return enif_make_badarg(env);
+ }
+ ret = enif_make_resource(env, ctx);
+ enif_release_resource(ctx);
+ return ret;
+}
+
+static ErlNifFunc bcrypt_nif_funcs[] =
+{
+ {"encode_salt", 2, bcrypt_encode_salt},
+ {"hashpw", 5, bcrypt_hashpw},
+ {"create_ctx", 0, bcrypt_create_ctx},
+};
+
+static void bcrypt_rt_dtor(ErlNifEnv* env, void* obj)
+{
+ ctx_t *ctx = (ctx_t*)obj;
+ task_t *task = alloc_task(SHUTDOWN);
+ void *result = NULL;
+
+ async_queue_push(ctx->queue, task);
+ enif_thread_join(ctx->tid, &result);
+ async_queue_destroy(ctx->queue);
+ enif_thread_opts_destroy(ctx->topts);
+}
+
+static int on_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
+{
+ const char *mod = "bcrypt_nif";
+ const char *name = "nif_resource";
+
+ ErlNifResourceFlags flags = (ErlNifResourceFlags)(ERL_NIF_RT_CREATE | ERL_NIF_RT_TAKEOVER);
+
+ bcrypt_privdata_t *priv = (bcrypt_privdata_t*)enif_alloc(sizeof(bcrypt_privdata_t));
+ priv->bcrypt_rt = enif_open_resource_type(env, mod, name, bcrypt_rt_dtor, flags, NULL);
+ if (priv->bcrypt_rt == NULL)
+ return -1;
+ *priv_data = priv;
+ return 0;
+}
+
+ERL_NIF_INIT(bcrypt_nif, bcrypt_nif_funcs, &on_load, NULL, NULL, NULL)
diff --git a/server/_build/default/lib/bcrypt/c_src/bcrypt_nif.h b/server/_build/default/lib/bcrypt/c_src/bcrypt_nif.h
new file mode 100644
index 0000000..9c1cd69
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/bcrypt_nif.h
@@ -0,0 +1,40 @@
+#ifndef ERLANG_BCRYPT_BCRYPT_NIF_H
+#define ERLANG_BCRYPT_BCRYPT_NIF_H
+
+#include "async_queue.h"
+
+typedef unsigned char byte;
+
+int ts_bcrypt(char *, const char *, const char *);
+void encode_salt(char *, u_int8_t *, u_int16_t, u_int8_t);
+
+typedef struct {
+ ErlNifResourceType *bcrypt_rt;
+} bcrypt_privdata_t;
+
+typedef struct {
+ async_queue_t *queue;
+ ErlNifThreadOpts *topts;
+ ErlNifTid tid;
+} ctx_t;
+
+typedef enum {
+ UNKNOWN,
+ SHUTDOWN,
+ HASH
+} task_type_t;
+
+typedef struct {
+ task_type_t type;
+ ErlNifEnv *env;
+ ErlNifPid pid;
+ ERL_NIF_TERM ref;
+ union {
+ struct {
+ ErlNifBinary salt;
+ ErlNifBinary password;
+ } hash;
+ } data;
+} task_t;
+
+#endif // ERLANG_BCRYPT_BCRYPT_NIF_H
diff --git a/server/_build/default/lib/bcrypt/c_src/bcrypt_nif.o b/server/_build/default/lib/bcrypt/c_src/bcrypt_nif.o
new file mode 100644
index 0000000..64cab20
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/bcrypt_nif.o
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/c_src/bcrypt_port.c b/server/_build/default/lib/bcrypt/c_src/bcrypt_port.c
new file mode 100644
index 0000000..7cafb2b
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/bcrypt_port.c
@@ -0,0 +1,266 @@
+/*
+ * Copyright (c) 2011 Hunter Morris <hunter.morris@smarkets.com>
+ *
+ * Permission to use, copy, modify, and 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "erl_blf.h"
+#include "ei.h"
+
+#define dec_int16(s) ((((unsigned char*)(s))[0] << 8) | \
+ (((unsigned char*)(s))[1]))
+
+#define BUFSIZE (1 << 16)
+#define CMD_SALT 0
+#define CMD_HASHPW 1
+
+#define DATASIZE 1024
+
+extern int ts_bcrypt(char *, const char *, const char *);
+extern void encode_salt(char *, u_int8_t *, u_int16_t, u_int8_t);
+
+
+static void
+fail(int place) {
+ fprintf(stderr, "Something went wrong %d\n", place);
+ exit(1);
+}
+
+/* These methods came from the Erlang port command tutorial:
+ * http://www.erlang.org/doc/tutorial/c_port.html#4.2
+ */
+static int
+read_buf(int fd, char *buf, int len)
+{
+ int i, got = 0;
+ do {
+ if ((i = read(fd, buf+got, len-got)) <= 0) {
+ if (i == 0) return got;
+ if (errno != EINTR)
+ fail(-1);
+ i = 0;
+ }
+ got += i;
+ } while (got < len);
+ return (len);
+}
+
+static int
+read_cmd(char *buf)
+{
+ int len;
+ if (read_buf(0, buf, 2) != 2)
+ return 0;
+ len = dec_int16(buf);
+ if (read_buf(0, buf, len) != len)
+ return 0;
+ return 1;
+}
+
+static void
+write_buf(int fd, const char *buf, int len)
+{
+ int i;
+ int done = 0;
+
+ do {
+ if ((i = write(fd, buf+done, len-done)) < 0) {
+ if (errno != EINTR)
+ fail(-2);
+ i = 0;
+ }
+ done += i;
+ } while (done < len);
+}
+
+static void
+write_cmd(const char *buf, int len)
+{
+ unsigned char li;
+
+ li = (len >> 8) & 0xff;
+ write_buf(1, (char *) &li, 1);
+ li = len & 0xff;
+ write_buf(1, (char *) &li, 1);
+ write_buf(1, buf, len);
+}
+
+static void
+process_reply(int cmd, const char *result)
+{
+ ei_x_buff res_buf;
+
+ if (ei_x_new_with_version(&res_buf) != 0)
+ fail(-10);
+ if (ei_x_encode_tuple_header(&res_buf, 2) != 0)
+ fail(-11);
+ if (ei_x_encode_long(&res_buf, (long) cmd) != 0)
+ fail(-12);
+ if (ei_x_encode_binary(&res_buf, result, (long) strlen( (const char *) result)) != 0)
+ fail(-13);
+
+ write_cmd(res_buf.buff, res_buf.index);
+
+ if (ei_x_free(&res_buf) != 0)
+ fail(-14);
+}
+
+static void
+process_error_reply(int cmd, const char *error)
+{
+ ei_x_buff res_buf;
+
+ if (ei_x_new_with_version(&res_buf) != 0)
+ fail(-20);
+ if (ei_x_encode_tuple_header(&res_buf, 2) != 0)
+ fail(-21);
+ if (ei_x_encode_long(&res_buf, (long) cmd) != 0)
+ fail(-22);
+ if (ei_x_encode_atom(&res_buf, error) != 0)
+ fail(-23);
+
+ write_cmd(res_buf.buff, res_buf.index);
+
+ if (ei_x_free(&res_buf) != 0)
+ fail(-24);
+}
+
+static void
+process_encode_salt(
+ int salt_size,
+ char *salt,
+ long rounds)
+{
+ char encoded_salt[64];
+
+ if (16 != salt_size) {
+ process_error_reply(CMD_SALT, "invalid_salt_length");
+ } else if (rounds < 4 || rounds > 31) {
+ process_error_reply(CMD_SALT, "invalid_rounds");
+ } else {
+ memset(encoded_salt, 0, 64);
+ encode_salt(encoded_salt, (u_int8_t *) salt, (u_int16_t) salt_size, (u_int8_t) rounds);
+ process_reply(CMD_SALT, encoded_salt);
+ }
+}
+
+static void
+process_hashpw(
+ int password_size,
+ char *password,
+ int salt_size,
+ char *salt)
+{
+ char encrypted[DATASIZE+1];
+
+ memset(encrypted, 0, DATASIZE+1);
+ if (ts_bcrypt(encrypted, password, salt)) {
+ process_error_reply(CMD_HASHPW, "invalid_salt");
+ } else {
+ process_reply(CMD_HASHPW, encrypted);
+ }
+}
+
+static void
+process_command(char *buf)
+{
+ int index = 0;
+ int version = 0;
+ int arity = 0;
+ int type;
+ long cmd;
+ long len;
+ long rounds;
+ int data_size;
+ char data[DATASIZE+1];
+ int salt_size;
+ char salt[DATASIZE+1];
+
+ memset(data, 0, DATASIZE+1);
+ memset(salt, 0, DATASIZE+1);
+
+ if (ei_decode_version(buf, &index, &version) != 0)
+ fail(1);
+
+ // Three tuple: {Cmd, Port, Data}
+ if (ei_decode_tuple_header(buf, &index, &arity) != 0)
+ fail(2);
+ if (arity != 2)
+ fail(3);
+ if (ei_decode_long(buf, &index, &cmd) != 0)
+ fail(4);
+
+ // All commands have a two tuple for Data
+ if (ei_decode_tuple_header(buf, &index, &arity) != 0)
+ fail(6);
+ if (arity != 2)
+ fail(7);
+
+ // First arg is always a binary
+ if (ei_get_type(buf, &index, &type, &data_size) != 0)
+ fail(8);
+ if (type != ERL_BINARY_EXT)
+ fail(9);
+ if (data_size < 0 || data_size > DATASIZE)
+ fail(10);
+ if (ei_decode_binary(buf, &index, data, &len) != 0)
+ fail(11);
+
+ switch (cmd) {
+ case CMD_HASHPW:
+ // Two tuple: {Pass, Salt}
+ if (ei_get_type(buf, &index, &type, &salt_size) != 0)
+ fail(12);
+ if (type != ERL_BINARY_EXT)
+ fail(13);
+ if (salt_size < 0 || salt_size > DATASIZE)
+ fail(14);
+ if (ei_decode_binary(buf, &index, salt, &len) != 0)
+ fail(15);
+
+ process_hashpw(data_size, data, salt_size, salt);
+ break;
+ case CMD_SALT:
+ // Two tuple: {Csalt, LogRounds}
+ if (ei_decode_long(buf, &index, &rounds) != 0)
+ fail(16);
+
+ process_encode_salt(data_size, data, rounds);
+ break;
+ default:
+ fail(17);
+ }
+}
+
+static void
+loop(void)
+{
+ char buf[BUFSIZE];
+
+ while (read_cmd(buf) == 1) {
+ process_command(buf);
+ }
+}
+
+int
+main(int argc, char *argv[])
+{
+ ei_init();
+ loop();
+ return 0;
+}
diff --git a/server/_build/default/lib/bcrypt/c_src/bcrypt_port.o b/server/_build/default/lib/bcrypt/c_src/bcrypt_port.o
new file mode 100644
index 0000000..9abcc81
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/bcrypt_port.o
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/c_src/blowfish.c b/server/_build/default/lib/bcrypt/c_src/blowfish.c
new file mode 100644
index 0000000..f78055c
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/blowfish.c
@@ -0,0 +1,686 @@
+/* $OpenBSD: blowfish.c,v 1.18 2004/11/02 17:23:26 hshoexer Exp $ */
+/*
+ * Blowfish block cipher for OpenBSD
+ * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
+ * All rights reserved.
+ *
+ * Implementation advice by David Mazieres <dm@lcs.mit.edu>.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Niels Provos.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * This code is derived from section 14.3 and the given source
+ * in section V of Applied Cryptography, second edition.
+ * Blowfish is an unpatented fast block cipher designed by
+ * Bruce Schneier.
+ */
+
+#if 0
+#include <stdio.h> /* used for debugging */
+#include <string.h>
+#endif
+
+#include <sys/types.h>
+
+#include "erl_blf.h"
+
+#undef inline
+#ifdef __GNUC__
+#define inline __inline
+#else /* !__GNUC__ */
+#define inline
+#endif /* !__GNUC__ */
+
+/* Function for Feistel Networks */
+
+#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \
+ + (s)[0x100 + (((x)>>16)&0xFF)]) \
+ ^ (s)[0x200 + (((x)>> 8)&0xFF)]) \
+ + (s)[0x300 + ( (x) &0xFF)])
+
+#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n])
+
+void
+Blowfish_encipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr)
+{
+ u_int32_t Xl;
+ u_int32_t Xr;
+ u_int32_t *s = c->S[0];
+ u_int32_t *p = c->P;
+
+ Xl = *xl;
+ Xr = *xr;
+
+ Xl ^= p[0];
+ BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2);
+ BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4);
+ BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6);
+ BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8);
+ BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10);
+ BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12);
+ BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14);
+ BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16);
+
+ *xl = Xr ^ p[17];
+ *xr = Xl;
+}
+
+void
+Blowfish_decipher(blf_ctx *c, u_int32_t *xl, u_int32_t *xr)
+{
+ u_int32_t Xl;
+ u_int32_t Xr;
+ u_int32_t *s = c->S[0];
+ u_int32_t *p = c->P;
+
+ Xl = *xl;
+ Xr = *xr;
+
+ Xl ^= p[17];
+ BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15);
+ BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13);
+ BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11);
+ BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9);
+ BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7);
+ BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5);
+ BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3);
+ BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1);
+
+ *xl = Xr ^ p[0];
+ *xr = Xl;
+}
+
+void
+Blowfish_initstate(blf_ctx *c)
+{
+ /* P-box and S-box tables initialized with digits of Pi */
+
+ static const blf_ctx initstate =
+ { {
+ {
+ 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7,
+ 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99,
+ 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16,
+ 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e,
+ 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee,
+ 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013,
+ 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef,
+ 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e,
+ 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60,
+ 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440,
+ 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce,
+ 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a,
+ 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e,
+ 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677,
+ 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193,
+ 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032,
+ 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88,
+ 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239,
+ 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e,
+ 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0,
+ 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3,
+ 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98,
+ 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88,
+ 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe,
+ 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6,
+ 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d,
+ 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b,
+ 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7,
+ 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba,
+ 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463,
+ 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f,
+ 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09,
+ 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3,
+ 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb,
+ 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279,
+ 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8,
+ 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab,
+ 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82,
+ 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db,
+ 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573,
+ 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0,
+ 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b,
+ 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790,
+ 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8,
+ 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4,
+ 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0,
+ 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7,
+ 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c,
+ 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad,
+ 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1,
+ 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299,
+ 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9,
+ 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477,
+ 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf,
+ 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49,
+ 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af,
+ 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa,
+ 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5,
+ 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41,
+ 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915,
+ 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400,
+ 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915,
+ 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664,
+ 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a},
+ {
+ 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623,
+ 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266,
+ 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1,
+ 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e,
+ 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6,
+ 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1,
+ 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e,
+ 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1,
+ 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737,
+ 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8,
+ 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff,
+ 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd,
+ 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701,
+ 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7,
+ 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41,
+ 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331,
+ 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf,
+ 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af,
+ 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e,
+ 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87,
+ 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c,
+ 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2,
+ 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16,
+ 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd,
+ 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b,
+ 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509,
+ 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e,
+ 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3,
+ 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f,
+ 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a,
+ 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4,
+ 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960,
+ 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66,
+ 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28,
+ 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802,
+ 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84,
+ 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510,
+ 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf,
+ 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14,
+ 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e,
+ 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50,
+ 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7,
+ 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8,
+ 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281,
+ 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99,
+ 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696,
+ 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128,
+ 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73,
+ 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0,
+ 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0,
+ 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105,
+ 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250,
+ 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3,
+ 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285,
+ 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00,
+ 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061,
+ 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb,
+ 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e,
+ 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735,
+ 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc,
+ 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9,
+ 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340,
+ 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20,
+ 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7},
+ {
+ 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934,
+ 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068,
+ 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af,
+ 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840,
+ 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45,
+ 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504,
+ 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a,
+ 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb,
+ 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee,
+ 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6,
+ 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42,
+ 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b,
+ 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2,
+ 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb,
+ 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527,
+ 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b,
+ 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33,
+ 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c,
+ 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3,
+ 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc,
+ 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17,
+ 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564,
+ 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b,
+ 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115,
+ 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922,
+ 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728,
+ 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0,
+ 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e,
+ 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37,
+ 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d,
+ 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804,
+ 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b,
+ 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3,
+ 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb,
+ 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d,
+ 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c,
+ 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350,
+ 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9,
+ 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a,
+ 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe,
+ 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d,
+ 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc,
+ 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f,
+ 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61,
+ 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2,
+ 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9,
+ 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2,
+ 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c,
+ 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e,
+ 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633,
+ 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10,
+ 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169,
+ 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52,
+ 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027,
+ 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5,
+ 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62,
+ 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634,
+ 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76,
+ 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24,
+ 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc,
+ 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4,
+ 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c,
+ 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837,
+ 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0},
+ {
+ 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b,
+ 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe,
+ 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b,
+ 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4,
+ 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8,
+ 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6,
+ 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304,
+ 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22,
+ 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4,
+ 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6,
+ 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9,
+ 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59,
+ 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593,
+ 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51,
+ 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28,
+ 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c,
+ 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b,
+ 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28,
+ 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c,
+ 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd,
+ 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a,
+ 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319,
+ 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb,
+ 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f,
+ 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991,
+ 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32,
+ 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680,
+ 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166,
+ 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae,
+ 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb,
+ 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5,
+ 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47,
+ 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370,
+ 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d,
+ 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84,
+ 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048,
+ 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8,
+ 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd,
+ 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9,
+ 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7,
+ 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38,
+ 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f,
+ 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c,
+ 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525,
+ 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1,
+ 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442,
+ 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964,
+ 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e,
+ 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8,
+ 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d,
+ 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f,
+ 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299,
+ 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02,
+ 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc,
+ 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614,
+ 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a,
+ 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6,
+ 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b,
+ 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0,
+ 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060,
+ 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e,
+ 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9,
+ 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f,
+ 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6}
+ },
+ {
+ 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344,
+ 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89,
+ 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c,
+ 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917,
+ 0x9216d5d9, 0x8979fb1b
+ } };
+
+ *c = initstate;
+}
+
+u_int32_t
+Blowfish_stream2word(const u_int8_t *data, u_int16_t databytes,
+ u_int16_t *current)
+{
+ u_int8_t i;
+ u_int16_t j;
+ u_int32_t temp;
+
+ temp = 0x00000000;
+ j = *current;
+
+ for (i = 0; i < 4; i++, j++) {
+ if (j >= databytes)
+ j = 0;
+ temp = (temp << 8) | data[j];
+ }
+
+ *current = j;
+ return temp;
+}
+
+void
+Blowfish_expand0state(blf_ctx *c, const u_int8_t *key, u_int16_t keybytes)
+{
+ u_int16_t i;
+ u_int16_t j;
+ u_int16_t k;
+ u_int32_t temp;
+ u_int32_t datal;
+ u_int32_t datar;
+
+ j = 0;
+ for (i = 0; i < BLF_N + 2; i++) {
+ /* Extract 4 int8 to 1 int32 from keystream */
+ temp = Blowfish_stream2word(key, keybytes, &j);
+ c->P[i] = c->P[i] ^ temp;
+ }
+
+ j = 0;
+ datal = 0x00000000;
+ datar = 0x00000000;
+ for (i = 0; i < BLF_N + 2; i += 2) {
+ Blowfish_encipher(c, &datal, &datar);
+
+ c->P[i] = datal;
+ c->P[i + 1] = datar;
+ }
+
+ for (i = 0; i < 4; i++) {
+ for (k = 0; k < 256; k += 2) {
+ Blowfish_encipher(c, &datal, &datar);
+
+ c->S[i][k] = datal;
+ c->S[i][k + 1] = datar;
+ }
+ }
+}
+
+
+void
+Blowfish_expandstate(blf_ctx *c, const u_int8_t *data, u_int16_t databytes,
+ const u_int8_t *key, u_int16_t keybytes)
+{
+ u_int16_t i;
+ u_int16_t j;
+ u_int16_t k;
+ u_int32_t temp;
+ u_int32_t datal;
+ u_int32_t datar;
+
+ j = 0;
+ for (i = 0; i < BLF_N + 2; i++) {
+ /* Extract 4 int8 to 1 int32 from keystream */
+ temp = Blowfish_stream2word(key, keybytes, &j);
+ c->P[i] = c->P[i] ^ temp;
+ }
+
+ j = 0;
+ datal = 0x00000000;
+ datar = 0x00000000;
+ for (i = 0; i < BLF_N + 2; i += 2) {
+ datal ^= Blowfish_stream2word(data, databytes, &j);
+ datar ^= Blowfish_stream2word(data, databytes, &j);
+ Blowfish_encipher(c, &datal, &datar);
+
+ c->P[i] = datal;
+ c->P[i + 1] = datar;
+ }
+
+ for (i = 0; i < 4; i++) {
+ for (k = 0; k < 256; k += 2) {
+ datal ^= Blowfish_stream2word(data, databytes, &j);
+ datar ^= Blowfish_stream2word(data, databytes, &j);
+ Blowfish_encipher(c, &datal, &datar);
+
+ c->S[i][k] = datal;
+ c->S[i][k + 1] = datar;
+ }
+ }
+
+}
+
+void
+blf_key(blf_ctx *c, const u_int8_t *k, u_int16_t len)
+{
+ /* Initialize S-boxes and subkeys with Pi */
+ Blowfish_initstate(c);
+
+ /* Transform S-boxes and subkeys with key */
+ Blowfish_expand0state(c, k, len);
+}
+
+void
+blf_enc(blf_ctx *c, u_int32_t *data, u_int16_t blocks)
+{
+ u_int32_t *d;
+ u_int16_t i;
+
+ d = data;
+ for (i = 0; i < blocks; i++) {
+ Blowfish_encipher(c, d, d + 1);
+ d += 2;
+ }
+}
+
+void
+blf_dec(blf_ctx *c, u_int32_t *data, u_int16_t blocks)
+{
+ u_int32_t *d;
+ u_int16_t i;
+
+ d = data;
+ for (i = 0; i < blocks; i++) {
+ Blowfish_decipher(c, d, d + 1);
+ d += 2;
+ }
+}
+
+void
+blf_ecb_encrypt(blf_ctx *c, u_int8_t *data, u_int32_t len)
+{
+ u_int32_t l, r;
+ u_int32_t i;
+
+ for (i = 0; i < len; i += 8) {
+ l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
+ Blowfish_encipher(c, &l, &r);
+ data[0] = l >> 24 & 0xff;
+ data[1] = l >> 16 & 0xff;
+ data[2] = l >> 8 & 0xff;
+ data[3] = l & 0xff;
+ data[4] = r >> 24 & 0xff;
+ data[5] = r >> 16 & 0xff;
+ data[6] = r >> 8 & 0xff;
+ data[7] = r & 0xff;
+ data += 8;
+ }
+}
+
+void
+blf_ecb_decrypt(blf_ctx *c, u_int8_t *data, u_int32_t len)
+{
+ u_int32_t l, r;
+ u_int32_t i;
+
+ for (i = 0; i < len; i += 8) {
+ l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
+ Blowfish_decipher(c, &l, &r);
+ data[0] = l >> 24 & 0xff;
+ data[1] = l >> 16 & 0xff;
+ data[2] = l >> 8 & 0xff;
+ data[3] = l & 0xff;
+ data[4] = r >> 24 & 0xff;
+ data[5] = r >> 16 & 0xff;
+ data[6] = r >> 8 & 0xff;
+ data[7] = r & 0xff;
+ data += 8;
+ }
+}
+
+void
+blf_cbc_encrypt(blf_ctx *c, u_int8_t *iv, u_int8_t *data, u_int32_t len)
+{
+ u_int32_t l, r;
+ u_int32_t i, j;
+
+ for (i = 0; i < len; i += 8) {
+ for (j = 0; j < 8; j++)
+ data[j] ^= iv[j];
+ l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
+ Blowfish_encipher(c, &l, &r);
+ data[0] = l >> 24 & 0xff;
+ data[1] = l >> 16 & 0xff;
+ data[2] = l >> 8 & 0xff;
+ data[3] = l & 0xff;
+ data[4] = r >> 24 & 0xff;
+ data[5] = r >> 16 & 0xff;
+ data[6] = r >> 8 & 0xff;
+ data[7] = r & 0xff;
+ iv = data;
+ data += 8;
+ }
+}
+
+void
+blf_cbc_decrypt(blf_ctx *c, u_int8_t *iva, u_int8_t *data, u_int32_t len)
+{
+ u_int32_t l, r;
+ u_int8_t *iv;
+ u_int32_t i, j;
+
+ iv = data + len - 16;
+ data = data + len - 8;
+ for (i = len - 8; i >= 8; i -= 8) {
+ l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
+ Blowfish_decipher(c, &l, &r);
+ data[0] = l >> 24 & 0xff;
+ data[1] = l >> 16 & 0xff;
+ data[2] = l >> 8 & 0xff;
+ data[3] = l & 0xff;
+ data[4] = r >> 24 & 0xff;
+ data[5] = r >> 16 & 0xff;
+ data[6] = r >> 8 & 0xff;
+ data[7] = r & 0xff;
+ for (j = 0; j < 8; j++)
+ data[j] ^= iv[j];
+ iv -= 8;
+ data -= 8;
+ }
+ l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3];
+ r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7];
+ Blowfish_decipher(c, &l, &r);
+ data[0] = l >> 24 & 0xff;
+ data[1] = l >> 16 & 0xff;
+ data[2] = l >> 8 & 0xff;
+ data[3] = l & 0xff;
+ data[4] = r >> 24 & 0xff;
+ data[5] = r >> 16 & 0xff;
+ data[6] = r >> 8 & 0xff;
+ data[7] = r & 0xff;
+ for (j = 0; j < 8; j++)
+ data[j] ^= iva[j];
+}
+
+#if 0
+void
+report(u_int32_t data[], u_int16_t len)
+{
+ u_int16_t i;
+ for (i = 0; i < len; i += 2)
+ printf("Block %0hd: %08lx %08lx.\n",
+ i / 2, data[i], data[i + 1]);
+}
+void
+main(void)
+{
+
+ blf_ctx c;
+ char key[] = "AAAAA";
+ char key2[] = "abcdefghijklmnopqrstuvwxyz";
+
+ u_int32_t data[10];
+ u_int32_t data2[] =
+ {0x424c4f57l, 0x46495348l};
+
+ u_int16_t i;
+
+ /* First test */
+ for (i = 0; i < 10; i++)
+ data[i] = i;
+
+ blf_key(&c, (u_int8_t *) key, 5);
+ blf_enc(&c, data, 5);
+ blf_dec(&c, data, 1);
+ blf_dec(&c, data + 2, 4);
+ printf("Should read as 0 - 9.\n");
+ report(data, 10);
+
+ /* Second test */
+ blf_key(&c, (u_int8_t *) key2, strlen(key2));
+ blf_enc(&c, data2, 1);
+ printf("\nShould read as: 0x324ed0fe 0xf413a203.\n");
+ report(data2, 2);
+ blf_dec(&c, data2, 1);
+ report(data2, 2);
+}
+#endif
diff --git a/server/_build/default/lib/bcrypt/c_src/blowfish.o b/server/_build/default/lib/bcrypt/c_src/blowfish.o
new file mode 100644
index 0000000..6a9aba6
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/blowfish.o
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/c_src/erl_blf.h b/server/_build/default/lib/bcrypt/c_src/erl_blf.h
new file mode 100644
index 0000000..b03a3a0
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/c_src/erl_blf.h
@@ -0,0 +1,94 @@
+/* $OpenBSD: blf.h,v 1.7 2007/03/14 17:59:41 grunk Exp $ */
+/*
+ * Blowfish - a fast block cipher designed by Bruce Schneier
+ *
+ * Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. All advertising materials mentioning features or use of this software
+ * must display the following acknowledgement:
+ * This product includes software developed by Niels Provos.
+ * 4. The name of the author may not be used to endorse or promote products
+ * derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef _ERL_BLF_H_
+#define _ERL_BLF_H_
+
+/* Solaris compatibility */
+#ifdef __sun
+#define u_int8_t uint8_t
+#define u_int16_t uint16_t
+#define u_int32_t uint32_t
+#define u_int64_t uint64_t
+#endif
+
+#include <sys/types.h>
+
+/* Schneier specifies a maximum key length of 56 bytes.
+ * This ensures that every key bit affects every cipher
+ * bit. However, the subkeys can hold up to 72 bytes.
+ * Warning: For normal blowfish encryption only 56 bytes
+ * of the key affect all cipherbits.
+ */
+
+#define BLF_N 16 /* Number of Subkeys */
+#define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */
+#define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */
+
+#define _PASSWORD_LEN 128 /* max length, not counting NUL */
+
+/* Blowfish context */
+typedef struct BlowfishContext {
+ u_int32_t S[4][256]; /* S-Boxes */
+ u_int32_t P[BLF_N + 2]; /* Subkeys */
+} blf_ctx;
+
+/* Raw access to customized Blowfish
+ * blf_key is just:
+ * Blowfish_initstate( state )
+ * Blowfish_expand0state( state, key, keylen )
+ */
+
+void Blowfish_encipher(blf_ctx *, u_int32_t *, u_int32_t *);
+void Blowfish_decipher(blf_ctx *, u_int32_t *, u_int32_t *);
+void Blowfish_initstate(blf_ctx *);
+void Blowfish_expand0state(blf_ctx *, const u_int8_t *, u_int16_t);
+void Blowfish_expandstate
+(blf_ctx *, const u_int8_t *, u_int16_t, const u_int8_t *, u_int16_t);
+
+/* Standard Blowfish */
+
+void blf_key(blf_ctx *, const u_int8_t *, u_int16_t);
+void blf_enc(blf_ctx *, u_int32_t *, u_int16_t);
+void blf_dec(blf_ctx *, u_int32_t *, u_int16_t);
+
+void blf_ecb_encrypt(blf_ctx *, u_int8_t *, u_int32_t);
+void blf_ecb_decrypt(blf_ctx *, u_int8_t *, u_int32_t);
+
+void blf_cbc_encrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t);
+void blf_cbc_decrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t);
+
+/* Converts u_int8_t to u_int32_t */
+u_int32_t Blowfish_stream2word(const u_int8_t *, u_int16_t , u_int16_t *);
+
+#endif
diff --git a/server/_build/default/lib/bcrypt/ebin/bcrypt.app b/server/_build/default/lib/bcrypt/ebin/bcrypt.app
new file mode 100644
index 0000000..48bb861
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/ebin/bcrypt.app
@@ -0,0 +1,18 @@
+{application,bcrypt,
+ [{description,"An Erlang wrapper (NIF or port program) for the OpenBSD password scheme, bcrypt."},
+ {vsn,"1.2.0"},
+ {registered,[bcrypt_sup,bcrypt_port_sup,bcrypt_pool]},
+ {mod,{bcrypt_app,[]}},
+ {applications,[kernel,stdlib,crypto,poolboy]},
+ {env,[{default_log_rounds,12},
+ {mechanism,nif},
+ {pool_size,4},
+ {nif_pool_size,4},
+ {nif_pool_max_overflow,10}]},
+ {exclude_files,["priv/bcrypt"]},
+ {maintainers,["Hunter Morris","Mrinal Wadhwa","ErlangPack"]},
+ {licenses,["MIT"]},
+ {links,[{"Github","https://github.com/erlangpack/bcrypt"}]},
+ {modules,[bcrypt,bcrypt_app,bcrypt_nif,bcrypt_nif_pool_sup,
+ bcrypt_nif_worker,bcrypt_pool,bcrypt_port,
+ bcrypt_port_sup,bcrypt_sup]}]}.
diff --git a/server/_build/default/lib/bcrypt/ebin/bcrypt.beam b/server/_build/default/lib/bcrypt/ebin/bcrypt.beam
new file mode 100644
index 0000000..9331a98
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/ebin/bcrypt.beam
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/ebin/bcrypt_app.beam b/server/_build/default/lib/bcrypt/ebin/bcrypt_app.beam
new file mode 100644
index 0000000..4fcd74f
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/ebin/bcrypt_app.beam
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/ebin/bcrypt_nif.beam b/server/_build/default/lib/bcrypt/ebin/bcrypt_nif.beam
new file mode 100644
index 0000000..86b1bf3
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/ebin/bcrypt_nif.beam
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/ebin/bcrypt_nif_pool_sup.beam b/server/_build/default/lib/bcrypt/ebin/bcrypt_nif_pool_sup.beam
new file mode 100644
index 0000000..f66ce73
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/ebin/bcrypt_nif_pool_sup.beam
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/ebin/bcrypt_nif_worker.beam b/server/_build/default/lib/bcrypt/ebin/bcrypt_nif_worker.beam
new file mode 100644
index 0000000..0b120f8
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/ebin/bcrypt_nif_worker.beam
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/ebin/bcrypt_pool.beam b/server/_build/default/lib/bcrypt/ebin/bcrypt_pool.beam
new file mode 100644
index 0000000..7637e75
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/ebin/bcrypt_pool.beam
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/ebin/bcrypt_port.beam b/server/_build/default/lib/bcrypt/ebin/bcrypt_port.beam
new file mode 100644
index 0000000..5509406
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/ebin/bcrypt_port.beam
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/ebin/bcrypt_port_sup.beam b/server/_build/default/lib/bcrypt/ebin/bcrypt_port_sup.beam
new file mode 100644
index 0000000..b262153
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/ebin/bcrypt_port_sup.beam
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/ebin/bcrypt_sup.beam b/server/_build/default/lib/bcrypt/ebin/bcrypt_sup.beam
new file mode 100644
index 0000000..b4b1b7d
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/ebin/bcrypt_sup.beam
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/hex_metadata.config b/server/_build/default/lib/bcrypt/hex_metadata.config
new file mode 100644
index 0000000..50d2c35
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/hex_metadata.config
@@ -0,0 +1,24 @@
+{<<"app">>,<<"bcrypt">>}.
+{<<"build_tools">>,[<<"rebar3">>]}.
+{<<"description">>,
+ <<"An Erlang wrapper (NIF or port program) for the OpenBSD password scheme, bcrypt.">>}.
+{<<"files">>,
+ [<<"LICENSE">>,<<"README.md">>,<<"c_src">>,<<"c_src/Makefile">>,
+ <<"c_src/async_queue.c">>,<<"c_src/async_queue.h">>,<<"c_src/bcrypt.c">>,
+ <<"c_src/bcrypt_nif.c">>,<<"c_src/bcrypt_nif.h">>,<<"c_src/bcrypt_port.c">>,
+ <<"c_src/blowfish.c">>,<<"c_src/erl_blf.h">>,<<"priv">>,
+ <<"priv/.gitignore">>,<<"rebar.config">>,<<"rebar.lock">>,<<"src">>,
+ <<"src/bcrypt.app.src">>,<<"src/bcrypt.erl">>,<<"src/bcrypt_app.erl">>,
+ <<"src/bcrypt_nif.erl">>,<<"src/bcrypt_nif_pool_sup.erl">>,
+ <<"src/bcrypt_nif_worker.erl">>,<<"src/bcrypt_pool.erl">>,
+ <<"src/bcrypt_port.erl">>,<<"src/bcrypt_port_sup.erl">>,
+ <<"src/bcrypt_sup.erl">>]}.
+{<<"licenses">>,[<<"MIT">>]}.
+{<<"links">>,[{<<"Github">>,<<"https://github.com/erlangpack/bcrypt">>}]}.
+{<<"name">>,<<"bcrypt">>}.
+{<<"requirements">>,
+ [{<<"poolboy">>,
+ [{<<"app">>,<<"poolboy">>},
+ {<<"optional">>,false},
+ {<<"requirement">>,<<"1.5.2">>}]}]}.
+{<<"version">>,<<"1.2.0">>}.
diff --git a/server/_build/default/lib/bcrypt/priv/.gitignore b/server/_build/default/lib/bcrypt/priv/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/priv/.gitignore
diff --git a/server/_build/default/lib/bcrypt/priv/bcrypt b/server/_build/default/lib/bcrypt/priv/bcrypt
new file mode 100755
index 0000000..0c3f3a4
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/priv/bcrypt
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/priv/bcrypt_nif.so b/server/_build/default/lib/bcrypt/priv/bcrypt_nif.so
new file mode 100755
index 0000000..131b212
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/priv/bcrypt_nif.so
Binary files differ
diff --git a/server/_build/default/lib/bcrypt/rebar.config b/server/_build/default/lib/bcrypt/rebar.config
new file mode 100644
index 0000000..6d00b4f
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/rebar.config
@@ -0,0 +1,62 @@
+%% -*- mode: erlang;erlang-indent-level: 2;indent-tabs-mode: nil -*-
+
+{require_min_otp_vsn, "21.3"}.
+
+{erl_opts,
+ [debug_info]}.
+
+{pre_hooks,
+ [{"(linux|darwin|solaris)", compile, "make -C c_src"},
+ {"(freebsd|openbsd)", compile, "gmake -C c_src"}]}.
+
+{post_hooks,
+ [{"(linux|darwin|solaris)", clean, "make -C c_src clean"},
+ {"(freebsd|openbsd)", clean, "gmake -C c_src clean"}]}.
+{deps, [
+ {poolboy, "1.5.2"}
+]}.
+
+{profiles, [
+ {test, [
+ {xref_checks, [
+ undefined_function_calls,
+ locals_not_used,
+ deprecated_function_calls
+ ]},
+
+ {xref_ignores, [
+ ]},
+
+ {dialyzer, [
+ {warnings, [
+ no_return
+ ]}
+ ]},
+ {plugins, [
+ rebar3_proper
+ ]},
+ {deps, [{proper,"1.4.0"}]}
+ ]},
+ {edoc_private, [
+ {edoc_opts, [
+ {private, true}
+ ]}
+ ]}
+]}.
+
+{project_plugins, [rebar3_hex, rebar3_ex_doc]}.
+
+{hex, [
+ {doc, #{provider => ex_doc}}
+]}.
+
+{ex_doc, [
+ {extras, [
+ {"README.md", #{title => "Overview"}},
+ {"LICENSE", #{title => "License"}}
+ ]},
+ {main, "README.md"},
+ {source_url, "https://github.com/erlangpack/bcrypt"},
+ {assets, "assets"},
+ {api_reference, true}
+]}.
diff --git a/server/_build/default/lib/bcrypt/rebar.lock b/server/_build/default/lib/bcrypt/rebar.lock
new file mode 100644
index 0000000..1ab8da9
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/rebar.lock
@@ -0,0 +1,8 @@
+{"1.2.0",
+[{<<"poolboy">>,{pkg,<<"poolboy">>,<<"1.5.2">>},0}]}.
+[
+{pkg_hash,[
+ {<<"poolboy">>, <<"392B007A1693A64540CEAD79830443ABF5762F5D30CF50BC95CB2C1AAAFA006B">>}]},
+{pkg_hash_ext,[
+ {<<"poolboy">>, <<"DAD79704CE5440F3D5A3681C8590B9DC25D1A561E8F5A9C995281012860901E3">>}]}
+].
diff --git a/server/_build/default/lib/bcrypt/src/bcrypt.app.src b/server/_build/default/lib/bcrypt/src/bcrypt.app.src
new file mode 100644
index 0000000..d834377
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/src/bcrypt.app.src
@@ -0,0 +1,15 @@
+{application,bcrypt,
+ [{description,"An Erlang wrapper (NIF or port program) for the OpenBSD password scheme, bcrypt."},
+ {vsn,"1.2.0"},
+ {registered,[bcrypt_sup,bcrypt_port_sup,bcrypt_pool]},
+ {mod,{bcrypt_app,[]}},
+ {applications,[kernel,stdlib,crypto,poolboy]},
+ {env,[{default_log_rounds,12},
+ {mechanism,nif},
+ {pool_size,4},
+ {nif_pool_size,4},
+ {nif_pool_max_overflow,10}]},
+ {exclude_files,["priv/bcrypt"]},
+ {maintainers,["Hunter Morris","Mrinal Wadhwa","ErlangPack"]},
+ {licenses,["MIT"]},
+ {links,[{"Github","https://github.com/erlangpack/bcrypt"}]}]}.
diff --git a/server/_build/default/lib/bcrypt/src/bcrypt.erl b/server/_build/default/lib/bcrypt/src/bcrypt.erl
new file mode 100644
index 0000000..f1997c2
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/src/bcrypt.erl
@@ -0,0 +1,91 @@
+%% Copyright (c) 2011 Hunter Morris
+%% Distributed under the MIT license; see LICENSE for details.
+%% @doc The OpenBSD Blowfish password hashing algorithm wrapper module.
+-module(bcrypt).
+-author('Hunter Morris <hunter.morris@smarkets.com>').
+
+%% API
+-export([start/0, stop/0]).
+-export([mechanism/0]).
+-export([gen_salt/0, gen_salt/1, hashpw/2]).
+
+-type mechanism() :: nif | port.
+-type rounds() :: 4..31.
+-type pwerr() :: invalid_salt | invalid_salt_length | invalid_rounds.
+
+-export_type([ mechanism/0, rounds/0, pwerr/0 ]).
+
+%% @doc Starts `Application' `bcrypt'.
+%% <b>See also:</b>
+%% [http://erlang.org/doc/man/application.html#start-1 application:start/1].
+
+start() -> application:start(bcrypt).
+
+%% @doc Stops `Application' `bcrypt'.
+%% <b>See also:</b>
+%% [http://erlang.org/doc/man/application.html#stop-1 application:stop/1].
+
+stop() -> application:stop(bcrypt).
+
+%% @doc Get environment setting of hash generation.
+
+-spec mechanism() -> mechanism().
+mechanism() ->
+ {ok, M} = application:get_env(bcrypt, mechanism),
+ M.
+
+%% @doc Returns a random string data.
+
+-spec gen_salt() -> Result when
+ Result :: {ok, Salt},
+ Salt :: [byte()].
+gen_salt() ->
+ do_gen_salt(mechanism()).
+
+%% @doc Generate a random string data.
+
+-spec gen_salt( Rounds ) -> Result when
+ Rounds :: rounds(),
+ Result :: {ok, Salt},
+ Salt :: [byte()].
+gen_salt(Rounds) when is_integer(Rounds), Rounds < 32, Rounds > 3 ->
+ do_gen_salt(mechanism(), Rounds).
+
+%% @doc Make hash string based on `Password' and `Salt'.
+
+-spec hashpw( Password, Salt ) -> Result when
+ Password :: [byte()] | binary(),
+ Salt :: [byte()] | binary(),
+ Result :: {ok, Hash} | {error, ErrorDescription},
+ Hash :: [byte()],
+ ErrorDescription :: pwerr().
+hashpw(Password, Salt) ->
+ do_hashpw(mechanism(), Password, Salt).
+
+%% @private
+
+-spec do_gen_salt(nif | port) -> Result when
+ Result :: {ok, Salt},
+ Salt :: [byte()].
+do_gen_salt(nif) -> bcrypt_nif_worker:gen_salt();
+do_gen_salt(port) -> bcrypt_pool:gen_salt().
+
+%% @private
+
+-spec do_gen_salt(nif | port, Rounds) -> Result when
+ Rounds :: rounds(),
+ Result :: {ok, Salt},
+ Salt :: [byte()].
+do_gen_salt(nif, Rounds) -> bcrypt_nif_worker:gen_salt(Rounds);
+do_gen_salt(port, Rounds) -> bcrypt_pool:gen_salt(Rounds).
+
+%% @private
+
+-spec do_hashpw(nif | port, Password, Salt) -> Result when
+ Password :: [byte()] | binary(),
+ Salt :: [byte()],
+ Result :: {ok, Hash} | {error, ErrorDescription},
+ Hash :: [byte()],
+ ErrorDescription :: pwerr().
+do_hashpw(nif, Password, Salt) -> bcrypt_nif_worker:hashpw(Password, Salt);
+do_hashpw(port, Password, Salt) -> bcrypt_pool:hashpw(Password, Salt).
diff --git a/server/_build/default/lib/bcrypt/src/bcrypt_app.erl b/server/_build/default/lib/bcrypt/src/bcrypt_app.erl
new file mode 100644
index 0000000..d83db7d
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/src/bcrypt_app.erl
@@ -0,0 +1,27 @@
+%% @copyright 2011 Hunter Morris
+%% @doc Implementation of `application' behaviour.
+%% @private
+%% @end
+%% Distributed under the MIT license; see LICENSE for details.
+-module(bcrypt_app).
+-author('Hunter Morris <hunter.morris@smarkets.com>').
+
+-behaviour(application).
+
+-export([start/2, stop/1]).
+
+-spec start(StartType, StartArgs) -> Result when
+ StartType :: normal,
+ StartArgs :: term(),
+ Result :: {ok, pid()} | {error, Reason},
+ Reason :: term().
+start(normal, _Args) ->
+ case bcrypt_sup:start_link() of
+ {ok, Pid} -> {ok, Pid};
+ {error, _} = Error -> Error
+ end.
+
+-spec stop(State) -> Result when
+ State :: term(),
+ Result :: ok.
+stop(_State) -> ok.
diff --git a/server/_build/default/lib/bcrypt/src/bcrypt_nif.erl b/server/_build/default/lib/bcrypt/src/bcrypt_nif.erl
new file mode 100644
index 0000000..348d8df
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/src/bcrypt_nif.erl
@@ -0,0 +1,99 @@
+%% @author Hunter Morris <hunter.morris@smarkets.com>
+%% @copyright 2011 Hunter Morris
+%%
+%% @doc Bcrypt Erlang wrapper. <div>The wrapper around the OpenBSD Blowfish password hashing algorithm, as
+%% described in: [http://www.openbsd.org/papers/bcrypt-paper.ps "A Future-Adaptable Password Scheme"]
+%% by Niels Provos and David Mazieres.</div>
+%% @end
+%%
+%% Permission to use, copy, modify, and 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(bcrypt_nif).
+-author('Hunter Morris <hunter.morris@smarkets.com>').
+
+%% API
+-export([init/0]).
+-export([gen_salt/1, hashpw/5, create_ctx/0]).
+
+-on_load(init/0).
+
+%%--------------------------------------------------------------------
+%% @doc Load the bcrypt NIFs
+%% @private
+%% @end
+%%--------------------------------------------------------------------
+
+-spec init() -> Result when
+ Result :: ok | Error,
+ Error :: {error, {Reason, ErrorText}},
+ Reason :: load_failed | bad_lib | load | reload | upgrade | old_code,
+ ErrorText :: string().
+init() ->
+ Dir = case code:priv_dir(bcrypt) of
+ {error, bad_name} ->
+ case code:which(bcrypt) of
+ Filename when is_list(Filename) ->
+ filename:join(
+ [filename:dirname(Filename), "../priv"]);
+ _ ->
+ "../priv"
+ end;
+ Priv -> Priv
+ end,
+ erlang:load_nif(filename:join(Dir, "bcrypt_nif"), 0).
+
+%%--------------------------------------------------------------------
+%% @doc Generate a random text salt for use with hashpw/3. LogRounds
+%% defines the complexity of the hashing, increasing the cost as
+%% 2^log_rounds.
+%% @end
+%%--------------------------------------------------------------------
+
+-spec gen_salt(LogRounds) -> Result when
+ LogRounds :: integer(),
+ Result :: [byte()].
+gen_salt(LogRounds)
+ when is_integer(LogRounds), LogRounds < 32, LogRounds > 3 ->
+ R = crypto:strong_rand_bytes(16),
+ encode_salt(R, LogRounds).
+
+encode_salt(_R, _LogRounds) ->
+ nif_stub_error(?LINE).
+
+%%--------------------------------------------------------------------
+%% @doc Create a context which hashes passwords in a separate thread.
+%% @end
+%%--------------------------------------------------------------------
+
+-spec create_ctx() -> Context when
+ Context :: term().
+create_ctx() ->
+ nif_stub_error(?LINE).
+
+%%--------------------------------------------------------------------
+%% @doc Hash the specified password and the salt using the OpenBSD
+%% Blowfish password hashing algorithm. Returns the hashed password.
+%% @end
+%%--------------------------------------------------------------------
+
+-spec hashpw(Ctx, Ref, Pid, Password, Salt) -> Result when
+ Ctx :: term(),
+ Ref :: reference(),
+ Pid :: pid(),
+ Password :: [byte()],
+ Salt :: [byte()],
+ Result :: ok.
+hashpw(_Ctx, _Ref, _Pid, _Password, _Salt) ->
+ nif_stub_error(?LINE).
+
+nif_stub_error(Line) ->
+ erlang:nif_error({nif_not_loaded, module, ?MODULE, line, Line}).
diff --git a/server/_build/default/lib/bcrypt/src/bcrypt_nif_pool_sup.erl b/server/_build/default/lib/bcrypt/src/bcrypt_nif_pool_sup.erl
new file mode 100644
index 0000000..dbd5868
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/src/bcrypt_nif_pool_sup.erl
@@ -0,0 +1,50 @@
+%% @copyright 2011 Hunter Morris
+%% @doc Implementation of `supervisor' behaviour.
+%% @private
+%% @end
+%% Distributed under the MIT license; see LICENSE for details.
+-module(bcrypt_nif_pool_sup).
+
+-behaviour(supervisor).
+
+-export([start_link/0, start_child/0, init/1]).
+
+%% @doc Creates a supervisor process as part of a supervision tree.
+
+
+-spec start_link() -> Result when
+ Result :: {ok, pid()} | ignore | {error, StartlinkError},
+ StartlinkError :: {already_started, pid()} | {shutdown, term()} | term().
+start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+%% @doc Dynamically adds a child specification to supervisor,
+%% which starts the corresponding child process.
+
+-spec start_child() -> Result when
+ Info :: term(),
+ Child :: undefined | pid(),
+ Result :: {ok, Child} | {ok, Child, Info} | {error, StartChildError},
+ StartChildError :: already_present | {already_started, Child} | term().
+start_child() -> supervisor:start_child(?MODULE, []).
+
+-spec init(Args) -> Result when
+ Args :: list(),
+ Result :: {ok,{SupFlags, ChildSpec}} | ignore,
+ SupFlags :: {one_for_one, 10, 10},
+ ChildSpec :: [supervisor:child_spec()].
+init([]) ->
+ {ok, PoolSize} = application:get_env(bcrypt, nif_pool_size),
+ {ok, MaxOverFlow} = application:get_env(bcrypt, nif_pool_max_overflow),
+
+ PoolArgs = [
+ {name, {local, bcrypt_nif_pool}},
+ {size, PoolSize},
+ {max_overflow, MaxOverFlow},
+ {worker_module, bcrypt_nif_worker}
+ ],
+
+ PoolSpecs = [
+ poolboy:child_spec(bcrypt_nif_pool, PoolArgs, [])
+ ],
+
+ {ok, {{one_for_one, 10, 10}, PoolSpecs}}. \ No newline at end of file
diff --git a/server/_build/default/lib/bcrypt/src/bcrypt_nif_worker.erl b/server/_build/default/lib/bcrypt/src/bcrypt_nif_worker.erl
new file mode 100644
index 0000000..70b8eed
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/src/bcrypt_nif_worker.erl
@@ -0,0 +1,144 @@
+%% @copyright 2011 Hunter Morris.
+%% @doc Implementation of `gen_server' behaviour.
+%% @end
+%% Distributed under the MIT license; see LICENSE for details.
+-module(bcrypt_nif_worker).
+-author('Hunter Morris <huntermorris@gmail.com>').
+
+-behaviour(gen_server).
+
+-export([start_link/1]).
+-export([gen_salt/0, gen_salt/1]).
+-export([hashpw/2]).
+
+%% gen_server
+-export([init/1, code_change/3, terminate/2,
+ handle_call/3, handle_cast/2, handle_info/2]).
+
+-record(state, {
+ default_log_rounds :: integer(),
+ context :: term()
+ }).
+
+-type state() :: #state{default_log_rounds :: integer(), context :: term()}.
+
+%% @doc Creates a `gen_server' process as part of a supervision tree.
+
+-spec start_link(Args) -> Result when
+ Args :: term(),
+ Result :: {ok,Pid} | ignore | {error,Error},
+ Pid :: pid(),
+ Error :: {already_started,Pid} | term().
+start_link(Args) -> gen_server:start_link(?MODULE, Args, []).
+
+%% @doc Returns bcrypt salt.
+
+-spec gen_salt() -> Result when
+ Result :: [byte()].
+gen_salt() ->
+ poolboy:transaction(bcrypt_nif_pool, fun(Worker) ->
+ gen_server:call(Worker, gen_salt, infinity)
+ end).
+
+%% @doc Returns bcrypt salt.
+
+-spec gen_salt(Rounds) -> Result when
+ Rounds :: bcrypt:rounds(),
+ Result :: [byte()].
+gen_salt(Rounds) ->
+ poolboy:transaction(bcrypt_nif_pool, fun(Worker) ->
+ gen_server:call(Worker, {gen_salt, Rounds}, infinity)
+ end).
+
+%% @doc Make hash string based on `Password' and `Salt'.
+
+-spec hashpw( Password, Salt ) -> Result when
+ Password :: [byte()] | binary(),
+ Salt :: [byte()] | binary(),
+ Result :: {ok, Hash} | {error, ErrorDescription},
+ Hash :: [byte()],
+ ErrorDescription :: bcrypt:pwerr().
+hashpw(Password, Salt) ->
+ poolboy:transaction(bcrypt_nif_pool, fun(Worker) ->
+ gen_server:call(Worker, {hashpw, Password, Salt}, infinity)
+ end).
+
+%% @private
+
+-spec init(Args) -> Result when
+ Args :: list(),
+ Result :: {ok, state()}.
+init([]) ->
+ process_flag(trap_exit, true),
+ {ok, Default} = application:get_env(bcrypt, default_log_rounds),
+ Ctx = bcrypt_nif:create_ctx(),
+ {ok, #state{default_log_rounds = Default, context = Ctx}}.
+
+%% @private
+
+terminate(shutdown, _) -> ok.
+
+%% @private
+
+-spec handle_call(Request, From, State) -> Result when
+ Request :: gen_salt,
+ From :: {pid(), atom()},
+ State :: state(),
+ Result :: {reply, Reply, State},
+ Reply :: {ok, Salt},
+ Salt :: integer();
+(Request, From, State) -> Result when
+ Request :: {gen_salt, Rounds},
+ From :: {pid(), atom()},
+ State :: state(),
+ Rounds :: bcrypt:rounds(),
+ Result :: {reply, Reply, State},
+ Reply :: {ok, Salt},
+ Salt :: integer();
+(Request, From, State) -> Result when
+ Request :: {hashpw, Password, Salt},
+ From :: {pid(), atom()},
+ State :: state(),
+ Password :: [byte()],
+ Salt :: integer(),
+ Result :: {reply, Reply, State} | {reply, Reply, State},
+ Reply :: {ok, ResultInfo} | {error, ResultInfo},
+ ResultInfo :: term().
+
+handle_call(gen_salt, _From, #state{default_log_rounds = R} = State) ->
+ Salt = bcrypt_nif:gen_salt(R),
+ {reply, {ok, Salt}, State};
+handle_call({gen_salt, R}, _From, State) ->
+ Salt = bcrypt_nif:gen_salt(R),
+ {reply, {ok, Salt}, State};
+handle_call({hashpw, Password, Salt}, _From, #state{context=Ctx}=State) ->
+ Ref = make_ref(),
+ ok = bcrypt_nif:hashpw(Ctx, Ref, self(), to_list(Password), to_list(Salt)),
+ receive
+ {ok, Ref, Result} ->
+ {reply, {ok, Result}, State};
+ {error, Ref, Result} ->
+ {reply, {error, Result}, State}
+ end;
+handle_call(Msg, _, _) -> exit({unknown_call, Msg}).
+
+%% @private
+
+handle_cast(Msg, _) -> exit({unknown_cast, Msg}).
+
+%% @private
+
+handle_info(Msg, _) -> exit({unknown_info, Msg}).
+
+%% @private
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+-spec to_list(List) -> Result when
+ List :: [byte()],
+ Result :: [byte()];
+(Binary) -> Result when
+ Binary :: binary(),
+ Result :: [byte()].
+to_list(L) when is_list(L) -> L;
+to_list(B) when is_binary(B) -> binary_to_list(B).
diff --git a/server/_build/default/lib/bcrypt/src/bcrypt_pool.erl b/server/_build/default/lib/bcrypt/src/bcrypt_pool.erl
new file mode 100644
index 0000000..7b6ea91
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/src/bcrypt_pool.erl
@@ -0,0 +1,140 @@
+%% @copyright 2011 Hunter Morris
+%% @doc Implementation of `gen_server' behaviour.
+%% @end
+%% Distributed under the MIT license; see LICENSE for details.
+-module(bcrypt_pool).
+-author('Hunter Morris <huntermorris@gmail.com>').
+
+-behaviour(gen_server).
+
+-export([start_link/0, available/1]).
+-export([gen_salt/0, gen_salt/1]).
+-export([hashpw/2]).
+
+%% gen_server
+-export([init/1, code_change/3, terminate/2,
+ handle_call/3, handle_cast/2, handle_info/2]).
+
+-record(state, {
+ size = 0,
+ busy = 0,
+ requests = queue:new(),
+ ports = queue:new()
+ }).
+
+-record(req, {mon :: reference(), from :: {pid(), atom()}}).
+
+-type state() :: #state{size :: 0, busy :: 0, requests :: queue:queue(), ports :: queue:queue()}.
+
+%% @doc Creates a `gen_server' process as part of a supervision tree.
+
+-spec start_link() -> Result when
+ Result :: {ok,Pid} | ignore | {error,Error},
+ Pid :: pid(),
+ Error :: {already_started,Pid} | term().
+start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+%% @doc Asynchronosly check if `Pid' in `#state:requests' queue or not.
+
+-spec available(Pid) -> Result when
+ Pid :: pid(),
+ Result :: ok.
+available(Pid) -> gen_server:cast(?MODULE, {available, Pid}).
+
+%% @doc Generate a random text salt.
+
+-spec gen_salt() -> Result when
+ Result :: {ok, Salt},
+ Salt :: [byte()].
+gen_salt() -> do_call(fun bcrypt_port:gen_salt/1, []).
+
+%% @doc Generate a random text salt. Rounds defines the complexity of
+%% the hashing, increasing the cost as 2^log_rounds.
+
+-spec gen_salt(Rounds) -> Result when
+ Rounds :: bcrypt:rounds(),
+ Result :: {ok, Salt},
+ Salt :: [byte()].
+gen_salt(Rounds) -> do_call(fun bcrypt_port:gen_salt/2, [Rounds]).
+
+%% @doc Hash the specified password and the salt.
+
+hashpw(Password, Salt) -> do_call(fun bcrypt_port:hashpw/3, [Password, Salt]).
+
+%% @private
+
+-spec init([]) -> Result when
+ Result :: {ok, state()}.
+init([]) ->
+ {ok, Size} = application:get_env(bcrypt, pool_size),
+ {ok, #state{size = Size}}.
+
+%% @private
+
+terminate(shutdown, _) -> ok.
+
+%% @private
+
+-spec handle_call(Request, From, State) -> Result when
+ Request :: request,
+ From :: {RPid, atom()},
+ RPid :: pid(),
+ State :: state(),
+ Result :: {noreply, state()} | {reply, {ok, pid()}, state()}.
+handle_call(request, {RPid, _} = From, #state{ports = P} = State) ->
+ case queue:out(P) of
+ {empty, P} ->
+ #state{size = Size, busy = B, requests = R} = State,
+ B1 =
+ if Size > B ->
+ {ok, _} = bcrypt_port_sup:start_child(),
+ B + 1;
+ true ->
+ B
+ end,
+ RRef = erlang:monitor(process, RPid),
+ R1 = queue:in(#req{mon = RRef, from = From}, R),
+ {noreply, State#state{requests = R1,
+ busy = B1}};
+ {{value, PPid}, P1} ->
+ #state{busy = B} = State,
+ {reply, {ok, PPid}, State#state{busy = B + 1, ports = P1}}
+ end;
+handle_call(Msg, _, _) -> exit({unknown_call, Msg}).
+
+%% @private
+
+-spec handle_cast({available, Pid}, state()) -> Result when
+ Pid :: pid(),
+ Result :: {noreply, state()}.
+handle_cast(
+ {available, Pid},
+ #state{requests = R, ports = P, busy = B} = S) ->
+ case queue:out(R) of
+ {empty, R} ->
+ {noreply, S#state{ports = queue:in(Pid, P), busy = B - 1}};
+ {{value, #req{mon = Mon, from = F}}, R1} ->
+ true = erlang:demonitor(Mon, [flush]),
+ gen_server:reply(F, {ok, Pid}),
+ {noreply, S#state{requests = R1}}
+ end;
+handle_cast(Msg, _) -> exit({unknown_cast, Msg}).
+
+%% @private
+
+handle_info({'DOWN', Ref, process, _Pid, _Reason}, #state{requests = R} = State) ->
+ R1 = queue:from_list(lists:keydelete(Ref, #req.mon, queue:to_list(R))),
+ {noreply, State#state{requests = R1}};
+
+%% @private
+
+handle_info(Msg, _) -> exit({unknown_info, Msg}).
+
+%% @private
+
+code_change(_OldVsn, State, _Extra) -> {ok, State}.
+
+do_call(F, Args0) ->
+ {ok, Pid} = gen_server:call(?MODULE, request, infinity),
+ Args = [Pid|Args0],
+ apply(F, Args).
diff --git a/server/_build/default/lib/bcrypt/src/bcrypt_port.erl b/server/_build/default/lib/bcrypt/src/bcrypt_port.erl
new file mode 100644
index 0000000..6365de6
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/src/bcrypt_port.erl
@@ -0,0 +1,157 @@
+%% @copyright 2011 Hunter Morris
+%% @doc Implementation of `gen_server' behaviour.
+%% @end
+%% Distributed under the MIT license; see LICENSE for details.
+-module(bcrypt_port).
+-author('Hunter Morris <hunter.morris@smarkets.com>').
+
+-behaviour(gen_server).
+
+%% API
+-export([start_link/0, stop/0]).
+-export([gen_salt/1, gen_salt/2]).
+-export([hashpw/3]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {
+ port :: port(),
+ default_log_rounds :: non_neg_integer(),
+ cmd_from :: {pid(), term()} | undefined
+ }).
+
+-type state() :: #state{port :: port(),
+ default_log_rounds :: non_neg_integer(),
+ cmd_from :: {pid(), term()} | undefined}.
+
+-define(CMD_SALT, 0).
+-define(CMD_HASH, 1).
+-define(BCRYPT_ERROR(F, D), error_logger:error_msg(F, D)).
+-define(BCRYPT_WARNING(F, D), error_logger:warning_msg(F, D)).
+
+-spec start_link() -> Result when
+ Result :: {ok,Pid} | ignore | {error,Error},
+ Pid :: pid(),
+ Error :: {already_started,Pid} | term(),
+ Pid :: pid().
+start_link() ->
+ Dir = case code:priv_dir(bcrypt) of
+ {error, bad_name} ->
+ case code:which(bcrypt) of
+ Filename when is_list(Filename) ->
+ filename:join(
+ [filename:dirname(Filename), "../priv"]);
+ _ ->
+ "../priv"
+ end;
+ Priv -> Priv
+ end,
+ Port = filename:join(Dir, "bcrypt"),
+ gen_server:start_link(?MODULE, [Port], []).
+
+-spec stop() -> Result when
+ Result :: {stop, normal, ok, state()}.
+stop() -> gen_server:call(?MODULE, stop).
+
+-spec gen_salt(Pid) -> Result when
+ Pid :: pid(),
+ Result :: {ok, Salt},
+ Salt :: [byte()].
+gen_salt(Pid) ->
+ R = crypto:strong_rand_bytes(16),
+ gen_server:call(Pid, {encode_salt, R}, infinity).
+
+-spec gen_salt(Pid, LogRounds) -> Result when
+ Pid :: pid(),
+ LogRounds :: bcrypt:rounds(),
+ Result :: {ok, Salt},
+ Salt :: [byte()].
+gen_salt(Pid, LogRounds) ->
+ R = crypto:strong_rand_bytes(16),
+ gen_server:call(Pid, {encode_salt, R, LogRounds}, infinity).
+
+-spec hashpw(Pid, Password, Salt) -> Result when
+ Pid :: pid(),
+ Password :: [byte()],
+ Salt :: [byte()],
+ Result :: [byte()].
+hashpw(Pid, Password, Salt) ->
+ gen_server:call(Pid, {hashpw, Password, Salt}, infinity).
+
+%%====================================================================
+%% gen_server callbacks
+%%====================================================================
+%% @private
+
+init([Filename]) ->
+ case file:read_file_info(Filename) of
+ {ok, _Info} ->
+ Port = open_port(
+ {spawn, Filename}, [{packet, 2}, binary, exit_status]),
+ ok = bcrypt_pool:available(self()),
+ {ok, Rounds} = application:get_env(bcrypt, default_log_rounds),
+ {ok, #state{port = Port, default_log_rounds = Rounds}};
+ {error, Reason} ->
+ ?BCRYPT_ERROR("Can't open file ~p: ~p", [Filename, Reason]),
+ {stop, error_opening_bcrypt_file}
+ end.
+
+%% @private
+
+terminate(_Reason, #state{port=Port}) ->
+ catch port_close(Port),
+ ok.
+
+%% @private
+
+handle_call({encode_salt, R}, From, #state{default_log_rounds = LogRounds} = State) ->
+ handle_call({encode_salt, R, LogRounds}, From, State);
+handle_call({encode_salt, R, LogRounds}, From, #state{ cmd_from = undefined } = State) ->
+ Port = State#state.port,
+ Data = term_to_binary({?CMD_SALT, {iolist_to_binary(R), LogRounds}}),
+ erlang:port_command(Port, Data),
+ {noreply, State#state{ cmd_from = From }};
+handle_call({encode_salt, _R, _Rounds}, From, #state{ cmd_from = CmdFrom } = State) ->
+ ?BCRYPT_ERROR("bcrypt: Salt request from ~p whilst busy for ~p", [ From, CmdFrom ]),
+ {reply, {error, {busy, From}}, State};
+handle_call({hashpw, Password, Salt}, From, #state{ cmd_from = undefined } = State) ->
+ Port = State#state.port,
+ Data = term_to_binary({?CMD_HASH, {iolist_to_binary(Password), iolist_to_binary(Salt)}}),
+ erlang:port_command(Port, Data),
+ {noreply, State#state{ cmd_from = From }};
+handle_call({hashpw, _Password, _Salt}, From, #state{ cmd_from = CmdFrom } = State) ->
+ ?BCRYPT_ERROR("bcrypt: Hash request from ~p whilst busy for ~p", [ From, CmdFrom ]),
+ {reply, {error, {busy, From}}, State};
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State};
+handle_call(Msg, _, State) ->
+ {stop, {unknown_call, Msg}, State}.
+
+handle_cast(Msg, State) ->
+ {stop, {unknown_cast, Msg}, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+handle_info({Port, {data, Data}}, #state{ port = Port, cmd_from = From } = State) ->
+ Reply =
+ case binary_to_term(Data) of
+ {_, Error} when is_atom(Error) ->
+ {error, Error};
+ {?CMD_SALT, Result} when is_binary(Result) ->
+ {ok, binary_to_list(Result)};
+ {?CMD_HASH, Result} when is_binary(Result) ->
+ {ok, binary_to_list(Result)}
+ end,
+ gen_server:reply(From, Reply),
+ ok = bcrypt_pool:available(self()),
+ {noreply, State#state{ cmd_from = undefined }};
+handle_info({Port, {exit_status, Status}}, #state{port=Port}=State) ->
+ %% Rely on whomever is supervising this process to restart.
+ ?BCRYPT_WARNING("Port died: ~p", [Status]),
+ {stop, port_died, State};
+handle_info(Msg, _) ->
+ exit({unknown_info, Msg}).
+
diff --git a/server/_build/default/lib/bcrypt/src/bcrypt_port_sup.erl b/server/_build/default/lib/bcrypt/src/bcrypt_port_sup.erl
new file mode 100644
index 0000000..89fe33d
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/src/bcrypt_port_sup.erl
@@ -0,0 +1,21 @@
+%% @copyright 2011 Hunter Morris
+%% @doc Implementation of `supervisor' behaviour.
+%% @private
+%% @end
+%% Distributed under the MIT license; see LICENSE for details.
+-module(bcrypt_port_sup).
+-author('Hunter Morris <huntermorris@gmail.com>').
+
+-behaviour(supervisor).
+
+-export([start_link/0, start_child/0, init/1]).
+
+start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+start_child() -> supervisor:start_child(?MODULE, []).
+
+init([]) ->
+ {ok, {{simple_one_for_one, 1, 1},
+ [{undefined,
+ {bcrypt_port, start_link, []},
+ transient, brutal_kill, worker, [bcrypt_port]}]}}.
diff --git a/server/_build/default/lib/bcrypt/src/bcrypt_sup.erl b/server/_build/default/lib/bcrypt/src/bcrypt_sup.erl
new file mode 100644
index 0000000..502a6a3
--- /dev/null
+++ b/server/_build/default/lib/bcrypt/src/bcrypt_sup.erl
@@ -0,0 +1,28 @@
+%% @copyright 2011 Hunter Morris
+%% @doc Implementation of `supervisor' behaviour.
+%% @private
+%% @end
+%% Distributed under the MIT license; see LICENSE for details.
+-module(bcrypt_sup).
+-author('Hunter Morris <huntermorris@gmail.com>').
+
+-behaviour(supervisor).
+
+-export([start_link/0, init/1]).
+
+start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ PortChildren
+ = [{bcrypt_port_sup, {bcrypt_port_sup, start_link, []}, permanent,
+ 16#ffffffff, supervisor, [bcrypt_port_sup]},
+ {bcrypt_pool, {bcrypt_pool, start_link, []}, permanent,
+ 16#ffffffff, worker, [bcrypt_pool]}],
+ NifChildren
+ = [{bcrypt_nif_pool_sup, {bcrypt_nif_pool_sup, start_link, []}, permanent,
+ 16#ffffffff, supervisor, [bcrypt_nif_pool_sup]}],
+ case application:get_env(bcrypt, mechanism) of
+ undefined -> {stop, no_mechanism_defined};
+ {ok, nif} -> {ok, {{one_for_all, 15, 60}, NifChildren}};
+ {ok, port} -> {ok, {{one_for_all, 15, 60}, PortChildren}}
+ end.
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}.
diff --git a/server/_build/default/lib/cowlib/LICENSE b/server/_build/default/lib/cowlib/LICENSE
new file mode 100644
index 0000000..4f91160
--- /dev/null
+++ b/server/_build/default/lib/cowlib/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted, provided that the above
+copyright notice and this permission notice appear in all copies.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
diff --git a/server/_build/default/lib/cowlib/Makefile b/server/_build/default/lib/cowlib/Makefile
new file mode 100644
index 0000000..0c17f95
--- /dev/null
+++ b/server/_build/default/lib/cowlib/Makefile
@@ -0,0 +1,119 @@
+# See LICENSE for licensing information.
+
+PROJECT = cowlib
+PROJECT_DESCRIPTION = Support library for manipulating Web protocols.
+PROJECT_VERSION = 2.12.1
+
+# Options.
+
+#ERLC_OPTS += +bin_opt_info
+ifdef HIPE
+ ERLC_OPTS += -smp +native
+ TEST_ERLC_OPTS += -smp +native
+endif
+
+DIALYZER_OPTS = -Werror_handling -Wunmatched_returns
+
+# Dependencies.
+
+LOCAL_DEPS = crypto
+
+DOC_DEPS = asciideck
+
+TEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) base32 horse proper jsx \
+ decimal structured-header-tests uritemplate-tests
+dep_base32 = git https://github.com/dnsimple/base32_erlang main
+dep_horse = git https://github.com/ninenines/horse.git master
+dep_jsx = git https://github.com/talentdeficit/jsx v2.10.0
+dep_decimal = git https://github.com/egobrain/decimal 0.6.2
+dep_structured-header-tests = git https://github.com/httpwg/structured-header-tests faed1f92942abd4fb5d61b1f9f0dc359f499f1d7
+dep_uritemplate-tests = git https://github.com/uri-templates/uritemplate-test master
+
+# CI configuration.
+
+dep_ci.erlang.mk = git https://github.com/ninenines/ci.erlang.mk master
+DEP_EARLY_PLUGINS = ci.erlang.mk
+
+AUTO_CI_OTP ?= OTP-21+
+AUTO_CI_HIPE ?= OTP-LATEST
+# AUTO_CI_ERLLVM ?= OTP-LATEST
+AUTO_CI_WINDOWS ?= OTP-21+
+
+# Hex configuration.
+
+define HEX_TARBALL_EXTRA_METADATA
+#{
+ licenses => [<<"ISC">>],
+ links => #{
+ <<"Function reference">> => <<"https://ninenines.eu/docs/en/cowlib/2.12/manual/">>,
+ <<"GitHub">> => <<"https://github.com/ninenines/cowlib">>,
+ <<"Sponsor">> => <<"https://github.com/sponsors/essen">>
+ }
+}
+endef
+
+# Standard targets.
+
+include erlang.mk
+
+# Always rebuild from scratch in CI because OTP-25.0+ can't use the older build.
+
+ci-setup:: distclean-deps
+ -$(verbose) rm -rf $(ERLANG_MK_TMP)/rebar
+
+# Compile options.
+
+TEST_ERLC_OPTS += +'{parse_transform, eunit_autoexport}' +'{parse_transform, horse_autoexport}'
+
+# Mimetypes module generator.
+
+GEN_URL = http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
+GEN_SRC = src/cow_mimetypes.erl.src
+GEN_OUT = src/cow_mimetypes.erl
+
+.PHONY: gen
+
+gen:
+ $(gen_verbose) cat $(GEN_SRC) \
+ | head -n `grep -n "%% GENERATED" $(GEN_SRC) | cut -d : -f 1` \
+ > $(GEN_OUT)
+ $(gen_verbose) wget -qO - $(GEN_URL) \
+ | grep -v ^# \
+ | awk '{for (i=2; i<=NF; i++) if ($$i != "") { \
+ split($$1, a, "/"); \
+ print "all_ext(<<\"" $$i "\">>) -> {<<\"" \
+ a[1] "\">>, <<\"" a[2] "\">>, []};"}}' \
+ | sort \
+ | uniq -w 25 \
+ >> $(GEN_OUT)
+ $(gen_verbose) cat $(GEN_SRC) \
+ | tail -n +`grep -n "%% GENERATED" $(GEN_SRC) | cut -d : -f 1` \
+ >> $(GEN_OUT)
+
+# Performance testing.
+
+ifeq ($(MAKECMDGOALS),perfs)
+.NOTPARALLEL:
+endif
+
+.PHONY: perfs
+
+perfs: test-build
+ $(gen_verbose) erl -noshell -pa ebin -eval 'horse:app_perf($(PROJECT)), erlang:halt().'
+
+# Prepare for the release.
+
+prepare_tag:
+ $(verbose) $(warning Hex metadata: $(HEX_TARBALL_EXTRA_METADATA))
+ $(verbose) echo
+ $(verbose) echo -n "Most recent tag: "
+ $(verbose) git tag --sort taggerdate | tail -n1
+ $(verbose) git verify-tag `git tag --sort taggerdate | tail -n1`
+ $(verbose) echo -n "MAKEFILE: "
+ $(verbose) grep -m1 PROJECT_VERSION Makefile
+ $(verbose) echo -n "APP: "
+ $(verbose) grep -m1 vsn ebin/$(PROJECT).app | sed 's/ //g'
+ $(verbose) echo
+ $(verbose) echo "Dependencies:"
+ $(verbose) grep ^DEPS Makefile || echo "DEPS ="
+ $(verbose) grep ^dep_ Makefile || true
diff --git a/server/_build/default/lib/cowlib/README.asciidoc b/server/_build/default/lib/cowlib/README.asciidoc
new file mode 100644
index 0000000..949e22d
--- /dev/null
+++ b/server/_build/default/lib/cowlib/README.asciidoc
@@ -0,0 +1,18 @@
+= Cowlib
+
+Cowlib is a support library for manipulating Web protocols.
+
+== Goals
+
+Cowlib provides libraries for parsing and building messages
+for various Web protocols, including HTTP/1.1, HTTP/2 and
+Websocket.
+
+It is optimized for completeness rather than speed. No value
+is ignored, they are all returned.
+
+== Support
+
+* Official IRC Channel: #ninenines on irc.freenode.net
+* https://ninenines.eu/services[Commercial Support]
+* https://github.com/sponsors/essen[Sponsor me!]
diff --git a/server/_build/default/lib/cowlib/ebin/cow_base64url.beam b/server/_build/default/lib/cowlib/ebin/cow_base64url.beam
new file mode 100644
index 0000000..c25dcf1
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_base64url.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_cookie.beam b/server/_build/default/lib/cowlib/ebin/cow_cookie.beam
new file mode 100644
index 0000000..c7d363e
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_cookie.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_date.beam b/server/_build/default/lib/cowlib/ebin/cow_date.beam
new file mode 100644
index 0000000..fa039c6
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_date.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_hpack.beam b/server/_build/default/lib/cowlib/ebin/cow_hpack.beam
new file mode 100644
index 0000000..eb528e3
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_hpack.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_http.beam b/server/_build/default/lib/cowlib/ebin/cow_http.beam
new file mode 100644
index 0000000..6d0f85b
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_http.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_http2.beam b/server/_build/default/lib/cowlib/ebin/cow_http2.beam
new file mode 100644
index 0000000..d2d6317
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_http2.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_http2_machine.beam b/server/_build/default/lib/cowlib/ebin/cow_http2_machine.beam
new file mode 100644
index 0000000..c3236db
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_http2_machine.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_http_hd.beam b/server/_build/default/lib/cowlib/ebin/cow_http_hd.beam
new file mode 100644
index 0000000..db16050
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_http_hd.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_http_struct_hd.beam b/server/_build/default/lib/cowlib/ebin/cow_http_struct_hd.beam
new file mode 100644
index 0000000..934f7d4
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_http_struct_hd.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_http_te.beam b/server/_build/default/lib/cowlib/ebin/cow_http_te.beam
new file mode 100644
index 0000000..95bf4c0
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_http_te.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_iolists.beam b/server/_build/default/lib/cowlib/ebin/cow_iolists.beam
new file mode 100644
index 0000000..fe08de9
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_iolists.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_link.beam b/server/_build/default/lib/cowlib/ebin/cow_link.beam
new file mode 100644
index 0000000..3a1714a
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_link.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_mimetypes.beam b/server/_build/default/lib/cowlib/ebin/cow_mimetypes.beam
new file mode 100644
index 0000000..9b3eefa
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_mimetypes.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_multipart.beam b/server/_build/default/lib/cowlib/ebin/cow_multipart.beam
new file mode 100644
index 0000000..bd021ae
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_multipart.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_qs.beam b/server/_build/default/lib/cowlib/ebin/cow_qs.beam
new file mode 100644
index 0000000..30739e0
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_qs.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_spdy.beam b/server/_build/default/lib/cowlib/ebin/cow_spdy.beam
new file mode 100644
index 0000000..46b65c6
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_spdy.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_sse.beam b/server/_build/default/lib/cowlib/ebin/cow_sse.beam
new file mode 100644
index 0000000..aeaea81
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_sse.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_uri.beam b/server/_build/default/lib/cowlib/ebin/cow_uri.beam
new file mode 100644
index 0000000..8a3e78c
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_uri.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_uri_template.beam b/server/_build/default/lib/cowlib/ebin/cow_uri_template.beam
new file mode 100644
index 0000000..7e4c15b
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_uri_template.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cow_ws.beam b/server/_build/default/lib/cowlib/ebin/cow_ws.beam
new file mode 100644
index 0000000..1fd2589
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cow_ws.beam
Binary files differ
diff --git a/server/_build/default/lib/cowlib/ebin/cowlib.app b/server/_build/default/lib/cowlib/ebin/cowlib.app
new file mode 100644
index 0000000..3d5c5e7
--- /dev/null
+++ b/server/_build/default/lib/cowlib/ebin/cowlib.app
@@ -0,0 +1,8 @@
+{application, 'cowlib', [
+ {description, "Support library for manipulating Web protocols."},
+ {vsn, "2.12.1"},
+ {modules, ['cow_base64url','cow_cookie','cow_date','cow_hpack','cow_http','cow_http2','cow_http2_machine','cow_http_hd','cow_http_struct_hd','cow_http_te','cow_iolists','cow_link','cow_mimetypes','cow_multipart','cow_qs','cow_spdy','cow_sse','cow_uri','cow_uri_template','cow_ws']},
+ {registered, []},
+ {applications, [kernel,stdlib,crypto]},
+ {env, []}
+]}. \ No newline at end of file
diff --git a/server/_build/default/lib/cowlib/erlang.mk b/server/_build/default/lib/cowlib/erlang.mk
new file mode 100644
index 0000000..4044d80
--- /dev/null
+++ b/server/_build/default/lib/cowlib/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 = 6423c1c
+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/cowlib/hex_metadata.config b/server/_build/default/lib/cowlib/hex_metadata.config
new file mode 100644
index 0000000..e237931
--- /dev/null
+++ b/server/_build/default/lib/cowlib/hex_metadata.config
@@ -0,0 +1,25 @@
+{<<"app">>,<<"cowlib">>}.
+{<<"build_tools">>,[<<"make">>,<<"rebar3">>]}.
+{<<"description">>,<<"Support library for manipulating Web protocols.">>}.
+{<<"files">>,
+ [<<"ebin/cowlib.app">>,<<"erlang.mk">>,<<"include/cow_inline.hrl">>,
+ <<"include/cow_parse.hrl">>,<<"LICENSE">>,<<"Makefile">>,
+ <<"README.asciidoc">>,<<"src/cow_base64url.erl">>,<<"src/cow_cookie.erl">>,
+ <<"src/cow_date.erl">>,<<"src/cow_hpack.erl">>,
+ <<"src/cow_hpack_dec_huffman_lookup.hrl">>,<<"src/cow_http.erl">>,
+ <<"src/cow_http2.erl">>,<<"src/cow_http2_machine.erl">>,
+ <<"src/cow_http_hd.erl">>,<<"src/cow_http_struct_hd.erl">>,
+ <<"src/cow_http_te.erl">>,<<"src/cow_iolists.erl">>,<<"src/cow_link.erl">>,
+ <<"src/cow_mimetypes.erl">>,<<"src/cow_mimetypes.erl.src">>,
+ <<"src/cow_multipart.erl">>,<<"src/cow_qs.erl">>,<<"src/cow_spdy.erl">>,
+ <<"src/cow_spdy.hrl">>,<<"src/cow_sse.erl">>,<<"src/cow_uri.erl">>,
+ <<"src/cow_uri_template.erl">>,<<"src/cow_ws.erl">>]}.
+{<<"licenses">>,[<<"ISC">>]}.
+{<<"links">>,
+ [{<<"Function reference">>,
+ <<"https://ninenines.eu/docs/en/cowlib/2.12/manual/">>},
+ {<<"GitHub">>,<<"https://github.com/ninenines/cowlib">>},
+ {<<"Sponsor">>,<<"https://github.com/sponsors/essen">>}]}.
+{<<"name">>,<<"cowlib">>}.
+{<<"requirements">>,[]}.
+{<<"version">>,<<"2.12.1">>}.
diff --git a/server/_build/default/lib/cowlib/include/cow_inline.hrl b/server/_build/default/lib/cowlib/include/cow_inline.hrl
new file mode 100644
index 0000000..1ad417e
--- /dev/null
+++ b/server/_build/default/lib/cowlib/include/cow_inline.hrl
@@ -0,0 +1,447 @@
+%% Copyright (c) 2014-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-ifndef(COW_INLINE_HRL).
+-define(COW_INLINE_HRL, 1).
+
+%% LC(Character)
+
+-define(LC(C), case C of
+ $A -> $a;
+ $B -> $b;
+ $C -> $c;
+ $D -> $d;
+ $E -> $e;
+ $F -> $f;
+ $G -> $g;
+ $H -> $h;
+ $I -> $i;
+ $J -> $j;
+ $K -> $k;
+ $L -> $l;
+ $M -> $m;
+ $N -> $n;
+ $O -> $o;
+ $P -> $p;
+ $Q -> $q;
+ $R -> $r;
+ $S -> $s;
+ $T -> $t;
+ $U -> $u;
+ $V -> $v;
+ $W -> $w;
+ $X -> $x;
+ $Y -> $y;
+ $Z -> $z;
+ _ -> C
+end).
+
+%% LOWER(Bin)
+%%
+%% Lowercase the entire binary string in a binary comprehension.
+
+-define(LOWER(Bin), << << ?LC(C) >> || << C >> <= Bin >>).
+
+%% LOWERCASE(Function, Rest, Acc, ...)
+%%
+%% To be included at the end of a case block.
+%% Defined for up to 10 extra arguments.
+
+-define(LOWER(Function, Rest, Acc), case C of
+ $A -> Function(Rest, << Acc/binary, $a >>);
+ $B -> Function(Rest, << Acc/binary, $b >>);
+ $C -> Function(Rest, << Acc/binary, $c >>);
+ $D -> Function(Rest, << Acc/binary, $d >>);
+ $E -> Function(Rest, << Acc/binary, $e >>);
+ $F -> Function(Rest, << Acc/binary, $f >>);
+ $G -> Function(Rest, << Acc/binary, $g >>);
+ $H -> Function(Rest, << Acc/binary, $h >>);
+ $I -> Function(Rest, << Acc/binary, $i >>);
+ $J -> Function(Rest, << Acc/binary, $j >>);
+ $K -> Function(Rest, << Acc/binary, $k >>);
+ $L -> Function(Rest, << Acc/binary, $l >>);
+ $M -> Function(Rest, << Acc/binary, $m >>);
+ $N -> Function(Rest, << Acc/binary, $n >>);
+ $O -> Function(Rest, << Acc/binary, $o >>);
+ $P -> Function(Rest, << Acc/binary, $p >>);
+ $Q -> Function(Rest, << Acc/binary, $q >>);
+ $R -> Function(Rest, << Acc/binary, $r >>);
+ $S -> Function(Rest, << Acc/binary, $s >>);
+ $T -> Function(Rest, << Acc/binary, $t >>);
+ $U -> Function(Rest, << Acc/binary, $u >>);
+ $V -> Function(Rest, << Acc/binary, $v >>);
+ $W -> Function(Rest, << Acc/binary, $w >>);
+ $X -> Function(Rest, << Acc/binary, $x >>);
+ $Y -> Function(Rest, << Acc/binary, $y >>);
+ $Z -> Function(Rest, << Acc/binary, $z >>);
+ C -> Function(Rest, << Acc/binary, C >>)
+end).
+
+-define(LOWER(Function, Rest, A0, Acc), case C of
+ $A -> Function(Rest, A0, << Acc/binary, $a >>);
+ $B -> Function(Rest, A0, << Acc/binary, $b >>);
+ $C -> Function(Rest, A0, << Acc/binary, $c >>);
+ $D -> Function(Rest, A0, << Acc/binary, $d >>);
+ $E -> Function(Rest, A0, << Acc/binary, $e >>);
+ $F -> Function(Rest, A0, << Acc/binary, $f >>);
+ $G -> Function(Rest, A0, << Acc/binary, $g >>);
+ $H -> Function(Rest, A0, << Acc/binary, $h >>);
+ $I -> Function(Rest, A0, << Acc/binary, $i >>);
+ $J -> Function(Rest, A0, << Acc/binary, $j >>);
+ $K -> Function(Rest, A0, << Acc/binary, $k >>);
+ $L -> Function(Rest, A0, << Acc/binary, $l >>);
+ $M -> Function(Rest, A0, << Acc/binary, $m >>);
+ $N -> Function(Rest, A0, << Acc/binary, $n >>);
+ $O -> Function(Rest, A0, << Acc/binary, $o >>);
+ $P -> Function(Rest, A0, << Acc/binary, $p >>);
+ $Q -> Function(Rest, A0, << Acc/binary, $q >>);
+ $R -> Function(Rest, A0, << Acc/binary, $r >>);
+ $S -> Function(Rest, A0, << Acc/binary, $s >>);
+ $T -> Function(Rest, A0, << Acc/binary, $t >>);
+ $U -> Function(Rest, A0, << Acc/binary, $u >>);
+ $V -> Function(Rest, A0, << Acc/binary, $v >>);
+ $W -> Function(Rest, A0, << Acc/binary, $w >>);
+ $X -> Function(Rest, A0, << Acc/binary, $x >>);
+ $Y -> Function(Rest, A0, << Acc/binary, $y >>);
+ $Z -> Function(Rest, A0, << Acc/binary, $z >>);
+ C -> Function(Rest, A0, << Acc/binary, C >>)
+end).
+
+-define(LOWER(Function, Rest, A0, A1, Acc), case C of
+ $A -> Function(Rest, A0, A1, << Acc/binary, $a >>);
+ $B -> Function(Rest, A0, A1, << Acc/binary, $b >>);
+ $C -> Function(Rest, A0, A1, << Acc/binary, $c >>);
+ $D -> Function(Rest, A0, A1, << Acc/binary, $d >>);
+ $E -> Function(Rest, A0, A1, << Acc/binary, $e >>);
+ $F -> Function(Rest, A0, A1, << Acc/binary, $f >>);
+ $G -> Function(Rest, A0, A1, << Acc/binary, $g >>);
+ $H -> Function(Rest, A0, A1, << Acc/binary, $h >>);
+ $I -> Function(Rest, A0, A1, << Acc/binary, $i >>);
+ $J -> Function(Rest, A0, A1, << Acc/binary, $j >>);
+ $K -> Function(Rest, A0, A1, << Acc/binary, $k >>);
+ $L -> Function(Rest, A0, A1, << Acc/binary, $l >>);
+ $M -> Function(Rest, A0, A1, << Acc/binary, $m >>);
+ $N -> Function(Rest, A0, A1, << Acc/binary, $n >>);
+ $O -> Function(Rest, A0, A1, << Acc/binary, $o >>);
+ $P -> Function(Rest, A0, A1, << Acc/binary, $p >>);
+ $Q -> Function(Rest, A0, A1, << Acc/binary, $q >>);
+ $R -> Function(Rest, A0, A1, << Acc/binary, $r >>);
+ $S -> Function(Rest, A0, A1, << Acc/binary, $s >>);
+ $T -> Function(Rest, A0, A1, << Acc/binary, $t >>);
+ $U -> Function(Rest, A0, A1, << Acc/binary, $u >>);
+ $V -> Function(Rest, A0, A1, << Acc/binary, $v >>);
+ $W -> Function(Rest, A0, A1, << Acc/binary, $w >>);
+ $X -> Function(Rest, A0, A1, << Acc/binary, $x >>);
+ $Y -> Function(Rest, A0, A1, << Acc/binary, $y >>);
+ $Z -> Function(Rest, A0, A1, << Acc/binary, $z >>);
+ C -> Function(Rest, A0, A1, << Acc/binary, C >>)
+end).
+
+-define(LOWER(Function, Rest, A0, A1, A2, Acc), case C of
+ $A -> Function(Rest, A0, A1, A2, << Acc/binary, $a >>);
+ $B -> Function(Rest, A0, A1, A2, << Acc/binary, $b >>);
+ $C -> Function(Rest, A0, A1, A2, << Acc/binary, $c >>);
+ $D -> Function(Rest, A0, A1, A2, << Acc/binary, $d >>);
+ $E -> Function(Rest, A0, A1, A2, << Acc/binary, $e >>);
+ $F -> Function(Rest, A0, A1, A2, << Acc/binary, $f >>);
+ $G -> Function(Rest, A0, A1, A2, << Acc/binary, $g >>);
+ $H -> Function(Rest, A0, A1, A2, << Acc/binary, $h >>);
+ $I -> Function(Rest, A0, A1, A2, << Acc/binary, $i >>);
+ $J -> Function(Rest, A0, A1, A2, << Acc/binary, $j >>);
+ $K -> Function(Rest, A0, A1, A2, << Acc/binary, $k >>);
+ $L -> Function(Rest, A0, A1, A2, << Acc/binary, $l >>);
+ $M -> Function(Rest, A0, A1, A2, << Acc/binary, $m >>);
+ $N -> Function(Rest, A0, A1, A2, << Acc/binary, $n >>);
+ $O -> Function(Rest, A0, A1, A2, << Acc/binary, $o >>);
+ $P -> Function(Rest, A0, A1, A2, << Acc/binary, $p >>);
+ $Q -> Function(Rest, A0, A1, A2, << Acc/binary, $q >>);
+ $R -> Function(Rest, A0, A1, A2, << Acc/binary, $r >>);
+ $S -> Function(Rest, A0, A1, A2, << Acc/binary, $s >>);
+ $T -> Function(Rest, A0, A1, A2, << Acc/binary, $t >>);
+ $U -> Function(Rest, A0, A1, A2, << Acc/binary, $u >>);
+ $V -> Function(Rest, A0, A1, A2, << Acc/binary, $v >>);
+ $W -> Function(Rest, A0, A1, A2, << Acc/binary, $w >>);
+ $X -> Function(Rest, A0, A1, A2, << Acc/binary, $x >>);
+ $Y -> Function(Rest, A0, A1, A2, << Acc/binary, $y >>);
+ $Z -> Function(Rest, A0, A1, A2, << Acc/binary, $z >>);
+ C -> Function(Rest, A0, A1, A2, << Acc/binary, C >>)
+end).
+
+-define(LOWER(Function, Rest, A0, A1, A2, A3, Acc), case C of
+ $A -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $a >>);
+ $B -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $b >>);
+ $C -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $c >>);
+ $D -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $d >>);
+ $E -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $e >>);
+ $F -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $f >>);
+ $G -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $g >>);
+ $H -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $h >>);
+ $I -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $i >>);
+ $J -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $j >>);
+ $K -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $k >>);
+ $L -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $l >>);
+ $M -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $m >>);
+ $N -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $n >>);
+ $O -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $o >>);
+ $P -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $p >>);
+ $Q -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $q >>);
+ $R -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $r >>);
+ $S -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $s >>);
+ $T -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $t >>);
+ $U -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $u >>);
+ $V -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $v >>);
+ $W -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $w >>);
+ $X -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $x >>);
+ $Y -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $y >>);
+ $Z -> Function(Rest, A0, A1, A2, A3, << Acc/binary, $z >>);
+ C -> Function(Rest, A0, A1, A2, A3, << Acc/binary, C >>)
+end).
+
+-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, Acc), case C of
+ $A -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $a >>);
+ $B -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $b >>);
+ $C -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $c >>);
+ $D -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $d >>);
+ $E -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $e >>);
+ $F -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $f >>);
+ $G -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $g >>);
+ $H -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $h >>);
+ $I -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $i >>);
+ $J -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $j >>);
+ $K -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $k >>);
+ $L -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $l >>);
+ $M -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $m >>);
+ $N -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $n >>);
+ $O -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $o >>);
+ $P -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $p >>);
+ $Q -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $q >>);
+ $R -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $r >>);
+ $S -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $s >>);
+ $T -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $t >>);
+ $U -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $u >>);
+ $V -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $v >>);
+ $W -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $w >>);
+ $X -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $x >>);
+ $Y -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $y >>);
+ $Z -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, $z >>);
+ C -> Function(Rest, A0, A1, A2, A3, A4, << Acc/binary, C >>)
+end).
+
+-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, A5, Acc), case C of
+ $A -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $a >>);
+ $B -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $b >>);
+ $C -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $c >>);
+ $D -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $d >>);
+ $E -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $e >>);
+ $F -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $f >>);
+ $G -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $g >>);
+ $H -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $h >>);
+ $I -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $i >>);
+ $J -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $j >>);
+ $K -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $k >>);
+ $L -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $l >>);
+ $M -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $m >>);
+ $N -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $n >>);
+ $O -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $o >>);
+ $P -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $p >>);
+ $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $q >>);
+ $R -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $r >>);
+ $S -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $s >>);
+ $T -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $t >>);
+ $U -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $u >>);
+ $V -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $v >>);
+ $W -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $w >>);
+ $X -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $x >>);
+ $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $y >>);
+ $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, $z >>);
+ C -> Function(Rest, A0, A1, A2, A3, A4, A5, << Acc/binary, C >>)
+end).
+
+-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, A5, A6, Acc), case C of
+ $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $a >>);
+ $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $b >>);
+ $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $c >>);
+ $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $d >>);
+ $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $e >>);
+ $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $f >>);
+ $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $g >>);
+ $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $h >>);
+ $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $i >>);
+ $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $j >>);
+ $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $k >>);
+ $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $l >>);
+ $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $m >>);
+ $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $n >>);
+ $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $o >>);
+ $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $p >>);
+ $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $q >>);
+ $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $r >>);
+ $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $s >>);
+ $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $t >>);
+ $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $u >>);
+ $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $v >>);
+ $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $w >>);
+ $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $x >>);
+ $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $y >>);
+ $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, $z >>);
+ C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, << Acc/binary, C >>)
+end).
+
+-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, Acc), case C of
+ $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $a >>);
+ $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $b >>);
+ $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $c >>);
+ $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $d >>);
+ $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $e >>);
+ $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $f >>);
+ $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $g >>);
+ $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $h >>);
+ $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $i >>);
+ $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $j >>);
+ $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $k >>);
+ $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $l >>);
+ $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $m >>);
+ $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $n >>);
+ $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $o >>);
+ $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $p >>);
+ $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $q >>);
+ $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $r >>);
+ $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $s >>);
+ $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $t >>);
+ $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $u >>);
+ $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $v >>);
+ $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $w >>);
+ $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $x >>);
+ $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $y >>);
+ $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, $z >>);
+ C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, << Acc/binary, C >>)
+end).
+
+-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, Acc), case C of
+ $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $a >>);
+ $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $b >>);
+ $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $c >>);
+ $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $d >>);
+ $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $e >>);
+ $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $f >>);
+ $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $g >>);
+ $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $h >>);
+ $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $i >>);
+ $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $j >>);
+ $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $k >>);
+ $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $l >>);
+ $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $m >>);
+ $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $n >>);
+ $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $o >>);
+ $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $p >>);
+ $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $q >>);
+ $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $r >>);
+ $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $s >>);
+ $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $t >>);
+ $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $u >>);
+ $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $v >>);
+ $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $w >>);
+ $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $x >>);
+ $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $y >>);
+ $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, $z >>);
+ C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, << Acc/binary, C >>)
+end).
+
+-define(LOWER(Function, Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, Acc), case C of
+ $A -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $a >>);
+ $B -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $b >>);
+ $C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $c >>);
+ $D -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $d >>);
+ $E -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $e >>);
+ $F -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $f >>);
+ $G -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $g >>);
+ $H -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $h >>);
+ $I -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $i >>);
+ $J -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $j >>);
+ $K -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $k >>);
+ $L -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $l >>);
+ $M -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $m >>);
+ $N -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $n >>);
+ $O -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $o >>);
+ $P -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $p >>);
+ $Q -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $q >>);
+ $R -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $r >>);
+ $S -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $s >>);
+ $T -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $t >>);
+ $U -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $u >>);
+ $V -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $v >>);
+ $W -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $w >>);
+ $X -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $x >>);
+ $Y -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $y >>);
+ $Z -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, $z >>);
+ C -> Function(Rest, A0, A1, A2, A3, A4, A5, A6, A7, A8, A9, << Acc/binary, C >>)
+end).
+
+%% HEX(C)
+
+-define(HEX(C), (?HEXHL(C bsr 4)), (?HEXHL(C band 16#0f))).
+
+-define(HEXHL(HL),
+ case HL of
+ 0 -> $0;
+ 1 -> $1;
+ 2 -> $2;
+ 3 -> $3;
+ 4 -> $4;
+ 5 -> $5;
+ 6 -> $6;
+ 7 -> $7;
+ 8 -> $8;
+ 9 -> $9;
+ 10 -> $A;
+ 11 -> $B;
+ 12 -> $C;
+ 13 -> $D;
+ 14 -> $E;
+ 15 -> $F
+ end
+).
+
+%% UNHEX(H, L)
+
+-define(UNHEX(H, L), (?UNHEX(H) bsl 4 bor ?UNHEX(L))).
+
+-define(UNHEX(C),
+ case C of
+ $0 -> 0;
+ $1 -> 1;
+ $2 -> 2;
+ $3 -> 3;
+ $4 -> 4;
+ $5 -> 5;
+ $6 -> 6;
+ $7 -> 7;
+ $8 -> 8;
+ $9 -> 9;
+ $A -> 10;
+ $B -> 11;
+ $C -> 12;
+ $D -> 13;
+ $E -> 14;
+ $F -> 15;
+ $a -> 10;
+ $b -> 11;
+ $c -> 12;
+ $d -> 13;
+ $e -> 14;
+ $f -> 15
+ end
+).
+
+-endif.
diff --git a/server/_build/default/lib/cowlib/include/cow_parse.hrl b/server/_build/default/lib/cowlib/include/cow_parse.hrl
new file mode 100644
index 0000000..72eaff6
--- /dev/null
+++ b/server/_build/default/lib/cowlib/include/cow_parse.hrl
@@ -0,0 +1,83 @@
+%% Copyright (c) 2015-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-ifndef(COW_PARSE_HRL).
+-define(COW_PARSE_HRL, 1).
+
+-define(IS_ALPHA(C),
+ (C =:= $a) or (C =:= $b) or (C =:= $c) or (C =:= $d) or (C =:= $e) or
+ (C =:= $f) or (C =:= $g) or (C =:= $h) or (C =:= $i) or (C =:= $j) or
+ (C =:= $k) or (C =:= $l) or (C =:= $m) or (C =:= $n) or (C =:= $o) or
+ (C =:= $p) or (C =:= $q) or (C =:= $r) or (C =:= $s) or (C =:= $t) or
+ (C =:= $u) or (C =:= $v) or (C =:= $w) or (C =:= $x) or (C =:= $y) or
+ (C =:= $z) or
+ (C =:= $A) or (C =:= $B) or (C =:= $C) or (C =:= $D) or (C =:= $E) or
+ (C =:= $F) or (C =:= $G) or (C =:= $H) or (C =:= $I) or (C =:= $J) or
+ (C =:= $K) or (C =:= $L) or (C =:= $M) or (C =:= $N) or (C =:= $O) or
+ (C =:= $P) or (C =:= $Q) or (C =:= $R) or (C =:= $S) or (C =:= $T) or
+ (C =:= $U) or (C =:= $V) or (C =:= $W) or (C =:= $X) or (C =:= $Y) or
+ (C =:= $Z)
+).
+
+-define(IS_ALPHANUM(C), ?IS_ALPHA(C) or ?IS_DIGIT(C)).
+-define(IS_CHAR(C), C > 0, C < 128).
+
+-define(IS_DIGIT(C),
+ (C =:= $0) or (C =:= $1) or (C =:= $2) or (C =:= $3) or (C =:= $4) or
+ (C =:= $5) or (C =:= $6) or (C =:= $7) or (C =:= $8) or (C =:= $9)).
+
+-define(IS_ETAGC(C), C =:= 16#21; C >= 16#23, C =/= 16#7f).
+
+-define(IS_HEX(C),
+ ?IS_DIGIT(C) or
+ (C =:= $a) or (C =:= $b) or (C =:= $c) or
+ (C =:= $d) or (C =:= $e) or (C =:= $f) or
+ (C =:= $A) or (C =:= $B) or (C =:= $C) or
+ (C =:= $D) or (C =:= $E) or (C =:= $F)).
+
+-define(IS_LHEX(C),
+ ?IS_DIGIT(C) or
+ (C =:= $a) or (C =:= $b) or (C =:= $c) or
+ (C =:= $d) or (C =:= $e) or (C =:= $f)).
+
+-define(IS_TOKEN(C),
+ ?IS_ALPHA(C) or ?IS_DIGIT(C) or
+ (C =:= $!) or (C =:= $#) or (C =:= $$) or (C =:= $%) or (C =:= $&) or
+ (C =:= $') or (C =:= $*) or (C =:= $+) or (C =:= $-) or (C =:= $.) or
+ (C =:= $^) or (C =:= $_) or (C =:= $`) or (C =:= $|) or (C =:= $~)).
+
+-define(IS_TOKEN68(C),
+ ?IS_ALPHA(C) or ?IS_DIGIT(C) or
+ (C =:= $-) or (C =:= $.) or (C =:= $_) or
+ (C =:= $~) or (C =:= $+) or (C =:= $/)).
+
+-define(IS_URI_UNRESERVED(C),
+ ?IS_ALPHA(C) or ?IS_DIGIT(C) or
+ (C =:= $-) or (C =:= $.) or (C =:= $_) or (C =:= $~)).
+
+-define(IS_URI_GEN_DELIMS(C),
+ (C =:= $:) or (C =:= $/) or (C =:= $?) or (C =:= $#) or
+ (C =:= $[) or (C =:= $]) or (C =:= $@)).
+
+-define(IS_URI_SUB_DELIMS(C),
+ (C =:= $!) or (C =:= $$) or (C =:= $&) or (C =:= $') or
+ (C =:= $() or (C =:= $)) or (C =:= $*) or (C =:= $+) or
+ (C =:= $,) or (C =:= $;) or (C =:= $=)).
+
+-define(IS_VCHAR(C), C =:= $\t; C > 31, C < 127).
+-define(IS_VCHAR_OBS(C), C =:= $\t; C > 31, C =/= 127).
+-define(IS_WS(C), (C =:= $\s) or (C =:= $\t)).
+-define(IS_WS_COMMA(C), ?IS_WS(C) or (C =:= $,)).
+
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_base64url.erl b/server/_build/default/lib/cowlib/src/cow_base64url.erl
new file mode 100644
index 0000000..e591fcf
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_base64url.erl
@@ -0,0 +1,81 @@
+%% Copyright (c) 2017-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% This module implements "base64url" following the algorithm
+%% found in Appendix C of RFC7515. The option #{padding => false}
+%% must be given to reproduce this variant exactly. The default
+%% will leave the padding characters.
+-module(cow_base64url).
+
+-export([decode/1]).
+-export([decode/2]).
+-export([encode/1]).
+-export([encode/2]).
+
+-ifdef(TEST).
+-include_lib("proper/include/proper.hrl").
+-endif.
+
+decode(Enc) ->
+ decode(Enc, #{}).
+
+decode(Enc0, Opts) ->
+ Enc1 = << << case C of
+ $- -> $+;
+ $_ -> $/;
+ _ -> C
+ end >> || << C >> <= Enc0 >>,
+ Enc = case Opts of
+ #{padding := false} ->
+ case byte_size(Enc1) rem 4 of
+ 0 -> Enc1;
+ 2 -> << Enc1/binary, "==" >>;
+ 3 -> << Enc1/binary, "=" >>
+ end;
+ _ ->
+ Enc1
+ end,
+ base64:decode(Enc).
+
+encode(Dec) ->
+ encode(Dec, #{}).
+
+encode(Dec, Opts) ->
+ encode(base64:encode(Dec), Opts, <<>>).
+
+encode(<<$+, R/bits>>, Opts, Acc) -> encode(R, Opts, <<Acc/binary, $->>);
+encode(<<$/, R/bits>>, Opts, Acc) -> encode(R, Opts, <<Acc/binary, $_>>);
+encode(<<$=, _/bits>>, #{padding := false}, Acc) -> Acc;
+encode(<<C, R/bits>>, Opts, Acc) -> encode(R, Opts, <<Acc/binary, C>>);
+encode(<<>>, _, Acc) -> Acc.
+
+-ifdef(TEST).
+
+rfc7515_test() ->
+ Dec = <<3,236,255,224,193>>,
+ Enc = <<"A-z_4ME">>,
+ Pad = <<"A-z_4ME=">>,
+ Dec = decode(<<Enc/binary,$=>>),
+ Dec = decode(Enc, #{padding => false}),
+ Pad = encode(Dec),
+ Enc = encode(Dec, #{padding => false}),
+ ok.
+
+prop_identity() ->
+ ?FORALL(B, binary(), B =:= decode(encode(B))).
+
+prop_identity_no_padding() ->
+ ?FORALL(B, binary(), B =:= decode(encode(B, #{padding => false}), #{padding => false})).
+
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_cookie.erl b/server/_build/default/lib/cowlib/src/cow_cookie.erl
new file mode 100644
index 0000000..11cf339
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_cookie.erl
@@ -0,0 +1,456 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_cookie).
+
+-export([parse_cookie/1]).
+-export([parse_set_cookie/1]).
+-export([cookie/1]).
+-export([setcookie/3]).
+
+-type cookie_attrs() :: #{
+ expires => calendar:datetime(),
+ max_age => calendar:datetime(),
+ domain => binary(),
+ path => binary(),
+ secure => true,
+ http_only => true,
+ same_site => default | none | strict | lax
+}.
+-export_type([cookie_attrs/0]).
+
+-type cookie_opts() :: #{
+ domain => binary(),
+ http_only => boolean(),
+ max_age => non_neg_integer(),
+ path => binary(),
+ same_site => default | none | strict | lax,
+ secure => boolean()
+}.
+-export_type([cookie_opts/0]).
+
+-include("cow_inline.hrl").
+
+%% Cookie header.
+
+-spec parse_cookie(binary()) -> [{binary(), binary()}].
+parse_cookie(Cookie) ->
+ parse_cookie(Cookie, []).
+
+parse_cookie(<<>>, Acc) ->
+ lists:reverse(Acc);
+parse_cookie(<< $\s, Rest/binary >>, Acc) ->
+ parse_cookie(Rest, Acc);
+parse_cookie(<< $\t, Rest/binary >>, Acc) ->
+ parse_cookie(Rest, Acc);
+parse_cookie(<< $,, Rest/binary >>, Acc) ->
+ parse_cookie(Rest, Acc);
+parse_cookie(<< $;, Rest/binary >>, Acc) ->
+ parse_cookie(Rest, Acc);
+parse_cookie(Cookie, Acc) ->
+ parse_cookie_name(Cookie, Acc, <<>>).
+
+parse_cookie_name(<<>>, Acc, Name) ->
+ lists:reverse([{<<>>, parse_cookie_trim(Name)}|Acc]);
+parse_cookie_name(<< $=, _/binary >>, _, <<>>) ->
+ error(badarg);
+parse_cookie_name(<< $=, Rest/binary >>, Acc, Name) ->
+ parse_cookie_value(Rest, Acc, Name, <<>>);
+parse_cookie_name(<< $,, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< $;, Rest/binary >>, Acc, Name) ->
+ parse_cookie(Rest, [{<<>>, parse_cookie_trim(Name)}|Acc]);
+parse_cookie_name(<< $\t, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< $\r, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< $\n, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< $\013, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< $\014, _/binary >>, _, _) ->
+ error(badarg);
+parse_cookie_name(<< C, Rest/binary >>, Acc, Name) ->
+ parse_cookie_name(Rest, Acc, << Name/binary, C >>).
+
+parse_cookie_value(<<>>, Acc, Name, Value) ->
+ lists:reverse([{Name, parse_cookie_trim(Value)}|Acc]);
+parse_cookie_value(<< $;, Rest/binary >>, Acc, Name, Value) ->
+ parse_cookie(Rest, [{Name, parse_cookie_trim(Value)}|Acc]);
+parse_cookie_value(<< $\t, _/binary >>, _, _, _) ->
+ error(badarg);
+parse_cookie_value(<< $\r, _/binary >>, _, _, _) ->
+ error(badarg);
+parse_cookie_value(<< $\n, _/binary >>, _, _, _) ->
+ error(badarg);
+parse_cookie_value(<< $\013, _/binary >>, _, _, _) ->
+ error(badarg);
+parse_cookie_value(<< $\014, _/binary >>, _, _, _) ->
+ error(badarg);
+parse_cookie_value(<< C, Rest/binary >>, Acc, Name, Value) ->
+ parse_cookie_value(Rest, Acc, Name, << Value/binary, C >>).
+
+parse_cookie_trim(Value = <<>>) ->
+ Value;
+parse_cookie_trim(Value) ->
+ case binary:last(Value) of
+ $\s ->
+ Size = byte_size(Value) - 1,
+ << Value2:Size/binary, _ >> = Value,
+ parse_cookie_trim(Value2);
+ _ ->
+ Value
+ end.
+
+-ifdef(TEST).
+parse_cookie_test_() ->
+ %% {Value, Result}.
+ Tests = [
+ {<<"name=value; name2=value2">>, [
+ {<<"name">>, <<"value">>},
+ {<<"name2">>, <<"value2">>}
+ ]},
+ %% Space in value.
+ {<<"foo=Thu Jul 11 2013 15:38:43 GMT+0400 (MSK)">>,
+ [{<<"foo">>, <<"Thu Jul 11 2013 15:38:43 GMT+0400 (MSK)">>}]},
+ %% Comma in value. Google Analytics sets that kind of cookies.
+ {<<"refk=sOUZDzq2w2; sk=B602064E0139D842D620C7569640DBB4C81C45080651"
+ "9CC124EF794863E10E80; __utma=64249653.825741573.1380181332.1400"
+ "015657.1400019557.703; __utmb=64249653.1.10.1400019557; __utmc="
+ "64249653; __utmz=64249653.1400019557.703.13.utmcsr=bluesky.chic"
+ "agotribune.com|utmccn=(referral)|utmcmd=referral|utmcct=/origin"
+ "als/chi-12-indispensable-digital-tools-bsi,0,0.storygallery">>, [
+ {<<"refk">>, <<"sOUZDzq2w2">>},
+ {<<"sk">>, <<"B602064E0139D842D620C7569640DBB4C81C45080651"
+ "9CC124EF794863E10E80">>},
+ {<<"__utma">>, <<"64249653.825741573.1380181332.1400"
+ "015657.1400019557.703">>},
+ {<<"__utmb">>, <<"64249653.1.10.1400019557">>},
+ {<<"__utmc">>, <<"64249653">>},
+ {<<"__utmz">>, <<"64249653.1400019557.703.13.utmcsr=bluesky.chic"
+ "agotribune.com|utmccn=(referral)|utmcmd=referral|utmcct=/origin"
+ "als/chi-12-indispensable-digital-tools-bsi,0,0.storygallery">>}
+ ]},
+ %% Potential edge cases (initially from Mochiweb).
+ {<<"foo=\\x">>, [{<<"foo">>, <<"\\x">>}]},
+ {<<"foo=;bar=">>, [{<<"foo">>, <<>>}, {<<"bar">>, <<>>}]},
+ {<<"foo=\\\";;bar=good ">>,
+ [{<<"foo">>, <<"\\\"">>}, {<<"bar">>, <<"good">>}]},
+ {<<"foo=\"\\\";bar=good">>,
+ [{<<"foo">>, <<"\"\\\"">>}, {<<"bar">>, <<"good">>}]},
+ {<<>>, []}, %% Flash player.
+ {<<"foo=bar , baz=wibble ">>, [{<<"foo">>, <<"bar , baz=wibble">>}]},
+ %% Technically invalid, but seen in the wild
+ {<<"foo">>, [{<<>>, <<"foo">>}]},
+ {<<"foo ">>, [{<<>>, <<"foo">>}]},
+ {<<"foo;">>, [{<<>>, <<"foo">>}]},
+ {<<"bar;foo=1">>, [{<<>>, <<"bar">>}, {<<"foo">>, <<"1">>}]}
+ ],
+ [{V, fun() -> R = parse_cookie(V) end} || {V, R} <- Tests].
+
+parse_cookie_error_test_() ->
+ %% Value.
+ Tests = [
+ <<"=">>
+ ],
+ [{V, fun() -> {'EXIT', {badarg, _}} = (catch parse_cookie(V)) end} || V <- Tests].
+-endif.
+
+%% Set-Cookie header.
+
+-spec parse_set_cookie(binary())
+ -> {ok, binary(), binary(), cookie_attrs()}
+ | ignore.
+parse_set_cookie(SetCookie) ->
+ case has_non_ws_ctl(SetCookie) of
+ true ->
+ ignore;
+ false ->
+ {NameValuePair, UnparsedAttrs} = take_until_semicolon(SetCookie, <<>>),
+ {Name, Value} = case binary:split(NameValuePair, <<$=>>) of
+ [Value0] -> {<<>>, trim(Value0)};
+ [Name0, Value0] -> {trim(Name0), trim(Value0)}
+ end,
+ case {Name, Value} of
+ {<<>>, <<>>} ->
+ ignore;
+ _ ->
+ Attrs = parse_set_cookie_attrs(UnparsedAttrs, #{}),
+ {ok, Name, Value, Attrs}
+ end
+ end.
+
+has_non_ws_ctl(<<>>) ->
+ false;
+has_non_ws_ctl(<<C,R/bits>>) ->
+ if
+ C =< 16#08 -> true;
+ C >= 16#0A, C =< 16#1F -> true;
+ C =:= 16#7F -> true;
+ true -> has_non_ws_ctl(R)
+ end.
+
+parse_set_cookie_attrs(<<>>, Attrs) ->
+ Attrs;
+parse_set_cookie_attrs(<<$;,Rest0/bits>>, Attrs) ->
+ {Av, Rest} = take_until_semicolon(Rest0, <<>>),
+ {Name, Value} = case binary:split(Av, <<$=>>) of
+ [Name0] -> {trim(Name0), <<>>};
+ [Name0, Value0] -> {trim(Name0), trim(Value0)}
+ end,
+ if
+ byte_size(Value) > 1024 ->
+ parse_set_cookie_attrs(Rest, Attrs);
+ true ->
+ case parse_set_cookie_attr(?LOWER(Name), Value) of
+ {ok, AttrName, AttrValue} ->
+ parse_set_cookie_attrs(Rest, Attrs#{AttrName => AttrValue});
+ {ignore, AttrName} ->
+ parse_set_cookie_attrs(Rest, maps:remove(AttrName, Attrs));
+ ignore ->
+ parse_set_cookie_attrs(Rest, Attrs)
+ end
+ end.
+
+take_until_semicolon(Rest = <<$;,_/bits>>, Acc) -> {Acc, Rest};
+take_until_semicolon(<<C,R/bits>>, Acc) -> take_until_semicolon(R, <<Acc/binary,C>>);
+take_until_semicolon(<<>>, Acc) -> {Acc, <<>>}.
+
+trim(String) ->
+ string:trim(String, both, [$\s, $\t]).
+
+parse_set_cookie_attr(<<"expires">>, Value) ->
+ try cow_date:parse_date(Value) of
+ DateTime ->
+ {ok, expires, DateTime}
+ catch _:_ ->
+ ignore
+ end;
+parse_set_cookie_attr(<<"max-age">>, Value) ->
+ try binary_to_integer(Value) of
+ MaxAge when MaxAge =< 0 ->
+ %% Year 0 corresponds to 1 BC.
+ {ok, max_age, {{0, 1, 1}, {0, 0, 0}}};
+ MaxAge ->
+ CurrentTime = erlang:universaltime(),
+ {ok, max_age, calendar:gregorian_seconds_to_datetime(
+ calendar:datetime_to_gregorian_seconds(CurrentTime) + MaxAge)}
+ catch _:_ ->
+ ignore
+ end;
+parse_set_cookie_attr(<<"domain">>, Value) ->
+ case Value of
+ <<>> ->
+ ignore;
+ <<".",Rest/bits>> ->
+ {ok, domain, ?LOWER(Rest)};
+ _ ->
+ {ok, domain, ?LOWER(Value)}
+ end;
+parse_set_cookie_attr(<<"path">>, Value) ->
+ case Value of
+ <<"/",_/bits>> ->
+ {ok, path, Value};
+ %% When the path is not absolute, or the path is empty, the default-path will be used.
+ %% Note that the default-path is also used when there are no path attributes,
+ %% so we are simply ignoring the attribute here.
+ _ ->
+ {ignore, path}
+ end;
+parse_set_cookie_attr(<<"secure">>, _) ->
+ {ok, secure, true};
+parse_set_cookie_attr(<<"httponly">>, _) ->
+ {ok, http_only, true};
+parse_set_cookie_attr(<<"samesite">>, Value) ->
+ case ?LOWER(Value) of
+ <<"none">> ->
+ {ok, same_site, none};
+ <<"strict">> ->
+ {ok, same_site, strict};
+ <<"lax">> ->
+ {ok, same_site, lax};
+ %% Unknown values and lack of value are equivalent.
+ _ ->
+ {ok, same_site, default}
+ end;
+parse_set_cookie_attr(_, _) ->
+ ignore.
+
+-ifdef(TEST).
+parse_set_cookie_test_() ->
+ Tests = [
+ {<<"a=b">>, {ok, <<"a">>, <<"b">>, #{}}},
+ {<<"a=b; Secure">>, {ok, <<"a">>, <<"b">>, #{secure => true}}},
+ {<<"a=b; HttpOnly">>, {ok, <<"a">>, <<"b">>, #{http_only => true}}},
+ {<<"a=b; Expires=Wed, 21 Oct 2015 07:28:00 GMT; Expires=Wed, 21 Oct 2015 07:29:00 GMT">>,
+ {ok, <<"a">>, <<"b">>, #{expires => {{2015,10,21},{7,29,0}}}}},
+ {<<"a=b; Max-Age=999; Max-Age=0">>,
+ {ok, <<"a">>, <<"b">>, #{max_age => {{0,1,1},{0,0,0}}}}},
+ {<<"a=b; Domain=example.org; Domain=foo.example.org">>,
+ {ok, <<"a">>, <<"b">>, #{domain => <<"foo.example.org">>}}},
+ {<<"a=b; Path=/path/to/resource; Path=/">>,
+ {ok, <<"a">>, <<"b">>, #{path => <<"/">>}}},
+ {<<"a=b; SameSite=UnknownValue">>, {ok, <<"a">>, <<"b">>, #{same_site => default}}},
+ {<<"a=b; SameSite=None">>, {ok, <<"a">>, <<"b">>, #{same_site => none}}},
+ {<<"a=b; SameSite=Lax">>, {ok, <<"a">>, <<"b">>, #{same_site => lax}}},
+ {<<"a=b; SameSite=Strict">>, {ok, <<"a">>, <<"b">>, #{same_site => strict}}},
+ {<<"a=b; SameSite=Lax; SameSite=Strict">>,
+ {ok, <<"a">>, <<"b">>, #{same_site => strict}}}
+ ],
+ [{SetCookie, fun() -> Res = parse_set_cookie(SetCookie) end}
+ || {SetCookie, Res} <- Tests].
+-endif.
+
+%% Build a cookie header.
+
+-spec cookie([{iodata(), iodata()}]) -> iolist().
+cookie([]) ->
+ [];
+cookie([{<<>>, Value}]) ->
+ [Value];
+cookie([{Name, Value}]) ->
+ [Name, $=, Value];
+cookie([{<<>>, Value}|Tail]) ->
+ [Value, $;, $\s|cookie(Tail)];
+cookie([{Name, Value}|Tail]) ->
+ [Name, $=, Value, $;, $\s|cookie(Tail)].
+
+-ifdef(TEST).
+cookie_test_() ->
+ Tests = [
+ {[], <<>>},
+ {[{<<"a">>, <<"b">>}], <<"a=b">>},
+ {[{<<"a">>, <<"b">>}, {<<"c">>, <<"d">>}], <<"a=b; c=d">>},
+ {[{<<>>, <<"b">>}, {<<"c">>, <<"d">>}], <<"b; c=d">>},
+ {[{<<"a">>, <<"b">>}, {<<>>, <<"d">>}], <<"a=b; d">>}
+ ],
+ [{Res, fun() -> Res = iolist_to_binary(cookie(Cookies)) end}
+ || {Cookies, Res} <- Tests].
+-endif.
+
+%% Convert a cookie name, value and options to its iodata form.
+%%
+%% Initially from Mochiweb:
+%% * Copyright 2007 Mochi Media, Inc.
+%% Initial binary implementation:
+%% * Copyright 2011 Thomas Burdick <thomas.burdick@gmail.com>
+%%
+%% @todo Rename the function to set_cookie eventually.
+
+-spec setcookie(iodata(), iodata(), cookie_opts()) -> iolist().
+setcookie(Name, Value, Opts) ->
+ nomatch = binary:match(iolist_to_binary(Name), [<<$=>>, <<$,>>, <<$;>>,
+ <<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]),
+ nomatch = binary:match(iolist_to_binary(Value), [<<$,>>, <<$;>>,
+ <<$\s>>, <<$\t>>, <<$\r>>, <<$\n>>, <<$\013>>, <<$\014>>]),
+ [Name, <<"=">>, Value, attributes(maps:to_list(Opts))].
+
+attributes([]) -> [];
+attributes([{domain, Domain}|Tail]) -> [<<"; Domain=">>, Domain|attributes(Tail)];
+attributes([{http_only, false}|Tail]) -> attributes(Tail);
+attributes([{http_only, true}|Tail]) -> [<<"; HttpOnly">>|attributes(Tail)];
+%% MSIE requires an Expires date in the past to delete a cookie.
+attributes([{max_age, 0}|Tail]) ->
+ [<<"; Expires=Thu, 01-Jan-1970 00:00:01 GMT; Max-Age=0">>|attributes(Tail)];
+attributes([{max_age, MaxAge}|Tail]) when is_integer(MaxAge), MaxAge > 0 ->
+ Secs = calendar:datetime_to_gregorian_seconds(calendar:universal_time()),
+ Expires = cow_date:rfc2109(calendar:gregorian_seconds_to_datetime(Secs + MaxAge)),
+ [<<"; Expires=">>, Expires, <<"; Max-Age=">>, integer_to_list(MaxAge)|attributes(Tail)];
+attributes([Opt={max_age, _}|_]) ->
+ error({badarg, Opt});
+attributes([{path, Path}|Tail]) -> [<<"; Path=">>, Path|attributes(Tail)];
+attributes([{secure, false}|Tail]) -> attributes(Tail);
+attributes([{secure, true}|Tail]) -> [<<"; Secure">>|attributes(Tail)];
+attributes([{same_site, default}|Tail]) -> attributes(Tail);
+attributes([{same_site, none}|Tail]) -> [<<"; SameSite=None">>|attributes(Tail)];
+attributes([{same_site, lax}|Tail]) -> [<<"; SameSite=Lax">>|attributes(Tail)];
+attributes([{same_site, strict}|Tail]) -> [<<"; SameSite=Strict">>|attributes(Tail)];
+%% Skip unknown options.
+attributes([_|Tail]) -> attributes(Tail).
+
+-ifdef(TEST).
+setcookie_test_() ->
+ %% {Name, Value, Opts, Result}
+ Tests = [
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{http_only => true, domain => <<"acme.com">>},
+ <<"Customer=WILE_E_COYOTE; "
+ "Domain=acme.com; HttpOnly">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{path => <<"/acme">>},
+ <<"Customer=WILE_E_COYOTE; Path=/acme">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{secure => true},
+ <<"Customer=WILE_E_COYOTE; Secure">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{secure => false, http_only => false},
+ <<"Customer=WILE_E_COYOTE">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{same_site => default},
+ <<"Customer=WILE_E_COYOTE">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{same_site => none},
+ <<"Customer=WILE_E_COYOTE; SameSite=None">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{same_site => lax},
+ <<"Customer=WILE_E_COYOTE; SameSite=Lax">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{same_site => strict},
+ <<"Customer=WILE_E_COYOTE; SameSite=Strict">>},
+ {<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{path => <<"/acme">>, badoption => <<"negatory">>},
+ <<"Customer=WILE_E_COYOTE; Path=/acme">>}
+ ],
+ [{R, fun() -> R = iolist_to_binary(setcookie(N, V, O)) end}
+ || {N, V, O, R} <- Tests].
+
+setcookie_max_age_test() ->
+ F = fun(N, V, O) ->
+ binary:split(iolist_to_binary(
+ setcookie(N, V, O)), <<";">>, [global])
+ end,
+ [<<"Customer=WILE_E_COYOTE">>,
+ <<" Expires=", _/binary>>,
+ <<" Max-Age=111">>,
+ <<" Secure">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{max_age => 111, secure => true}),
+ case catch F(<<"Customer">>, <<"WILE_E_COYOTE">>, #{max_age => -111}) of
+ {'EXIT', {{badarg, {max_age, -111}}, _}} -> ok
+ end,
+ [<<"Customer=WILE_E_COYOTE">>,
+ <<" Expires=", _/binary>>,
+ <<" Max-Age=86417">>] = F(<<"Customer">>, <<"WILE_E_COYOTE">>,
+ #{max_age => 86417}),
+ ok.
+
+setcookie_failures_test_() ->
+ F = fun(N, V) ->
+ try setcookie(N, V, #{}) of
+ _ ->
+ false
+ catch _:_ ->
+ true
+ end
+ end,
+ Tests = [
+ {<<"Na=me">>, <<"Value">>},
+ {<<"Name;">>, <<"Value">>},
+ {<<"\r\name">>, <<"Value">>},
+ {<<"Name">>, <<"Value;">>},
+ {<<"Name">>, <<"\value">>}
+ ],
+ [{iolist_to_binary(io_lib:format("{~p, ~p} failure", [N, V])),
+ fun() -> true = F(N, V) end}
+ || {N, V} <- Tests].
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_date.erl b/server/_build/default/lib/cowlib/src/cow_date.erl
new file mode 100644
index 0000000..00bc8af
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_date.erl
@@ -0,0 +1,434 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_date).
+
+-export([parse_date/1]).
+-export([rfc1123/1]).
+-export([rfc2109/1]).
+-export([rfc7231/1]).
+
+-ifdef(TEST).
+-include_lib("proper/include/proper.hrl").
+-endif.
+
+%% @doc Parse the HTTP date (IMF-fixdate, rfc850, asctime).
+
+-define(DIGITS(A, B), ((A - $0) * 10 + (B - $0))).
+-define(DIGITS(A, B, C, D), ((A - $0) * 1000 + (B - $0) * 100 + (C - $0) * 10 + (D - $0))).
+
+-spec parse_date(binary()) -> calendar:datetime().
+parse_date(DateBin) ->
+ Date = {{_, _, D}, {H, M, S}} = http_date(DateBin),
+ true = D >= 0 andalso D =< 31,
+ true = H >= 0 andalso H =< 23,
+ true = M >= 0 andalso M =< 59,
+ true = S >= 0 andalso S =< 60, %% Leap second.
+ Date.
+
+http_date(<<"Mon, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Tue, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Wed, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Thu, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Fri, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Sat, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Sun, ", D1, D2, " ", R/bits >>) -> fixdate(R, ?DIGITS(D1, D2));
+http_date(<<"Monday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Tuesday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Wednesday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Thursday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Friday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Saturday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Sunday, ", D1, D2, "-", R/bits >>) -> rfc850_date(R, ?DIGITS(D1, D2));
+http_date(<<"Mon ", R/bits >>) -> asctime_date(R);
+http_date(<<"Tue ", R/bits >>) -> asctime_date(R);
+http_date(<<"Wed ", R/bits >>) -> asctime_date(R);
+http_date(<<"Thu ", R/bits >>) -> asctime_date(R);
+http_date(<<"Fri ", R/bits >>) -> asctime_date(R);
+http_date(<<"Sat ", R/bits >>) -> asctime_date(R);
+http_date(<<"Sun ", R/bits >>) -> asctime_date(R).
+
+fixdate(<<"Jan ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 1, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Feb ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 2, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Mar ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 3, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Apr ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 4, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"May ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 5, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Jun ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 6, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Jul ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 7, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Aug ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 8, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Sep ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 9, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Oct ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 10, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Nov ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 11, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+fixdate(<<"Dec ", Y1, Y2, Y3, Y4, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 12, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}.
+
+rfc850_date(<<"Jan-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 1, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Feb-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 2, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Mar-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 3, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Apr-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 4, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"May-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 5, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Jun-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 6, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Jul-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 7, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Aug-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 8, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Sep-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 9, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Oct-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 10, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Nov-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 11, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+rfc850_date(<<"Dec-", Y1, Y2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " GMT">>, Day) ->
+ {{rfc850_year(?DIGITS(Y1, Y2)), 12, Day}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}.
+
+rfc850_year(Y) when Y > 50 -> Y + 1900;
+rfc850_year(Y) -> Y + 2000.
+
+asctime_date(<<"Jan ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 1, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Feb ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 2, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Mar ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 3, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Apr ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 4, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"May ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 5, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Jun ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 6, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Jul ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 7, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Aug ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 8, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Sep ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 9, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Oct ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 10, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Nov ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 11, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}};
+asctime_date(<<"Dec ", D1, D2, " ", H1, H2, ":", M1, M2, ":", S1, S2, " ", Y1, Y2, Y3, Y4 >>) ->
+ {{?DIGITS(Y1, Y2, Y3, Y4), 12, asctime_day(D1, D2)}, {?DIGITS(H1, H2), ?DIGITS(M1, M2), ?DIGITS(S1, S2)}}.
+
+asctime_day($\s, D2) -> (D2 - $0);
+asctime_day(D1, D2) -> (D1 - $0) * 10 + (D2 - $0).
+
+-ifdef(TEST).
+day_name() -> oneof(["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"]).
+day_name_l() -> oneof(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]).
+year() -> integer(1951, 2050).
+month() -> integer(1, 12).
+day() -> integer(1, 31).
+hour() -> integer(0, 23).
+minute() -> integer(0, 59).
+second() -> integer(0, 60).
+
+fixdate_gen() ->
+ ?LET({DayName, Y, Mo, D, H, Mi, S},
+ {day_name(), year(), month(), day(), hour(), minute(), second()},
+ {{{Y, Mo, D}, {H, Mi, S}},
+ list_to_binary([DayName, ", ", pad_int(D), " ", month(Mo), " ", integer_to_binary(Y),
+ " ", pad_int(H), ":", pad_int(Mi), ":", pad_int(S), " GMT"])}).
+
+rfc850_gen() ->
+ ?LET({DayName, Y, Mo, D, H, Mi, S},
+ {day_name_l(), year(), month(), day(), hour(), minute(), second()},
+ {{{Y, Mo, D}, {H, Mi, S}},
+ list_to_binary([DayName, ", ", pad_int(D), "-", month(Mo), "-", pad_int(Y rem 100),
+ " ", pad_int(H), ":", pad_int(Mi), ":", pad_int(S), " GMT"])}).
+
+asctime_gen() ->
+ ?LET({DayName, Y, Mo, D, H, Mi, S},
+ {day_name(), year(), month(), day(), hour(), minute(), second()},
+ {{{Y, Mo, D}, {H, Mi, S}},
+ list_to_binary([DayName, " ", month(Mo), " ",
+ if D < 10 -> << $\s, (D + $0) >>; true -> integer_to_binary(D) end,
+ " ", pad_int(H), ":", pad_int(Mi), ":", pad_int(S), " ", integer_to_binary(Y)])}).
+
+prop_http_date() ->
+ ?FORALL({Date, DateBin},
+ oneof([fixdate_gen(), rfc850_gen(), asctime_gen()]),
+ Date =:= parse_date(DateBin)).
+
+http_date_test_() ->
+ Tests = [
+ {<<"Sun, 06 Nov 1994 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
+ {<<"Sunday, 06-Nov-94 08:49:37 GMT">>, {{1994, 11, 6}, {8, 49, 37}}},
+ {<<"Sun Nov 6 08:49:37 1994">>, {{1994, 11, 6}, {8, 49, 37}}}
+ ],
+ [{V, fun() -> R = http_date(V) end} || {V, R} <- Tests].
+
+horse_http_date_fixdate() ->
+ horse:repeat(200000,
+ http_date(<<"Sun, 06 Nov 1994 08:49:37 GMT">>)
+ ).
+
+horse_http_date_rfc850() ->
+ horse:repeat(200000,
+ http_date(<<"Sunday, 06-Nov-94 08:49:37 GMT">>)
+ ).
+
+horse_http_date_asctime() ->
+ horse:repeat(200000,
+ http_date(<<"Sun Nov 6 08:49:37 1994">>)
+ ).
+-endif.
+
+%% @doc Return the date formatted according to RFC1123.
+
+-spec rfc1123(calendar:datetime()) -> binary().
+rfc1123(DateTime) ->
+ rfc7231(DateTime).
+
+%% @doc Return the date formatted according to RFC2109.
+
+-spec rfc2109(calendar:datetime()) -> binary().
+rfc2109({Date = {Y, Mo, D}, {H, Mi, S}}) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ",
+ (pad_int(D))/binary, "-",
+ (month(Mo))/binary, "-",
+ (year(Y))/binary, " ",
+ (pad_int(H))/binary, ":",
+ (pad_int(Mi))/binary, ":",
+ (pad_int(S))/binary, " GMT" >>.
+
+-ifdef(TEST).
+rfc2109_test_() ->
+ Tests = [
+ {<<"Sat, 14-May-2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}},
+ {<<"Sun, 01-Jan-2012 00:00:00 GMT">>, {{2012, 1, 1}, { 0, 0, 0}}}
+ ],
+ [{R, fun() -> R = rfc2109(D) end} || {R, D} <- Tests].
+
+horse_rfc2109_20130101_000000() ->
+ horse:repeat(100000,
+ rfc2109({{2013, 1, 1}, {0, 0, 0}})
+ ).
+
+horse_rfc2109_20131231_235959() ->
+ horse:repeat(100000,
+ rfc2109({{2013, 12, 31}, {23, 59, 59}})
+ ).
+
+horse_rfc2109_12340506_070809() ->
+ horse:repeat(100000,
+ rfc2109({{1234, 5, 6}, {7, 8, 9}})
+ ).
+-endif.
+
+%% @doc Return the date formatted according to RFC7231.
+
+-spec rfc7231(calendar:datetime()) -> binary().
+rfc7231({Date = {Y, Mo, D}, {H, Mi, S}}) ->
+ Wday = calendar:day_of_the_week(Date),
+ << (weekday(Wday))/binary, ", ",
+ (pad_int(D))/binary, " ",
+ (month(Mo))/binary, " ",
+ (year(Y))/binary, " ",
+ (pad_int(H))/binary, ":",
+ (pad_int(Mi))/binary, ":",
+ (pad_int(S))/binary, " GMT" >>.
+
+-ifdef(TEST).
+rfc7231_test_() ->
+ Tests = [
+ {<<"Sat, 14 May 2011 14:25:33 GMT">>, {{2011, 5, 14}, {14, 25, 33}}},
+ {<<"Sun, 01 Jan 2012 00:00:00 GMT">>, {{2012, 1, 1}, { 0, 0, 0}}}
+ ],
+ [{R, fun() -> R = rfc7231(D) end} || {R, D} <- Tests].
+
+horse_rfc7231_20130101_000000() ->
+ horse:repeat(100000,
+ rfc7231({{2013, 1, 1}, {0, 0, 0}})
+ ).
+
+horse_rfc7231_20131231_235959() ->
+ horse:repeat(100000,
+ rfc7231({{2013, 12, 31}, {23, 59, 59}})
+ ).
+
+horse_rfc7231_12340506_070809() ->
+ horse:repeat(100000,
+ rfc7231({{1234, 5, 6}, {7, 8, 9}})
+ ).
+-endif.
+
+%% Internal.
+
+-spec pad_int(0..59) -> <<_:16>>.
+pad_int( 0) -> <<"00">>;
+pad_int( 1) -> <<"01">>;
+pad_int( 2) -> <<"02">>;
+pad_int( 3) -> <<"03">>;
+pad_int( 4) -> <<"04">>;
+pad_int( 5) -> <<"05">>;
+pad_int( 6) -> <<"06">>;
+pad_int( 7) -> <<"07">>;
+pad_int( 8) -> <<"08">>;
+pad_int( 9) -> <<"09">>;
+pad_int(10) -> <<"10">>;
+pad_int(11) -> <<"11">>;
+pad_int(12) -> <<"12">>;
+pad_int(13) -> <<"13">>;
+pad_int(14) -> <<"14">>;
+pad_int(15) -> <<"15">>;
+pad_int(16) -> <<"16">>;
+pad_int(17) -> <<"17">>;
+pad_int(18) -> <<"18">>;
+pad_int(19) -> <<"19">>;
+pad_int(20) -> <<"20">>;
+pad_int(21) -> <<"21">>;
+pad_int(22) -> <<"22">>;
+pad_int(23) -> <<"23">>;
+pad_int(24) -> <<"24">>;
+pad_int(25) -> <<"25">>;
+pad_int(26) -> <<"26">>;
+pad_int(27) -> <<"27">>;
+pad_int(28) -> <<"28">>;
+pad_int(29) -> <<"29">>;
+pad_int(30) -> <<"30">>;
+pad_int(31) -> <<"31">>;
+pad_int(32) -> <<"32">>;
+pad_int(33) -> <<"33">>;
+pad_int(34) -> <<"34">>;
+pad_int(35) -> <<"35">>;
+pad_int(36) -> <<"36">>;
+pad_int(37) -> <<"37">>;
+pad_int(38) -> <<"38">>;
+pad_int(39) -> <<"39">>;
+pad_int(40) -> <<"40">>;
+pad_int(41) -> <<"41">>;
+pad_int(42) -> <<"42">>;
+pad_int(43) -> <<"43">>;
+pad_int(44) -> <<"44">>;
+pad_int(45) -> <<"45">>;
+pad_int(46) -> <<"46">>;
+pad_int(47) -> <<"47">>;
+pad_int(48) -> <<"48">>;
+pad_int(49) -> <<"49">>;
+pad_int(50) -> <<"50">>;
+pad_int(51) -> <<"51">>;
+pad_int(52) -> <<"52">>;
+pad_int(53) -> <<"53">>;
+pad_int(54) -> <<"54">>;
+pad_int(55) -> <<"55">>;
+pad_int(56) -> <<"56">>;
+pad_int(57) -> <<"57">>;
+pad_int(58) -> <<"58">>;
+pad_int(59) -> <<"59">>;
+pad_int(60) -> <<"60">>;
+pad_int(Int) -> integer_to_binary(Int).
+
+-spec weekday(1..7) -> <<_:24>>.
+weekday(1) -> <<"Mon">>;
+weekday(2) -> <<"Tue">>;
+weekday(3) -> <<"Wed">>;
+weekday(4) -> <<"Thu">>;
+weekday(5) -> <<"Fri">>;
+weekday(6) -> <<"Sat">>;
+weekday(7) -> <<"Sun">>.
+
+-spec month(1..12) -> <<_:24>>.
+month( 1) -> <<"Jan">>;
+month( 2) -> <<"Feb">>;
+month( 3) -> <<"Mar">>;
+month( 4) -> <<"Apr">>;
+month( 5) -> <<"May">>;
+month( 6) -> <<"Jun">>;
+month( 7) -> <<"Jul">>;
+month( 8) -> <<"Aug">>;
+month( 9) -> <<"Sep">>;
+month(10) -> <<"Oct">>;
+month(11) -> <<"Nov">>;
+month(12) -> <<"Dec">>.
+
+-spec year(pos_integer()) -> <<_:32>>.
+year(1970) -> <<"1970">>;
+year(1971) -> <<"1971">>;
+year(1972) -> <<"1972">>;
+year(1973) -> <<"1973">>;
+year(1974) -> <<"1974">>;
+year(1975) -> <<"1975">>;
+year(1976) -> <<"1976">>;
+year(1977) -> <<"1977">>;
+year(1978) -> <<"1978">>;
+year(1979) -> <<"1979">>;
+year(1980) -> <<"1980">>;
+year(1981) -> <<"1981">>;
+year(1982) -> <<"1982">>;
+year(1983) -> <<"1983">>;
+year(1984) -> <<"1984">>;
+year(1985) -> <<"1985">>;
+year(1986) -> <<"1986">>;
+year(1987) -> <<"1987">>;
+year(1988) -> <<"1988">>;
+year(1989) -> <<"1989">>;
+year(1990) -> <<"1990">>;
+year(1991) -> <<"1991">>;
+year(1992) -> <<"1992">>;
+year(1993) -> <<"1993">>;
+year(1994) -> <<"1994">>;
+year(1995) -> <<"1995">>;
+year(1996) -> <<"1996">>;
+year(1997) -> <<"1997">>;
+year(1998) -> <<"1998">>;
+year(1999) -> <<"1999">>;
+year(2000) -> <<"2000">>;
+year(2001) -> <<"2001">>;
+year(2002) -> <<"2002">>;
+year(2003) -> <<"2003">>;
+year(2004) -> <<"2004">>;
+year(2005) -> <<"2005">>;
+year(2006) -> <<"2006">>;
+year(2007) -> <<"2007">>;
+year(2008) -> <<"2008">>;
+year(2009) -> <<"2009">>;
+year(2010) -> <<"2010">>;
+year(2011) -> <<"2011">>;
+year(2012) -> <<"2012">>;
+year(2013) -> <<"2013">>;
+year(2014) -> <<"2014">>;
+year(2015) -> <<"2015">>;
+year(2016) -> <<"2016">>;
+year(2017) -> <<"2017">>;
+year(2018) -> <<"2018">>;
+year(2019) -> <<"2019">>;
+year(2020) -> <<"2020">>;
+year(2021) -> <<"2021">>;
+year(2022) -> <<"2022">>;
+year(2023) -> <<"2023">>;
+year(2024) -> <<"2024">>;
+year(2025) -> <<"2025">>;
+year(2026) -> <<"2026">>;
+year(2027) -> <<"2027">>;
+year(2028) -> <<"2028">>;
+year(2029) -> <<"2029">>;
+year(Year) -> integer_to_binary(Year).
diff --git a/server/_build/default/lib/cowlib/src/cow_hpack.erl b/server/_build/default/lib/cowlib/src/cow_hpack.erl
new file mode 100644
index 0000000..d7ae475
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_hpack.erl
@@ -0,0 +1,1449 @@
+%% Copyright (c) 2015-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% The current implementation is not suitable for use in
+%% intermediaries as the information about headers that
+%% should never be indexed is currently lost.
+
+-module(cow_hpack).
+-dialyzer(no_improper_lists).
+
+-export([init/0]).
+-export([init/1]).
+-export([set_max_size/2]).
+
+-export([decode/1]).
+-export([decode/2]).
+
+-export([encode/1]).
+-export([encode/2]).
+-export([encode/3]).
+
+-record(state, {
+ size = 0 :: non_neg_integer(),
+ max_size = 4096 :: non_neg_integer(),
+ configured_max_size = 4096 :: non_neg_integer(),
+ dyn_table = [] :: [{pos_integer(), {binary(), binary()}}]
+}).
+
+-opaque state() :: #state{}.
+-export_type([state/0]).
+
+-type opts() :: map().
+-export_type([opts/0]).
+
+-ifdef(TEST).
+-include_lib("proper/include/proper.hrl").
+-endif.
+
+%% State initialization.
+
+-spec init() -> state().
+init() ->
+ #state{}.
+
+-spec init(non_neg_integer()) -> state().
+init(MaxSize) ->
+ #state{max_size=MaxSize, configured_max_size=MaxSize}.
+
+%% Update the configured max size.
+%%
+%% When decoding, the local endpoint also needs to send a SETTINGS
+%% frame with this value and it is then up to the remote endpoint
+%% to decide what actual limit it will use. The actual limit is
+%% signaled via dynamic table size updates in the encoded data.
+%%
+%% When encoding, the local endpoint will call this function after
+%% receiving a SETTINGS frame with this value. The encoder will
+%% then use this value as the new max after signaling via a dynamic
+%% table size update. The value given as argument may be lower
+%% than the one received in the SETTINGS.
+
+-spec set_max_size(non_neg_integer(), State) -> State when State::state().
+set_max_size(MaxSize, State) ->
+ State#state{configured_max_size=MaxSize}.
+
+%% Decoding.
+
+-spec decode(binary()) -> {cow_http:headers(), state()}.
+decode(Data) ->
+ decode(Data, init()).
+
+-spec decode(binary(), State) -> {cow_http:headers(), State} when State::state().
+%% Dynamic table size update is only allowed at the beginning of a HEADERS block.
+decode(<< 0:2, 1:1, Rest/bits >>, State=#state{configured_max_size=ConfigMaxSize}) ->
+ {MaxSize, Rest2} = dec_int5(Rest),
+ if
+ MaxSize =< ConfigMaxSize ->
+ State2 = table_update_size(MaxSize, State),
+ decode(Rest2, State2)
+ end;
+decode(Data, State) ->
+ decode(Data, State, []).
+
+decode(<<>>, State, Acc) ->
+ {lists:reverse(Acc), State};
+%% Indexed header field representation.
+decode(<< 1:1, Rest/bits >>, State, Acc) ->
+ dec_indexed(Rest, State, Acc);
+%% Literal header field with incremental indexing: new name.
+decode(<< 0:1, 1:1, 0:6, Rest/bits >>, State, Acc) ->
+ dec_lit_index_new_name(Rest, State, Acc);
+%% Literal header field with incremental indexing: indexed name.
+decode(<< 0:1, 1:1, Rest/bits >>, State, Acc) ->
+ dec_lit_index_indexed_name(Rest, State, Acc);
+%% Literal header field without indexing: new name.
+decode(<< 0:8, Rest/bits >>, State, Acc) ->
+ dec_lit_no_index_new_name(Rest, State, Acc);
+%% Literal header field without indexing: indexed name.
+decode(<< 0:4, Rest/bits >>, State, Acc) ->
+ dec_lit_no_index_indexed_name(Rest, State, Acc);
+%% Literal header field never indexed: new name.
+%% @todo Keep track of "never indexed" headers.
+decode(<< 0:3, 1:1, 0:4, Rest/bits >>, State, Acc) ->
+ dec_lit_no_index_new_name(Rest, State, Acc);
+%% Literal header field never indexed: indexed name.
+%% @todo Keep track of "never indexed" headers.
+decode(<< 0:3, 1:1, Rest/bits >>, State, Acc) ->
+ dec_lit_no_index_indexed_name(Rest, State, Acc).
+
+%% Indexed header field representation.
+
+%% We do the integer decoding inline where appropriate, falling
+%% back to dec_big_int for larger values.
+dec_indexed(<<2#1111111:7, 0:1, Int:7, Rest/bits>>, State, Acc) ->
+ {Name, Value} = table_get(127 + Int, State),
+ decode(Rest, State, [{Name, Value}|Acc]);
+dec_indexed(<<2#1111111:7, Rest0/bits>>, State, Acc) ->
+ {Index, Rest} = dec_big_int(Rest0, 127, 0),
+ {Name, Value} = table_get(Index, State),
+ decode(Rest, State, [{Name, Value}|Acc]);
+dec_indexed(<<Index:7, Rest/bits>>, State, Acc) ->
+ {Name, Value} = table_get(Index, State),
+ decode(Rest, State, [{Name, Value}|Acc]).
+
+%% Literal header field with incremental indexing.
+
+dec_lit_index_new_name(Rest, State, Acc) ->
+ {Name, Rest2} = dec_str(Rest),
+ dec_lit_index(Rest2, State, Acc, Name).
+
+%% We do the integer decoding inline where appropriate, falling
+%% back to dec_big_int for larger values.
+dec_lit_index_indexed_name(<<2#111111:6, 0:1, Int:7, Rest/bits>>, State, Acc) ->
+ Name = table_get_name(63 + Int, State),
+ dec_lit_index(Rest, State, Acc, Name);
+dec_lit_index_indexed_name(<<2#111111:6, Rest0/bits>>, State, Acc) ->
+ {Index, Rest} = dec_big_int(Rest0, 63, 0),
+ Name = table_get_name(Index, State),
+ dec_lit_index(Rest, State, Acc, Name);
+dec_lit_index_indexed_name(<<Index:6, Rest/bits>>, State, Acc) ->
+ Name = table_get_name(Index, State),
+ dec_lit_index(Rest, State, Acc, Name).
+
+dec_lit_index(Rest, State, Acc, Name) ->
+ {Value, Rest2} = dec_str(Rest),
+ State2 = table_insert({Name, Value}, State),
+ decode(Rest2, State2, [{Name, Value}|Acc]).
+
+%% Literal header field without indexing.
+
+dec_lit_no_index_new_name(Rest, State, Acc) ->
+ {Name, Rest2} = dec_str(Rest),
+ dec_lit_no_index(Rest2, State, Acc, Name).
+
+%% We do the integer decoding inline where appropriate, falling
+%% back to dec_big_int for larger values.
+dec_lit_no_index_indexed_name(<<2#1111:4, 0:1, Int:7, Rest/bits>>, State, Acc) ->
+ Name = table_get_name(15 + Int, State),
+ dec_lit_no_index(Rest, State, Acc, Name);
+dec_lit_no_index_indexed_name(<<2#1111:4, Rest0/bits>>, State, Acc) ->
+ {Index, Rest} = dec_big_int(Rest0, 15, 0),
+ Name = table_get_name(Index, State),
+ dec_lit_no_index(Rest, State, Acc, Name);
+dec_lit_no_index_indexed_name(<<Index:4, Rest/bits>>, State, Acc) ->
+ Name = table_get_name(Index, State),
+ dec_lit_no_index(Rest, State, Acc, Name).
+
+dec_lit_no_index(Rest, State, Acc, Name) ->
+ {Value, Rest2} = dec_str(Rest),
+ decode(Rest2, State, [{Name, Value}|Acc]).
+
+%% @todo Literal header field never indexed.
+
+%% Decode an integer.
+
+%% The HPACK format has 4 different integer prefixes length (from 4 to 7)
+%% and each can be used to create an indefinite length integer if all bits
+%% of the prefix are set to 1.
+
+dec_int5(<< 2#11111:5, Rest/bits >>) ->
+ dec_big_int(Rest, 31, 0);
+dec_int5(<< Int:5, Rest/bits >>) ->
+ {Int, Rest}.
+
+dec_big_int(<< 0:1, Value:7, Rest/bits >>, Int, M) ->
+ {Int + (Value bsl M), Rest};
+dec_big_int(<< 1:1, Value:7, Rest/bits >>, Int, M) ->
+ dec_big_int(Rest, Int + (Value bsl M), M + 7).
+
+%% Decode a string.
+
+dec_str(<<0:1, 2#1111111:7, Rest0/bits>>) ->
+ {Length, Rest1} = dec_big_int(Rest0, 127, 0),
+ <<Str:Length/binary, Rest/bits>> = Rest1,
+ {Str, Rest};
+dec_str(<<0:1, Length:7, Rest0/bits>>) ->
+ <<Str:Length/binary, Rest/bits>> = Rest0,
+ {Str, Rest};
+dec_str(<<1:1, 2#1111111:7, Rest0/bits>>) ->
+ {Length, Rest} = dec_big_int(Rest0, 127, 0),
+ dec_huffman(Rest, Length, 0, <<>>);
+dec_str(<<1:1, Length:7, Rest/bits>>) ->
+ dec_huffman(Rest, Length, 0, <<>>).
+
+%% We use a lookup table that allows us to benefit from
+%% the binary match context optimization. A more naive
+%% implementation using bit pattern matching cannot reuse
+%% a match context because it wouldn't always match on
+%% byte boundaries.
+%%
+%% See cow_hpack_dec_huffman_lookup.hrl for more details.
+
+dec_huffman(<<A:4, B:4, R/bits>>, Len, Huff0, Acc) when Len > 1 ->
+ {_, CharA, Huff1} = dec_huffman_lookup(Huff0, A),
+ {_, CharB, Huff} = dec_huffman_lookup(Huff1, B),
+ case {CharA, CharB} of
+ {undefined, undefined} -> dec_huffman(R, Len - 1, Huff, Acc);
+ {CharA, undefined} -> dec_huffman(R, Len - 1, Huff, <<Acc/binary, CharA>>);
+ {undefined, CharB} -> dec_huffman(R, Len - 1, Huff, <<Acc/binary, CharB>>);
+ {CharA, CharB} -> dec_huffman(R, Len - 1, Huff, <<Acc/binary, CharA, CharB>>)
+ end;
+dec_huffman(<<A:4, B:4, Rest/bits>>, 1, Huff0, Acc) ->
+ {_, CharA, Huff} = dec_huffman_lookup(Huff0, A),
+ {ok, CharB, _} = dec_huffman_lookup(Huff, B),
+ case {CharA, CharB} of
+ %% {undefined, undefined} (> 7-bit final padding) is rejected with a crash.
+ {CharA, undefined} ->
+ {<<Acc/binary, CharA>>, Rest};
+ {undefined, CharB} ->
+ {<<Acc/binary, CharB>>, Rest};
+ _ ->
+ {<<Acc/binary, CharA, CharB>>, Rest}
+ end;
+%% Can only be reached when the string length to decode is 0.
+dec_huffman(Rest, 0, _, <<>>) ->
+ {<<>>, Rest}.
+
+-include("cow_hpack_dec_huffman_lookup.hrl").
+
+-ifdef(TEST).
+%% Test case extracted from h2spec.
+decode_reject_eos_test() ->
+ {'EXIT', _} = (catch decode(<<16#0085f2b24a84ff874951fffffffa7f:120>>)),
+ ok.
+
+req_decode_test() ->
+ %% First request (raw then huffman).
+ {Headers1, State1} = decode(<< 16#828684410f7777772e6578616d706c652e636f6d:160 >>),
+ {Headers1, State1} = decode(<< 16#828684418cf1e3c2e5f23a6ba0ab90f4ff:136 >>),
+ Headers1 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>}
+ ],
+ #state{size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = State1,
+ %% Second request (raw then huffman).
+ {Headers2, State2} = decode(<< 16#828684be58086e6f2d6361636865:112 >>, State1),
+ {Headers2, State2} = decode(<< 16#828684be5886a8eb10649cbf:96 >>, State1),
+ Headers2 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"cache-control">>, <<"no-cache">>}
+ ],
+ #state{size=110, dyn_table=[
+ {53,{<<"cache-control">>, <<"no-cache">>}},
+ {57,{<<":authority">>, <<"www.example.com">>}}]} = State2,
+ %% Third request (raw then huffman).
+ {Headers3, State3} = decode(<< 16#828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565:232 >>, State2),
+ {Headers3, State3} = decode(<< 16#828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf:192 >>, State2),
+ Headers3 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"https">>},
+ {<<":path">>, <<"/index.html">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"custom-key">>, <<"custom-value">>}
+ ],
+ #state{size=164, dyn_table=[
+ {54,{<<"custom-key">>, <<"custom-value">>}},
+ {53,{<<"cache-control">>, <<"no-cache">>}},
+ {57,{<<":authority">>, <<"www.example.com">>}}]} = State3,
+ ok.
+
+resp_decode_test() ->
+ %% Use a max_size of 256 to trigger header evictions.
+ State0 = init(256),
+ %% First response (raw then huffman).
+ {Headers1, State1} = decode(<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >>, State0),
+ {Headers1, State1} = decode(<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >>, State0),
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Second response (raw then huffman).
+ {Headers2, State2} = decode(<< 16#4803333037c1c0bf:64 >>, State1),
+ {Headers2, State2} = decode(<< 16#4883640effc1c0bf:64 >>, State1),
+ Headers2 = [
+ {<<":status">>, <<"307">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}}]} = State2,
+ %% Third response (raw then huffman).
+ {Headers3, State3} = decode(<< 16#88c1611d4d6f6e2c203231204f637420323031332032303a31333a323220474d54c05a04677a69707738666f6f3d4153444a4b48514b425a584f5157454f50495541585157454f49553b206d61782d6167653d333630303b2076657273696f6e3d31:784 >>, State2),
+ {Headers3, State3} = decode(<< 16#88c16196d07abe941054d444a8200595040b8166e084a62d1bffc05a839bd9ab77ad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007:632 >>, State2),
+ Headers3 = [
+ {<<":status">>, <<"200">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>},
+ {<<"content-encoding">>, <<"gzip">>},
+ {<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}
+ ],
+ #state{size=215, dyn_table=[
+ {98,{<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}},
+ {52,{<<"content-encoding">>, <<"gzip">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>}}]} = State3,
+ ok.
+
+table_update_decode_test() ->
+ %% Use a max_size of 256 to trigger header evictions
+ %% when the code is not updating the max size.
+ State0 = init(256),
+ %% First response (raw then huffman).
+ {Headers1, State1} = decode(<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >>, State0),
+ {Headers1, State1} = decode(<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >>, State0),
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Set a new configured max_size to avoid header evictions.
+ State2 = set_max_size(512, State1),
+ %% Second response with the table size update (raw then huffman).
+ MaxSize = enc_big_int(512 - 31, <<>>),
+ {Headers2, State3} = decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4803333037c1c0bf:64>>]),
+ State2),
+ {Headers2, State3} = decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4883640effc1c0bf:64>>]),
+ State2),
+ Headers2 = [
+ {<<":status">>, <<"307">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=264, configured_max_size=512, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State3,
+ ok.
+
+table_update_decode_smaller_test() ->
+ %% Use a max_size of 256 to trigger header evictions
+ %% when the code is not updating the max size.
+ State0 = init(256),
+ %% First response (raw then huffman).
+ {Headers1, State1} = decode(<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >>, State0),
+ {Headers1, State1} = decode(<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >>, State0),
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Set a new configured max_size to avoid header evictions.
+ State2 = set_max_size(512, State1),
+ %% Second response with the table size update smaller than the limit (raw then huffman).
+ MaxSize = enc_big_int(400 - 31, <<>>),
+ {Headers2, State3} = decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4803333037c1c0bf:64>>]),
+ State2),
+ {Headers2, State3} = decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4883640effc1c0bf:64>>]),
+ State2),
+ Headers2 = [
+ {<<":status">>, <<"307">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=264, configured_max_size=512, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State3,
+ ok.
+
+table_update_decode_too_large_test() ->
+ %% Use a max_size of 256 to trigger header evictions
+ %% when the code is not updating the max size.
+ State0 = init(256),
+ %% First response (raw then huffman).
+ {Headers1, State1} = decode(<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >>, State0),
+ {Headers1, State1} = decode(<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >>, State0),
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Set a new configured max_size to avoid header evictions.
+ State2 = set_max_size(512, State1),
+ %% Second response with the table size update (raw then huffman).
+ MaxSize = enc_big_int(1024 - 31, <<>>),
+ {'EXIT', _} = (catch decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4803333037c1c0bf:64>>]),
+ State2)),
+ {'EXIT', _} = (catch decode(
+ iolist_to_binary([<< 2#00111111>>, MaxSize, <<16#4883640effc1c0bf:64>>]),
+ State2)),
+ ok.
+
+table_update_decode_zero_test() ->
+ State0 = init(256),
+ %% First response (raw then huffman).
+ {Headers1, State1} = decode(<< 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >>, State0),
+ {Headers1, State1} = decode(<< 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >>, State0),
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Set a new configured max_size to avoid header evictions.
+ State2 = set_max_size(512, State1),
+ %% Second response with the table size update (raw then huffman).
+ %% We set the table size to 0 to evict all values before setting
+ %% it to 512 so we only get the second request indexed.
+ MaxSize = enc_big_int(512 - 31, <<>>),
+ {Headers1, State3} = decode(iolist_to_binary([
+ <<2#00100000, 2#00111111>>, MaxSize,
+ <<16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560>>]),
+ State2),
+ {Headers1, State3} = decode(iolist_to_binary([
+ <<2#00100000, 2#00111111>>, MaxSize,
+ <<16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432>>]),
+ State2),
+ #state{size=222, configured_max_size=512, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State3,
+ ok.
+
+horse_decode_raw() ->
+ horse:repeat(20000,
+ do_horse_decode_raw()
+ ).
+
+do_horse_decode_raw() ->
+ {_, State1} = decode(<<16#828684410f7777772e6578616d706c652e636f6d:160>>),
+ {_, State2} = decode(<<16#828684be58086e6f2d6361636865:112>>, State1),
+ {_, _} = decode(<<16#828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565:232>>, State2),
+ ok.
+
+horse_decode_huffman() ->
+ horse:repeat(20000,
+ do_horse_decode_huffman()
+ ).
+
+do_horse_decode_huffman() ->
+ {_, State1} = decode(<<16#828684418cf1e3c2e5f23a6ba0ab90f4ff:136>>),
+ {_, State2} = decode(<<16#828684be5886a8eb10649cbf:96>>, State1),
+ {_, _} = decode(<<16#828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf:192>>, State2),
+ ok.
+-endif.
+
+%% Encoding.
+
+-spec encode(cow_http:headers()) -> {iodata(), state()}.
+encode(Headers) ->
+ encode(Headers, init(), huffman, []).
+
+-spec encode(cow_http:headers(), State) -> {iodata(), State} when State::state().
+encode(Headers, State=#state{max_size=MaxSize, configured_max_size=MaxSize}) ->
+ encode(Headers, State, huffman, []);
+encode(Headers, State0=#state{configured_max_size=MaxSize}) ->
+ State1 = table_update_size(MaxSize, State0),
+ {Data, State} = encode(Headers, State1, huffman, []),
+ {[enc_int5(MaxSize, 2#001)|Data], State}.
+
+-spec encode(cow_http:headers(), State, opts()) -> {iodata(), State} when State::state().
+encode(Headers, State=#state{max_size=MaxSize, configured_max_size=MaxSize}, Opts) ->
+ encode(Headers, State, huffman_opt(Opts), []);
+encode(Headers, State0=#state{configured_max_size=MaxSize}, Opts) ->
+ State1 = table_update_size(MaxSize, State0),
+ {Data, State} = encode(Headers, State1, huffman_opt(Opts), []),
+ {[enc_int5(MaxSize, 2#001)|Data], State}.
+
+huffman_opt(#{huffman := false}) -> no_huffman;
+huffman_opt(_) -> huffman.
+
+%% @todo Handle cases where no/never indexing is expected.
+encode([], State, _, Acc) ->
+ {lists:reverse(Acc), State};
+encode([{Name, Value0}|Tail], State, HuffmanOpt, Acc) ->
+ %% We conditionally call iolist_to_binary/1 because a small
+ %% but noticeable speed improvement happens when we do this.
+ Value = if
+ is_binary(Value0) -> Value0;
+ true -> iolist_to_binary(Value0)
+ end,
+ Header = {Name, Value},
+ case table_find(Header, State) of
+ %% Indexed header field representation.
+ {field, Index} ->
+ encode(Tail, State, HuffmanOpt,
+ [enc_int7(Index, 2#1)|Acc]);
+ %% Literal header field representation: indexed name.
+ {name, Index} ->
+ State2 = table_insert(Header, State),
+ encode(Tail, State2, HuffmanOpt,
+ [[enc_int6(Index, 2#01)|enc_str(Value, HuffmanOpt)]|Acc]);
+ %% Literal header field representation: new name.
+ not_found ->
+ State2 = table_insert(Header, State),
+ encode(Tail, State2, HuffmanOpt,
+ [[<< 0:1, 1:1, 0:6 >>|[enc_str(Name, HuffmanOpt)|enc_str(Value, HuffmanOpt)]]|Acc])
+ end.
+
+%% Encode an integer.
+
+enc_int5(Int, Prefix) when Int < 31 ->
+ << Prefix:3, Int:5 >>;
+enc_int5(Int, Prefix) ->
+ enc_big_int(Int - 31, << Prefix:3, 2#11111:5 >>).
+
+enc_int6(Int, Prefix) when Int < 63 ->
+ << Prefix:2, Int:6 >>;
+enc_int6(Int, Prefix) ->
+ enc_big_int(Int - 63, << Prefix:2, 2#111111:6 >>).
+
+enc_int7(Int, Prefix) when Int < 127 ->
+ << Prefix:1, Int:7 >>;
+enc_int7(Int, Prefix) ->
+ enc_big_int(Int - 127, << Prefix:1, 2#1111111:7 >>).
+
+enc_big_int(Int, Acc) when Int < 128 ->
+ <<Acc/binary, Int:8>>;
+enc_big_int(Int, Acc) ->
+ enc_big_int(Int bsr 7, <<Acc/binary, 1:1, Int:7>>).
+
+%% Encode a string.
+
+enc_str(Str, huffman) ->
+ Str2 = enc_huffman(Str, <<>>),
+ [enc_int7(byte_size(Str2), 2#1)|Str2];
+enc_str(Str, no_huffman) ->
+ [enc_int7(byte_size(Str), 2#0)|Str].
+
+enc_huffman(<<>>, Acc) ->
+ case bit_size(Acc) rem 8 of
+ 1 -> << Acc/bits, 2#1111111:7 >>;
+ 2 -> << Acc/bits, 2#111111:6 >>;
+ 3 -> << Acc/bits, 2#11111:5 >>;
+ 4 -> << Acc/bits, 2#1111:4 >>;
+ 5 -> << Acc/bits, 2#111:3 >>;
+ 6 -> << Acc/bits, 2#11:2 >>;
+ 7 -> << Acc/bits, 2#1:1 >>;
+ 0 -> Acc
+ end;
+enc_huffman(<< 0, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111000:13 >>);
+enc_huffman(<< 1, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011000:23 >>);
+enc_huffman(<< 2, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100010:28 >>);
+enc_huffman(<< 3, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100011:28 >>);
+enc_huffman(<< 4, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100100:28 >>);
+enc_huffman(<< 5, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100101:28 >>);
+enc_huffman(<< 6, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100110:28 >>);
+enc_huffman(<< 7, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111100111:28 >>);
+enc_huffman(<< 8, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101000:28 >>);
+enc_huffman(<< 9, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101010:24 >>);
+enc_huffman(<< 10, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111111111100:30 >>);
+enc_huffman(<< 11, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101001:28 >>);
+enc_huffman(<< 12, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101010:28 >>);
+enc_huffman(<< 13, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111111111101:30 >>);
+enc_huffman(<< 14, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101011:28 >>);
+enc_huffman(<< 15, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101100:28 >>);
+enc_huffman(<< 16, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101101:28 >>);
+enc_huffman(<< 17, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101110:28 >>);
+enc_huffman(<< 18, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111101111:28 >>);
+enc_huffman(<< 19, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110000:28 >>);
+enc_huffman(<< 20, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110001:28 >>);
+enc_huffman(<< 21, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110010:28 >>);
+enc_huffman(<< 22, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111111111110:30 >>);
+enc_huffman(<< 23, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110011:28 >>);
+enc_huffman(<< 24, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110100:28 >>);
+enc_huffman(<< 25, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110101:28 >>);
+enc_huffman(<< 26, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110110:28 >>);
+enc_huffman(<< 27, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111110111:28 >>);
+enc_huffman(<< 28, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111000:28 >>);
+enc_huffman(<< 29, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111001:28 >>);
+enc_huffman(<< 30, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111010:28 >>);
+enc_huffman(<< 31, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111011:28 >>);
+enc_huffman(<< 32, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#010100:6 >>);
+enc_huffman(<< 33, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111000:10 >>);
+enc_huffman(<< 34, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111001:10 >>);
+enc_huffman(<< 35, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111010:12 >>);
+enc_huffman(<< 36, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111001:13 >>);
+enc_huffman(<< 37, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#010101:6 >>);
+enc_huffman(<< 38, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111000:8 >>);
+enc_huffman(<< 39, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111010:11 >>);
+enc_huffman(<< 40, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111010:10 >>);
+enc_huffman(<< 41, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111011:10 >>);
+enc_huffman(<< 42, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111001:8 >>);
+enc_huffman(<< 43, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111011:11 >>);
+enc_huffman(<< 44, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111010:8 >>);
+enc_huffman(<< 45, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#010110:6 >>);
+enc_huffman(<< 46, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#010111:6 >>);
+enc_huffman(<< 47, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011000:6 >>);
+enc_huffman(<< 48, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00000:5 >>);
+enc_huffman(<< 49, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00001:5 >>);
+enc_huffman(<< 50, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00010:5 >>);
+enc_huffman(<< 51, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011001:6 >>);
+enc_huffman(<< 52, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011010:6 >>);
+enc_huffman(<< 53, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011011:6 >>);
+enc_huffman(<< 54, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011100:6 >>);
+enc_huffman(<< 55, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011101:6 >>);
+enc_huffman(<< 56, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011110:6 >>);
+enc_huffman(<< 57, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#011111:6 >>);
+enc_huffman(<< 58, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1011100:7 >>);
+enc_huffman(<< 59, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111011:8 >>);
+enc_huffman(<< 60, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111100:15 >>);
+enc_huffman(<< 61, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100000:6 >>);
+enc_huffman(<< 62, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111011:12 >>);
+enc_huffman(<< 63, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111100:10 >>);
+enc_huffman(<< 64, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111010:13 >>);
+enc_huffman(<< 65, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100001:6 >>);
+enc_huffman(<< 66, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1011101:7 >>);
+enc_huffman(<< 67, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1011110:7 >>);
+enc_huffman(<< 68, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1011111:7 >>);
+enc_huffman(<< 69, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100000:7 >>);
+enc_huffman(<< 70, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100001:7 >>);
+enc_huffman(<< 71, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100010:7 >>);
+enc_huffman(<< 72, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100011:7 >>);
+enc_huffman(<< 73, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100100:7 >>);
+enc_huffman(<< 74, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100101:7 >>);
+enc_huffman(<< 75, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100110:7 >>);
+enc_huffman(<< 76, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1100111:7 >>);
+enc_huffman(<< 77, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101000:7 >>);
+enc_huffman(<< 78, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101001:7 >>);
+enc_huffman(<< 79, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101010:7 >>);
+enc_huffman(<< 80, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101011:7 >>);
+enc_huffman(<< 81, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101100:7 >>);
+enc_huffman(<< 82, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101101:7 >>);
+enc_huffman(<< 83, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101110:7 >>);
+enc_huffman(<< 84, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1101111:7 >>);
+enc_huffman(<< 85, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110000:7 >>);
+enc_huffman(<< 86, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110001:7 >>);
+enc_huffman(<< 87, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110010:7 >>);
+enc_huffman(<< 88, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111100:8 >>);
+enc_huffman(<< 89, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110011:7 >>);
+enc_huffman(<< 90, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111101:8 >>);
+enc_huffman(<< 91, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111011:13 >>);
+enc_huffman(<< 92, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111110000:19 >>);
+enc_huffman(<< 93, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111100:13 >>);
+enc_huffman(<< 94, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111100:14 >>);
+enc_huffman(<< 95, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100010:6 >>);
+enc_huffman(<< 96, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111101:15 >>);
+enc_huffman(<< 97, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00011:5 >>);
+enc_huffman(<< 98, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100011:6 >>);
+enc_huffman(<< 99, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00100:5 >>);
+enc_huffman(<< 100, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100100:6 >>);
+enc_huffman(<< 101, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00101:5 >>);
+enc_huffman(<< 102, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100101:6 >>);
+enc_huffman(<< 103, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100110:6 >>);
+enc_huffman(<< 104, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#100111:6 >>);
+enc_huffman(<< 105, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00110:5 >>);
+enc_huffman(<< 106, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110100:7 >>);
+enc_huffman(<< 107, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110101:7 >>);
+enc_huffman(<< 108, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101000:6 >>);
+enc_huffman(<< 109, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101001:6 >>);
+enc_huffman(<< 110, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101010:6 >>);
+enc_huffman(<< 111, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#00111:5 >>);
+enc_huffman(<< 112, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101011:6 >>);
+enc_huffman(<< 113, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110110:7 >>);
+enc_huffman(<< 114, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101100:6 >>);
+enc_huffman(<< 115, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#01000:5 >>);
+enc_huffman(<< 116, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#01001:5 >>);
+enc_huffman(<< 117, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#101101:6 >>);
+enc_huffman(<< 118, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1110111:7 >>);
+enc_huffman(<< 119, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111000:7 >>);
+enc_huffman(<< 120, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111001:7 >>);
+enc_huffman(<< 121, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111010:7 >>);
+enc_huffman(<< 122, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111011:7 >>);
+enc_huffman(<< 123, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111110:15 >>);
+enc_huffman(<< 124, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111100:11 >>);
+enc_huffman(<< 125, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111101:14 >>);
+enc_huffman(<< 126, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111101:13 >>);
+enc_huffman(<< 127, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111100:28 >>);
+enc_huffman(<< 128, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111100110:20 >>);
+enc_huffman(<< 129, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010010:22 >>);
+enc_huffman(<< 130, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111100111:20 >>);
+enc_huffman(<< 131, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101000:20 >>);
+enc_huffman(<< 132, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010011:22 >>);
+enc_huffman(<< 133, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010100:22 >>);
+enc_huffman(<< 134, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010101:22 >>);
+enc_huffman(<< 135, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011001:23 >>);
+enc_huffman(<< 136, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010110:22 >>);
+enc_huffman(<< 137, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011010:23 >>);
+enc_huffman(<< 138, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011011:23 >>);
+enc_huffman(<< 139, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011100:23 >>);
+enc_huffman(<< 140, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011101:23 >>);
+enc_huffman(<< 141, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011110:23 >>);
+enc_huffman(<< 142, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101011:24 >>);
+enc_huffman(<< 143, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111011111:23 >>);
+enc_huffman(<< 144, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101100:24 >>);
+enc_huffman(<< 145, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101101:24 >>);
+enc_huffman(<< 146, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111010111:22 >>);
+enc_huffman(<< 147, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100000:23 >>);
+enc_huffman(<< 148, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101110:24 >>);
+enc_huffman(<< 149, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100001:23 >>);
+enc_huffman(<< 150, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100010:23 >>);
+enc_huffman(<< 151, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100011:23 >>);
+enc_huffman(<< 152, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100100:23 >>);
+enc_huffman(<< 153, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111011100:21 >>);
+enc_huffman(<< 154, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011000:22 >>);
+enc_huffman(<< 155, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100101:23 >>);
+enc_huffman(<< 156, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011001:22 >>);
+enc_huffman(<< 157, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100110:23 >>);
+enc_huffman(<< 158, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111100111:23 >>);
+enc_huffman(<< 159, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111101111:24 >>);
+enc_huffman(<< 160, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011010:22 >>);
+enc_huffman(<< 161, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111011101:21 >>);
+enc_huffman(<< 162, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101001:20 >>);
+enc_huffman(<< 163, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011011:22 >>);
+enc_huffman(<< 164, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011100:22 >>);
+enc_huffman(<< 165, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101000:23 >>);
+enc_huffman(<< 166, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101001:23 >>);
+enc_huffman(<< 167, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111011110:21 >>);
+enc_huffman(<< 168, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101010:23 >>);
+enc_huffman(<< 169, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011101:22 >>);
+enc_huffman(<< 170, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011110:22 >>);
+enc_huffman(<< 171, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110000:24 >>);
+enc_huffman(<< 172, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111011111:21 >>);
+enc_huffman(<< 173, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111011111:22 >>);
+enc_huffman(<< 174, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101011:23 >>);
+enc_huffman(<< 175, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101100:23 >>);
+enc_huffman(<< 176, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100000:21 >>);
+enc_huffman(<< 177, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100001:21 >>);
+enc_huffman(<< 178, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100000:22 >>);
+enc_huffman(<< 179, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100010:21 >>);
+enc_huffman(<< 180, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101101:23 >>);
+enc_huffman(<< 181, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100001:22 >>);
+enc_huffman(<< 182, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101110:23 >>);
+enc_huffman(<< 183, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111101111:23 >>);
+enc_huffman(<< 184, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101010:20 >>);
+enc_huffman(<< 185, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100010:22 >>);
+enc_huffman(<< 186, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100011:22 >>);
+enc_huffman(<< 187, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100100:22 >>);
+enc_huffman(<< 188, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111110000:23 >>);
+enc_huffman(<< 189, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100101:22 >>);
+enc_huffman(<< 190, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100110:22 >>);
+enc_huffman(<< 191, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111110001:23 >>);
+enc_huffman(<< 192, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100000:26 >>);
+enc_huffman(<< 193, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100001:26 >>);
+enc_huffman(<< 194, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101011:20 >>);
+enc_huffman(<< 195, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111110001:19 >>);
+enc_huffman(<< 196, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111100111:22 >>);
+enc_huffman(<< 197, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111110010:23 >>);
+enc_huffman(<< 198, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111101000:22 >>);
+enc_huffman(<< 199, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111101100:25 >>);
+enc_huffman(<< 200, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100010:26 >>);
+enc_huffman(<< 201, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100011:26 >>);
+enc_huffman(<< 202, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100100:26 >>);
+enc_huffman(<< 203, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111011110:27 >>);
+enc_huffman(<< 204, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111011111:27 >>);
+enc_huffman(<< 205, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100101:26 >>);
+enc_huffman(<< 206, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110001:24 >>);
+enc_huffman(<< 207, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111101101:25 >>);
+enc_huffman(<< 208, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111110010:19 >>);
+enc_huffman(<< 209, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100011:21 >>);
+enc_huffman(<< 210, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100110:26 >>);
+enc_huffman(<< 211, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100000:27 >>);
+enc_huffman(<< 212, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100001:27 >>);
+enc_huffman(<< 213, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111100111:26 >>);
+enc_huffman(<< 214, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100010:27 >>);
+enc_huffman(<< 215, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110010:24 >>);
+enc_huffman(<< 216, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100100:21 >>);
+enc_huffman(<< 217, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100101:21 >>);
+enc_huffman(<< 218, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101000:26 >>);
+enc_huffman(<< 219, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101001:26 >>);
+enc_huffman(<< 220, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111101:28 >>);
+enc_huffman(<< 221, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100011:27 >>);
+enc_huffman(<< 222, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100100:27 >>);
+enc_huffman(<< 223, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100101:27 >>);
+enc_huffman(<< 224, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101100:20 >>);
+enc_huffman(<< 225, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110011:24 >>);
+enc_huffman(<< 226, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111101101:20 >>);
+enc_huffman(<< 227, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100110:21 >>);
+enc_huffman(<< 228, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111101001:22 >>);
+enc_huffman(<< 229, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111100111:21 >>);
+enc_huffman(<< 230, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111101000:21 >>);
+enc_huffman(<< 231, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111110011:23 >>);
+enc_huffman(<< 232, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111101010:22 >>);
+enc_huffman(<< 233, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111101011:22 >>);
+enc_huffman(<< 234, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111101110:25 >>);
+enc_huffman(<< 235, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111101111:25 >>);
+enc_huffman(<< 236, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110100:24 >>);
+enc_huffman(<< 237, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111110101:24 >>);
+enc_huffman(<< 238, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101010:26 >>);
+enc_huffman(<< 239, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111110100:23 >>);
+enc_huffman(<< 240, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101011:26 >>);
+enc_huffman(<< 241, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100110:27 >>);
+enc_huffman(<< 242, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101100:26 >>);
+enc_huffman(<< 243, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101101:26 >>);
+enc_huffman(<< 244, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111100111:27 >>);
+enc_huffman(<< 245, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101000:27 >>);
+enc_huffman(<< 246, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101001:27 >>);
+enc_huffman(<< 247, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101010:27 >>);
+enc_huffman(<< 248, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101011:27 >>);
+enc_huffman(<< 249, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#1111111111111111111111111110:28 >>);
+enc_huffman(<< 250, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101100:27 >>);
+enc_huffman(<< 251, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101101:27 >>);
+enc_huffman(<< 252, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101110:27 >>);
+enc_huffman(<< 253, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111101111:27 >>);
+enc_huffman(<< 254, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#111111111111111111111110000:27 >>);
+enc_huffman(<< 255, R/bits >>, A) -> enc_huffman(R, << A/bits, 2#11111111111111111111101110:26 >>).
+
+-ifdef(TEST).
+req_encode_test() ->
+ %% First request (raw then huffman).
+ Headers1 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>}
+ ],
+ {Raw1, State1} = encode(Headers1, init(), #{huffman => false}),
+ << 16#828684410f7777772e6578616d706c652e636f6d:160 >> = iolist_to_binary(Raw1),
+ {Huff1, State1} = encode(Headers1),
+ << 16#828684418cf1e3c2e5f23a6ba0ab90f4ff:136 >> = iolist_to_binary(Huff1),
+ #state{size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = State1,
+ %% Second request (raw then huffman).
+ Headers2 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"cache-control">>, <<"no-cache">>}
+ ],
+ {Raw2, State2} = encode(Headers2, State1, #{huffman => false}),
+ << 16#828684be58086e6f2d6361636865:112 >> = iolist_to_binary(Raw2),
+ {Huff2, State2} = encode(Headers2, State1),
+ << 16#828684be5886a8eb10649cbf:96 >> = iolist_to_binary(Huff2),
+ #state{size=110, dyn_table=[
+ {53,{<<"cache-control">>, <<"no-cache">>}},
+ {57,{<<":authority">>, <<"www.example.com">>}}]} = State2,
+ %% Third request (raw then huffman).
+ Headers3 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"https">>},
+ {<<":path">>, <<"/index.html">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"custom-key">>, <<"custom-value">>}
+ ],
+ {Raw3, State3} = encode(Headers3, State2, #{huffman => false}),
+ << 16#828785bf400a637573746f6d2d6b65790c637573746f6d2d76616c7565:232 >> = iolist_to_binary(Raw3),
+ {Huff3, State3} = encode(Headers3, State2),
+ << 16#828785bf408825a849e95ba97d7f8925a849e95bb8e8b4bf:192 >> = iolist_to_binary(Huff3),
+ #state{size=164, dyn_table=[
+ {54,{<<"custom-key">>, <<"custom-value">>}},
+ {53,{<<"cache-control">>, <<"no-cache">>}},
+ {57,{<<":authority">>, <<"www.example.com">>}}]} = State3,
+ ok.
+
+resp_encode_test() ->
+ %% Use a max_size of 256 to trigger header evictions.
+ State0 = init(256),
+ %% First response (raw then huffman).
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ {Raw1, State1} = encode(Headers1, State0, #{huffman => false}),
+ << 16#4803333032580770726976617465611d4d6f6e2c203231204f637420323031332032303a31333a323120474d546e1768747470733a2f2f7777772e6578616d706c652e636f6d:560 >> = iolist_to_binary(Raw1),
+ {Huff1, State1} = encode(Headers1, State0),
+ << 16#488264025885aec3771a4b6196d07abe941054d444a8200595040b8166e082a62d1bff6e919d29ad171863c78f0b97c8e9ae82ae43d3:432 >> = iolist_to_binary(Huff1),
+ #state{size=222, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = State1,
+ %% Second response (raw then huffman).
+ Headers2 = [
+ {<<":status">>, <<"307">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ {Raw2, State2} = encode(Headers2, State1, #{huffman => false}),
+ << 16#4803333037c1c0bf:64 >> = iolist_to_binary(Raw2),
+ {Huff2, State2} = encode(Headers2, State1),
+ << 16#4883640effc1c0bf:64 >> = iolist_to_binary(Huff2),
+ #state{size=222, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}}]} = State2,
+ %% Third response (raw then huffman).
+ Headers3 = [
+ {<<":status">>, <<"200">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>},
+ {<<"content-encoding">>, <<"gzip">>},
+ {<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}
+ ],
+ {Raw3, State3} = encode(Headers3, State2, #{huffman => false}),
+ << 16#88c1611d4d6f6e2c203231204f637420323031332032303a31333a323220474d54c05a04677a69707738666f6f3d4153444a4b48514b425a584f5157454f50495541585157454f49553b206d61782d6167653d333630303b2076657273696f6e3d31:784 >> = iolist_to_binary(Raw3),
+ {Huff3, State3} = encode(Headers3, State2),
+ << 16#88c16196d07abe941054d444a8200595040b8166e084a62d1bffc05a839bd9ab77ad94e7821dd7f2e6c7b335dfdfcd5b3960d5af27087f3672c1ab270fb5291f9587316065c003ed4ee5b1063d5007:632 >> = iolist_to_binary(Huff3),
+ #state{size=215, dyn_table=[
+ {98,{<<"set-cookie">>, <<"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1">>}},
+ {52,{<<"content-encoding">>, <<"gzip">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:22 GMT">>}}]} = State3,
+ ok.
+
+%% This test assumes that table updates work correctly when decoding.
+table_update_encode_test() ->
+ %% Use a max_size of 256 to trigger header evictions
+ %% when the code is not updating the max size.
+ DecState0 = EncState0 = init(256),
+ %% First response.
+ Headers1 = [
+ {<<":status">>, <<"302">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ {Encoded1, EncState1} = encode(Headers1, EncState0),
+ {Headers1, DecState1} = decode(iolist_to_binary(Encoded1), DecState0),
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = DecState1,
+ #state{size=222, configured_max_size=256, dyn_table=[
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = EncState1,
+ %% Set a new configured max_size to avoid header evictions.
+ DecState2 = set_max_size(512, DecState1),
+ EncState2 = set_max_size(512, EncState1),
+ %% Second response.
+ Headers2 = [
+ {<<":status">>, <<"307">>},
+ {<<"cache-control">>, <<"private">>},
+ {<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>},
+ {<<"location">>, <<"https://www.example.com">>}
+ ],
+ {Encoded2, EncState3} = encode(Headers2, EncState2),
+ {Headers2, DecState3} = decode(iolist_to_binary(Encoded2), DecState2),
+ #state{size=264, max_size=512, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = DecState3,
+ #state{size=264, max_size=512, dyn_table=[
+ {42,{<<":status">>, <<"307">>}},
+ {63,{<<"location">>, <<"https://www.example.com">>}},
+ {65,{<<"date">>, <<"Mon, 21 Oct 2013 20:13:21 GMT">>}},
+ {52,{<<"cache-control">>, <<"private">>}},
+ {42,{<<":status">>, <<"302">>}}]} = EncState3,
+ ok.
+
+%% Check that encode/2 is using the new table size after calling
+%% set_max_size/1 and that adding entries larger than the max size
+%% results in an empty table.
+table_update_encode_max_size_0_test() ->
+ %% Encoding starts with default max size
+ EncState0 = init(),
+ %% Decoding starts with max size of 0
+ DecState0 = init(0),
+ %% First request.
+ Headers1 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>}
+ ],
+ {Encoded1, EncState1} = encode(Headers1, EncState0),
+ {Headers1, DecState1} = decode(iolist_to_binary(Encoded1), DecState0),
+ #state{size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = EncState1,
+ #state{size=0, dyn_table=[]} = DecState1,
+ %% Settings received after the first request.
+ EncState2 = set_max_size(0, EncState1),
+ #state{configured_max_size=0, max_size=4096,
+ size=57, dyn_table=[{57,{<<":authority">>, <<"www.example.com">>}}]} = EncState2,
+ %% Second request.
+ Headers2 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"cache-control">>, <<"no-cache">>}
+ ],
+ {Encoded2, EncState3} = encode(Headers2, EncState2),
+ {Headers2, DecState2} = decode(iolist_to_binary(Encoded2), DecState1),
+ #state{configured_max_size=0, max_size=0, size=0, dyn_table=[]} = EncState3,
+ #state{size=0, dyn_table=[]} = DecState2,
+ ok.
+
+encode_iolist_test() ->
+ Headers = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"content-type">>, [<<"image">>,<<"/">>,<<"png">>,<<>>]}
+ ],
+ {_, _} = encode(Headers),
+ ok.
+
+horse_encode_raw() ->
+ horse:repeat(20000,
+ do_horse_encode_raw()
+ ).
+
+do_horse_encode_raw() ->
+ Headers1 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>}
+ ],
+ {_, State1} = encode(Headers1, init(), #{huffman => false}),
+ Headers2 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"cache-control">>, <<"no-cache">>}
+ ],
+ {_, State2} = encode(Headers2, State1, #{huffman => false}),
+ Headers3 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"https">>},
+ {<<":path">>, <<"/index.html">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"custom-key">>, <<"custom-value">>}
+ ],
+ {_, _} = encode(Headers3, State2, #{huffman => false}),
+ ok.
+
+horse_encode_huffman() ->
+ horse:repeat(20000,
+ do_horse_encode_huffman()
+ ).
+
+do_horse_encode_huffman() ->
+ Headers1 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>}
+ ],
+ {_, State1} = encode(Headers1),
+ Headers2 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":path">>, <<"/">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"cache-control">>, <<"no-cache">>}
+ ],
+ {_, State2} = encode(Headers2, State1),
+ Headers3 = [
+ {<<":method">>, <<"GET">>},
+ {<<":scheme">>, <<"https">>},
+ {<<":path">>, <<"/index.html">>},
+ {<<":authority">>, <<"www.example.com">>},
+ {<<"custom-key">>, <<"custom-value">>}
+ ],
+ {_, _} = encode(Headers3, State2),
+ ok.
+-endif.
+
+%% Static and dynamic tables.
+
+%% @todo There must be a more efficient way.
+table_find(Header = {Name, _}, State) ->
+ case table_find_field(Header, State) of
+ not_found ->
+ case table_find_name(Name, State) of
+ NotFound = not_found ->
+ NotFound;
+ Found ->
+ {name, Found}
+ end;
+ Found ->
+ {field, Found}
+ end.
+
+table_find_field({<<":authority">>, <<>>}, _) -> 1;
+table_find_field({<<":method">>, <<"GET">>}, _) -> 2;
+table_find_field({<<":method">>, <<"POST">>}, _) -> 3;
+table_find_field({<<":path">>, <<"/">>}, _) -> 4;
+table_find_field({<<":path">>, <<"/index.html">>}, _) -> 5;
+table_find_field({<<":scheme">>, <<"http">>}, _) -> 6;
+table_find_field({<<":scheme">>, <<"https">>}, _) -> 7;
+table_find_field({<<":status">>, <<"200">>}, _) -> 8;
+table_find_field({<<":status">>, <<"204">>}, _) -> 9;
+table_find_field({<<":status">>, <<"206">>}, _) -> 10;
+table_find_field({<<":status">>, <<"304">>}, _) -> 11;
+table_find_field({<<":status">>, <<"400">>}, _) -> 12;
+table_find_field({<<":status">>, <<"404">>}, _) -> 13;
+table_find_field({<<":status">>, <<"500">>}, _) -> 14;
+table_find_field({<<"accept-charset">>, <<>>}, _) -> 15;
+table_find_field({<<"accept-encoding">>, <<"gzip, deflate">>}, _) -> 16;
+table_find_field({<<"accept-language">>, <<>>}, _) -> 17;
+table_find_field({<<"accept-ranges">>, <<>>}, _) -> 18;
+table_find_field({<<"accept">>, <<>>}, _) -> 19;
+table_find_field({<<"access-control-allow-origin">>, <<>>}, _) -> 20;
+table_find_field({<<"age">>, <<>>}, _) -> 21;
+table_find_field({<<"allow">>, <<>>}, _) -> 22;
+table_find_field({<<"authorization">>, <<>>}, _) -> 23;
+table_find_field({<<"cache-control">>, <<>>}, _) -> 24;
+table_find_field({<<"content-disposition">>, <<>>}, _) -> 25;
+table_find_field({<<"content-encoding">>, <<>>}, _) -> 26;
+table_find_field({<<"content-language">>, <<>>}, _) -> 27;
+table_find_field({<<"content-length">>, <<>>}, _) -> 28;
+table_find_field({<<"content-location">>, <<>>}, _) -> 29;
+table_find_field({<<"content-range">>, <<>>}, _) -> 30;
+table_find_field({<<"content-type">>, <<>>}, _) -> 31;
+table_find_field({<<"cookie">>, <<>>}, _) -> 32;
+table_find_field({<<"date">>, <<>>}, _) -> 33;
+table_find_field({<<"etag">>, <<>>}, _) -> 34;
+table_find_field({<<"expect">>, <<>>}, _) -> 35;
+table_find_field({<<"expires">>, <<>>}, _) -> 36;
+table_find_field({<<"from">>, <<>>}, _) -> 37;
+table_find_field({<<"host">>, <<>>}, _) -> 38;
+table_find_field({<<"if-match">>, <<>>}, _) -> 39;
+table_find_field({<<"if-modified-since">>, <<>>}, _) -> 40;
+table_find_field({<<"if-none-match">>, <<>>}, _) -> 41;
+table_find_field({<<"if-range">>, <<>>}, _) -> 42;
+table_find_field({<<"if-unmodified-since">>, <<>>}, _) -> 43;
+table_find_field({<<"last-modified">>, <<>>}, _) -> 44;
+table_find_field({<<"link">>, <<>>}, _) -> 45;
+table_find_field({<<"location">>, <<>>}, _) -> 46;
+table_find_field({<<"max-forwards">>, <<>>}, _) -> 47;
+table_find_field({<<"proxy-authenticate">>, <<>>}, _) -> 48;
+table_find_field({<<"proxy-authorization">>, <<>>}, _) -> 49;
+table_find_field({<<"range">>, <<>>}, _) -> 50;
+table_find_field({<<"referer">>, <<>>}, _) -> 51;
+table_find_field({<<"refresh">>, <<>>}, _) -> 52;
+table_find_field({<<"retry-after">>, <<>>}, _) -> 53;
+table_find_field({<<"server">>, <<>>}, _) -> 54;
+table_find_field({<<"set-cookie">>, <<>>}, _) -> 55;
+table_find_field({<<"strict-transport-security">>, <<>>}, _) -> 56;
+table_find_field({<<"transfer-encoding">>, <<>>}, _) -> 57;
+table_find_field({<<"user-agent">>, <<>>}, _) -> 58;
+table_find_field({<<"vary">>, <<>>}, _) -> 59;
+table_find_field({<<"via">>, <<>>}, _) -> 60;
+table_find_field({<<"www-authenticate">>, <<>>}, _) -> 61;
+table_find_field(Header, #state{dyn_table=DynamicTable}) ->
+ table_find_field_dyn(Header, DynamicTable, 62).
+
+table_find_field_dyn(_, [], _) -> not_found;
+table_find_field_dyn(Header, [{_, Header}|_], Index) -> Index;
+table_find_field_dyn(Header, [_|Tail], Index) -> table_find_field_dyn(Header, Tail, Index + 1).
+
+table_find_name(<<":authority">>, _) -> 1;
+table_find_name(<<":method">>, _) -> 2;
+table_find_name(<<":path">>, _) -> 4;
+table_find_name(<<":scheme">>, _) -> 6;
+table_find_name(<<":status">>, _) -> 8;
+table_find_name(<<"accept-charset">>, _) -> 15;
+table_find_name(<<"accept-encoding">>, _) -> 16;
+table_find_name(<<"accept-language">>, _) -> 17;
+table_find_name(<<"accept-ranges">>, _) -> 18;
+table_find_name(<<"accept">>, _) -> 19;
+table_find_name(<<"access-control-allow-origin">>, _) -> 20;
+table_find_name(<<"age">>, _) -> 21;
+table_find_name(<<"allow">>, _) -> 22;
+table_find_name(<<"authorization">>, _) -> 23;
+table_find_name(<<"cache-control">>, _) -> 24;
+table_find_name(<<"content-disposition">>, _) -> 25;
+table_find_name(<<"content-encoding">>, _) -> 26;
+table_find_name(<<"content-language">>, _) -> 27;
+table_find_name(<<"content-length">>, _) -> 28;
+table_find_name(<<"content-location">>, _) -> 29;
+table_find_name(<<"content-range">>, _) -> 30;
+table_find_name(<<"content-type">>, _) -> 31;
+table_find_name(<<"cookie">>, _) -> 32;
+table_find_name(<<"date">>, _) -> 33;
+table_find_name(<<"etag">>, _) -> 34;
+table_find_name(<<"expect">>, _) -> 35;
+table_find_name(<<"expires">>, _) -> 36;
+table_find_name(<<"from">>, _) -> 37;
+table_find_name(<<"host">>, _) -> 38;
+table_find_name(<<"if-match">>, _) -> 39;
+table_find_name(<<"if-modified-since">>, _) -> 40;
+table_find_name(<<"if-none-match">>, _) -> 41;
+table_find_name(<<"if-range">>, _) -> 42;
+table_find_name(<<"if-unmodified-since">>, _) -> 43;
+table_find_name(<<"last-modified">>, _) -> 44;
+table_find_name(<<"link">>, _) -> 45;
+table_find_name(<<"location">>, _) -> 46;
+table_find_name(<<"max-forwards">>, _) -> 47;
+table_find_name(<<"proxy-authenticate">>, _) -> 48;
+table_find_name(<<"proxy-authorization">>, _) -> 49;
+table_find_name(<<"range">>, _) -> 50;
+table_find_name(<<"referer">>, _) -> 51;
+table_find_name(<<"refresh">>, _) -> 52;
+table_find_name(<<"retry-after">>, _) -> 53;
+table_find_name(<<"server">>, _) -> 54;
+table_find_name(<<"set-cookie">>, _) -> 55;
+table_find_name(<<"strict-transport-security">>, _) -> 56;
+table_find_name(<<"transfer-encoding">>, _) -> 57;
+table_find_name(<<"user-agent">>, _) -> 58;
+table_find_name(<<"vary">>, _) -> 59;
+table_find_name(<<"via">>, _) -> 60;
+table_find_name(<<"www-authenticate">>, _) -> 61;
+table_find_name(Name, #state{dyn_table=DynamicTable}) ->
+ table_find_name_dyn(Name, DynamicTable, 62).
+
+table_find_name_dyn(_, [], _) -> not_found;
+table_find_name_dyn(Name, [{Name, _}|_], Index) -> Index;
+table_find_name_dyn(Name, [_|Tail], Index) -> table_find_name_dyn(Name, Tail, Index + 1).
+
+table_get(1, _) -> {<<":authority">>, <<>>};
+table_get(2, _) -> {<<":method">>, <<"GET">>};
+table_get(3, _) -> {<<":method">>, <<"POST">>};
+table_get(4, _) -> {<<":path">>, <<"/">>};
+table_get(5, _) -> {<<":path">>, <<"/index.html">>};
+table_get(6, _) -> {<<":scheme">>, <<"http">>};
+table_get(7, _) -> {<<":scheme">>, <<"https">>};
+table_get(8, _) -> {<<":status">>, <<"200">>};
+table_get(9, _) -> {<<":status">>, <<"204">>};
+table_get(10, _) -> {<<":status">>, <<"206">>};
+table_get(11, _) -> {<<":status">>, <<"304">>};
+table_get(12, _) -> {<<":status">>, <<"400">>};
+table_get(13, _) -> {<<":status">>, <<"404">>};
+table_get(14, _) -> {<<":status">>, <<"500">>};
+table_get(15, _) -> {<<"accept-charset">>, <<>>};
+table_get(16, _) -> {<<"accept-encoding">>, <<"gzip, deflate">>};
+table_get(17, _) -> {<<"accept-language">>, <<>>};
+table_get(18, _) -> {<<"accept-ranges">>, <<>>};
+table_get(19, _) -> {<<"accept">>, <<>>};
+table_get(20, _) -> {<<"access-control-allow-origin">>, <<>>};
+table_get(21, _) -> {<<"age">>, <<>>};
+table_get(22, _) -> {<<"allow">>, <<>>};
+table_get(23, _) -> {<<"authorization">>, <<>>};
+table_get(24, _) -> {<<"cache-control">>, <<>>};
+table_get(25, _) -> {<<"content-disposition">>, <<>>};
+table_get(26, _) -> {<<"content-encoding">>, <<>>};
+table_get(27, _) -> {<<"content-language">>, <<>>};
+table_get(28, _) -> {<<"content-length">>, <<>>};
+table_get(29, _) -> {<<"content-location">>, <<>>};
+table_get(30, _) -> {<<"content-range">>, <<>>};
+table_get(31, _) -> {<<"content-type">>, <<>>};
+table_get(32, _) -> {<<"cookie">>, <<>>};
+table_get(33, _) -> {<<"date">>, <<>>};
+table_get(34, _) -> {<<"etag">>, <<>>};
+table_get(35, _) -> {<<"expect">>, <<>>};
+table_get(36, _) -> {<<"expires">>, <<>>};
+table_get(37, _) -> {<<"from">>, <<>>};
+table_get(38, _) -> {<<"host">>, <<>>};
+table_get(39, _) -> {<<"if-match">>, <<>>};
+table_get(40, _) -> {<<"if-modified-since">>, <<>>};
+table_get(41, _) -> {<<"if-none-match">>, <<>>};
+table_get(42, _) -> {<<"if-range">>, <<>>};
+table_get(43, _) -> {<<"if-unmodified-since">>, <<>>};
+table_get(44, _) -> {<<"last-modified">>, <<>>};
+table_get(45, _) -> {<<"link">>, <<>>};
+table_get(46, _) -> {<<"location">>, <<>>};
+table_get(47, _) -> {<<"max-forwards">>, <<>>};
+table_get(48, _) -> {<<"proxy-authenticate">>, <<>>};
+table_get(49, _) -> {<<"proxy-authorization">>, <<>>};
+table_get(50, _) -> {<<"range">>, <<>>};
+table_get(51, _) -> {<<"referer">>, <<>>};
+table_get(52, _) -> {<<"refresh">>, <<>>};
+table_get(53, _) -> {<<"retry-after">>, <<>>};
+table_get(54, _) -> {<<"server">>, <<>>};
+table_get(55, _) -> {<<"set-cookie">>, <<>>};
+table_get(56, _) -> {<<"strict-transport-security">>, <<>>};
+table_get(57, _) -> {<<"transfer-encoding">>, <<>>};
+table_get(58, _) -> {<<"user-agent">>, <<>>};
+table_get(59, _) -> {<<"vary">>, <<>>};
+table_get(60, _) -> {<<"via">>, <<>>};
+table_get(61, _) -> {<<"www-authenticate">>, <<>>};
+table_get(Index, #state{dyn_table=DynamicTable}) ->
+ {_, Header} = lists:nth(Index - 61, DynamicTable),
+ Header.
+
+table_get_name(1, _) -> <<":authority">>;
+table_get_name(2, _) -> <<":method">>;
+table_get_name(3, _) -> <<":method">>;
+table_get_name(4, _) -> <<":path">>;
+table_get_name(5, _) -> <<":path">>;
+table_get_name(6, _) -> <<":scheme">>;
+table_get_name(7, _) -> <<":scheme">>;
+table_get_name(8, _) -> <<":status">>;
+table_get_name(9, _) -> <<":status">>;
+table_get_name(10, _) -> <<":status">>;
+table_get_name(11, _) -> <<":status">>;
+table_get_name(12, _) -> <<":status">>;
+table_get_name(13, _) -> <<":status">>;
+table_get_name(14, _) -> <<":status">>;
+table_get_name(15, _) -> <<"accept-charset">>;
+table_get_name(16, _) -> <<"accept-encoding">>;
+table_get_name(17, _) -> <<"accept-language">>;
+table_get_name(18, _) -> <<"accept-ranges">>;
+table_get_name(19, _) -> <<"accept">>;
+table_get_name(20, _) -> <<"access-control-allow-origin">>;
+table_get_name(21, _) -> <<"age">>;
+table_get_name(22, _) -> <<"allow">>;
+table_get_name(23, _) -> <<"authorization">>;
+table_get_name(24, _) -> <<"cache-control">>;
+table_get_name(25, _) -> <<"content-disposition">>;
+table_get_name(26, _) -> <<"content-encoding">>;
+table_get_name(27, _) -> <<"content-language">>;
+table_get_name(28, _) -> <<"content-length">>;
+table_get_name(29, _) -> <<"content-location">>;
+table_get_name(30, _) -> <<"content-range">>;
+table_get_name(31, _) -> <<"content-type">>;
+table_get_name(32, _) -> <<"cookie">>;
+table_get_name(33, _) -> <<"date">>;
+table_get_name(34, _) -> <<"etag">>;
+table_get_name(35, _) -> <<"expect">>;
+table_get_name(36, _) -> <<"expires">>;
+table_get_name(37, _) -> <<"from">>;
+table_get_name(38, _) -> <<"host">>;
+table_get_name(39, _) -> <<"if-match">>;
+table_get_name(40, _) -> <<"if-modified-since">>;
+table_get_name(41, _) -> <<"if-none-match">>;
+table_get_name(42, _) -> <<"if-range">>;
+table_get_name(43, _) -> <<"if-unmodified-since">>;
+table_get_name(44, _) -> <<"last-modified">>;
+table_get_name(45, _) -> <<"link">>;
+table_get_name(46, _) -> <<"location">>;
+table_get_name(47, _) -> <<"max-forwards">>;
+table_get_name(48, _) -> <<"proxy-authenticate">>;
+table_get_name(49, _) -> <<"proxy-authorization">>;
+table_get_name(50, _) -> <<"range">>;
+table_get_name(51, _) -> <<"referer">>;
+table_get_name(52, _) -> <<"refresh">>;
+table_get_name(53, _) -> <<"retry-after">>;
+table_get_name(54, _) -> <<"server">>;
+table_get_name(55, _) -> <<"set-cookie">>;
+table_get_name(56, _) -> <<"strict-transport-security">>;
+table_get_name(57, _) -> <<"transfer-encoding">>;
+table_get_name(58, _) -> <<"user-agent">>;
+table_get_name(59, _) -> <<"vary">>;
+table_get_name(60, _) -> <<"via">>;
+table_get_name(61, _) -> <<"www-authenticate">>;
+table_get_name(Index, #state{dyn_table=DynamicTable}) ->
+ {_, {Name, _}} = lists:nth(Index - 61, DynamicTable),
+ Name.
+
+table_insert(Entry = {Name, Value}, State=#state{size=Size, max_size=MaxSize, dyn_table=DynamicTable}) ->
+ EntrySize = byte_size(Name) + byte_size(Value) + 32,
+ if
+ EntrySize + Size =< MaxSize ->
+ %% Add entry without eviction
+ State#state{size=Size + EntrySize, dyn_table=[{EntrySize, Entry}|DynamicTable]};
+ EntrySize =< MaxSize ->
+ %% Evict, then add entry
+ {DynamicTable2, Size2} = table_resize(DynamicTable, MaxSize - EntrySize, 0, []),
+ State#state{size=Size2 + EntrySize, dyn_table=[{EntrySize, Entry}|DynamicTable2]};
+ EntrySize > MaxSize ->
+ %% "an attempt to add an entry larger than the
+ %% maximum size causes the table to be emptied
+ %% of all existing entries and results in an
+ %% empty table" (RFC 7541, 4.4)
+ State#state{size=0, dyn_table=[]}
+ end.
+
+table_resize([], _, Size, Acc) ->
+ {lists:reverse(Acc), Size};
+table_resize([{EntrySize, _}|_], MaxSize, Size, Acc) when Size + EntrySize > MaxSize ->
+ {lists:reverse(Acc), Size};
+table_resize([Entry = {EntrySize, _}|Tail], MaxSize, Size, Acc) ->
+ table_resize(Tail, MaxSize, Size + EntrySize, [Entry|Acc]).
+
+table_update_size(0, State) ->
+ State#state{size=0, max_size=0, dyn_table=[]};
+table_update_size(MaxSize, State=#state{size=CurrentSize})
+ when CurrentSize =< MaxSize ->
+ State#state{max_size=MaxSize};
+table_update_size(MaxSize, State=#state{dyn_table=DynTable}) ->
+ {DynTable2, Size} = table_resize(DynTable, MaxSize, 0, []),
+ State#state{size=Size, max_size=MaxSize, dyn_table=DynTable2}.
+
+-ifdef(TEST).
+prop_str_raw() ->
+ ?FORALL(Str, binary(), begin
+ {Str, <<>>} =:= dec_str(iolist_to_binary(enc_str(Str, no_huffman)))
+ end).
+
+prop_str_huffman() ->
+ ?FORALL(Str, binary(), begin
+ {Str, <<>>} =:= dec_str(iolist_to_binary(enc_str(Str, huffman)))
+ end).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_hpack_dec_huffman_lookup.hrl b/server/_build/default/lib/cowlib/src/cow_hpack_dec_huffman_lookup.hrl
new file mode 100644
index 0000000..6e5da31
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_hpack_dec_huffman_lookup.hrl
@@ -0,0 +1,4132 @@
+%% Copyright (c) 2019-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% This lookup function was created by converting the
+%% table from Nginx[1] into a form better suitable for
+%% Erlang/OTP. This particular table takes a byte-sized
+%% state and 4 bits to determine whether to emit a
+%% character and what the next state is. It is most
+%% appropriate for Erlang/OTP because we can benefit
+%% from binary pattern matching optimizations by
+%% matching the binary one byte at a time, calling
+%% this lookup function twice. This and similar
+%% algorithms are discussed here[2] and there[3].
+%%
+%% It is possible to write a lookup table taking
+%% a full byte instead of just 4 bits, but this
+%% would make this function take 65536 clauses instead
+%% of the current 4096. This could be done later
+%% as a further optimization but might not yield
+%% significant improvements.
+%%
+%% [1] https://hg.nginx.org/nginx/file/tip/src/http/v2/ngx_http_v2_huff_decode.c
+%% [2] http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.9.4248&rep=rep1&type=pdf
+%% [3] https://commandlinefanatic.com/cgi-bin/showarticle.cgi?article=art007
+
+dec_huffman_lookup(16#00, 16#0) -> {more, undefined, 16#04};
+dec_huffman_lookup(16#00, 16#1) -> {more, undefined, 16#05};
+dec_huffman_lookup(16#00, 16#2) -> {more, undefined, 16#07};
+dec_huffman_lookup(16#00, 16#3) -> {more, undefined, 16#08};
+dec_huffman_lookup(16#00, 16#4) -> {more, undefined, 16#0b};
+dec_huffman_lookup(16#00, 16#5) -> {more, undefined, 16#0c};
+dec_huffman_lookup(16#00, 16#6) -> {more, undefined, 16#10};
+dec_huffman_lookup(16#00, 16#7) -> {more, undefined, 16#13};
+dec_huffman_lookup(16#00, 16#8) -> {more, undefined, 16#19};
+dec_huffman_lookup(16#00, 16#9) -> {more, undefined, 16#1c};
+dec_huffman_lookup(16#00, 16#a) -> {more, undefined, 16#20};
+dec_huffman_lookup(16#00, 16#b) -> {more, undefined, 16#23};
+dec_huffman_lookup(16#00, 16#c) -> {more, undefined, 16#2a};
+dec_huffman_lookup(16#00, 16#d) -> {more, undefined, 16#31};
+dec_huffman_lookup(16#00, 16#e) -> {more, undefined, 16#39};
+dec_huffman_lookup(16#00, 16#f) -> {ok, undefined, 16#40};
+dec_huffman_lookup(16#01, 16#0) -> {ok, 16#30, 16#00};
+dec_huffman_lookup(16#01, 16#1) -> {ok, 16#31, 16#00};
+dec_huffman_lookup(16#01, 16#2) -> {ok, 16#32, 16#00};
+dec_huffman_lookup(16#01, 16#3) -> {ok, 16#61, 16#00};
+dec_huffman_lookup(16#01, 16#4) -> {ok, 16#63, 16#00};
+dec_huffman_lookup(16#01, 16#5) -> {ok, 16#65, 16#00};
+dec_huffman_lookup(16#01, 16#6) -> {ok, 16#69, 16#00};
+dec_huffman_lookup(16#01, 16#7) -> {ok, 16#6f, 16#00};
+dec_huffman_lookup(16#01, 16#8) -> {ok, 16#73, 16#00};
+dec_huffman_lookup(16#01, 16#9) -> {ok, 16#74, 16#00};
+dec_huffman_lookup(16#01, 16#a) -> {more, undefined, 16#0d};
+dec_huffman_lookup(16#01, 16#b) -> {more, undefined, 16#0e};
+dec_huffman_lookup(16#01, 16#c) -> {more, undefined, 16#11};
+dec_huffman_lookup(16#01, 16#d) -> {more, undefined, 16#12};
+dec_huffman_lookup(16#01, 16#e) -> {more, undefined, 16#14};
+dec_huffman_lookup(16#01, 16#f) -> {more, undefined, 16#15};
+dec_huffman_lookup(16#02, 16#0) -> {more, 16#30, 16#01};
+dec_huffman_lookup(16#02, 16#1) -> {ok, 16#30, 16#16};
+dec_huffman_lookup(16#02, 16#2) -> {more, 16#31, 16#01};
+dec_huffman_lookup(16#02, 16#3) -> {ok, 16#31, 16#16};
+dec_huffman_lookup(16#02, 16#4) -> {more, 16#32, 16#01};
+dec_huffman_lookup(16#02, 16#5) -> {ok, 16#32, 16#16};
+dec_huffman_lookup(16#02, 16#6) -> {more, 16#61, 16#01};
+dec_huffman_lookup(16#02, 16#7) -> {ok, 16#61, 16#16};
+dec_huffman_lookup(16#02, 16#8) -> {more, 16#63, 16#01};
+dec_huffman_lookup(16#02, 16#9) -> {ok, 16#63, 16#16};
+dec_huffman_lookup(16#02, 16#a) -> {more, 16#65, 16#01};
+dec_huffman_lookup(16#02, 16#b) -> {ok, 16#65, 16#16};
+dec_huffman_lookup(16#02, 16#c) -> {more, 16#69, 16#01};
+dec_huffman_lookup(16#02, 16#d) -> {ok, 16#69, 16#16};
+dec_huffman_lookup(16#02, 16#e) -> {more, 16#6f, 16#01};
+dec_huffman_lookup(16#02, 16#f) -> {ok, 16#6f, 16#16};
+dec_huffman_lookup(16#03, 16#0) -> {more, 16#30, 16#02};
+dec_huffman_lookup(16#03, 16#1) -> {more, 16#30, 16#09};
+dec_huffman_lookup(16#03, 16#2) -> {more, 16#30, 16#17};
+dec_huffman_lookup(16#03, 16#3) -> {ok, 16#30, 16#28};
+dec_huffman_lookup(16#03, 16#4) -> {more, 16#31, 16#02};
+dec_huffman_lookup(16#03, 16#5) -> {more, 16#31, 16#09};
+dec_huffman_lookup(16#03, 16#6) -> {more, 16#31, 16#17};
+dec_huffman_lookup(16#03, 16#7) -> {ok, 16#31, 16#28};
+dec_huffman_lookup(16#03, 16#8) -> {more, 16#32, 16#02};
+dec_huffman_lookup(16#03, 16#9) -> {more, 16#32, 16#09};
+dec_huffman_lookup(16#03, 16#a) -> {more, 16#32, 16#17};
+dec_huffman_lookup(16#03, 16#b) -> {ok, 16#32, 16#28};
+dec_huffman_lookup(16#03, 16#c) -> {more, 16#61, 16#02};
+dec_huffman_lookup(16#03, 16#d) -> {more, 16#61, 16#09};
+dec_huffman_lookup(16#03, 16#e) -> {more, 16#61, 16#17};
+dec_huffman_lookup(16#03, 16#f) -> {ok, 16#61, 16#28};
+dec_huffman_lookup(16#04, 16#0) -> {more, 16#30, 16#03};
+dec_huffman_lookup(16#04, 16#1) -> {more, 16#30, 16#06};
+dec_huffman_lookup(16#04, 16#2) -> {more, 16#30, 16#0a};
+dec_huffman_lookup(16#04, 16#3) -> {more, 16#30, 16#0f};
+dec_huffman_lookup(16#04, 16#4) -> {more, 16#30, 16#18};
+dec_huffman_lookup(16#04, 16#5) -> {more, 16#30, 16#1f};
+dec_huffman_lookup(16#04, 16#6) -> {more, 16#30, 16#29};
+dec_huffman_lookup(16#04, 16#7) -> {ok, 16#30, 16#38};
+dec_huffman_lookup(16#04, 16#8) -> {more, 16#31, 16#03};
+dec_huffman_lookup(16#04, 16#9) -> {more, 16#31, 16#06};
+dec_huffman_lookup(16#04, 16#a) -> {more, 16#31, 16#0a};
+dec_huffman_lookup(16#04, 16#b) -> {more, 16#31, 16#0f};
+dec_huffman_lookup(16#04, 16#c) -> {more, 16#31, 16#18};
+dec_huffman_lookup(16#04, 16#d) -> {more, 16#31, 16#1f};
+dec_huffman_lookup(16#04, 16#e) -> {more, 16#31, 16#29};
+dec_huffman_lookup(16#04, 16#f) -> {ok, 16#31, 16#38};
+dec_huffman_lookup(16#05, 16#0) -> {more, 16#32, 16#03};
+dec_huffman_lookup(16#05, 16#1) -> {more, 16#32, 16#06};
+dec_huffman_lookup(16#05, 16#2) -> {more, 16#32, 16#0a};
+dec_huffman_lookup(16#05, 16#3) -> {more, 16#32, 16#0f};
+dec_huffman_lookup(16#05, 16#4) -> {more, 16#32, 16#18};
+dec_huffman_lookup(16#05, 16#5) -> {more, 16#32, 16#1f};
+dec_huffman_lookup(16#05, 16#6) -> {more, 16#32, 16#29};
+dec_huffman_lookup(16#05, 16#7) -> {ok, 16#32, 16#38};
+dec_huffman_lookup(16#05, 16#8) -> {more, 16#61, 16#03};
+dec_huffman_lookup(16#05, 16#9) -> {more, 16#61, 16#06};
+dec_huffman_lookup(16#05, 16#a) -> {more, 16#61, 16#0a};
+dec_huffman_lookup(16#05, 16#b) -> {more, 16#61, 16#0f};
+dec_huffman_lookup(16#05, 16#c) -> {more, 16#61, 16#18};
+dec_huffman_lookup(16#05, 16#d) -> {more, 16#61, 16#1f};
+dec_huffman_lookup(16#05, 16#e) -> {more, 16#61, 16#29};
+dec_huffman_lookup(16#05, 16#f) -> {ok, 16#61, 16#38};
+dec_huffman_lookup(16#06, 16#0) -> {more, 16#63, 16#02};
+dec_huffman_lookup(16#06, 16#1) -> {more, 16#63, 16#09};
+dec_huffman_lookup(16#06, 16#2) -> {more, 16#63, 16#17};
+dec_huffman_lookup(16#06, 16#3) -> {ok, 16#63, 16#28};
+dec_huffman_lookup(16#06, 16#4) -> {more, 16#65, 16#02};
+dec_huffman_lookup(16#06, 16#5) -> {more, 16#65, 16#09};
+dec_huffman_lookup(16#06, 16#6) -> {more, 16#65, 16#17};
+dec_huffman_lookup(16#06, 16#7) -> {ok, 16#65, 16#28};
+dec_huffman_lookup(16#06, 16#8) -> {more, 16#69, 16#02};
+dec_huffman_lookup(16#06, 16#9) -> {more, 16#69, 16#09};
+dec_huffman_lookup(16#06, 16#a) -> {more, 16#69, 16#17};
+dec_huffman_lookup(16#06, 16#b) -> {ok, 16#69, 16#28};
+dec_huffman_lookup(16#06, 16#c) -> {more, 16#6f, 16#02};
+dec_huffman_lookup(16#06, 16#d) -> {more, 16#6f, 16#09};
+dec_huffman_lookup(16#06, 16#e) -> {more, 16#6f, 16#17};
+dec_huffman_lookup(16#06, 16#f) -> {ok, 16#6f, 16#28};
+dec_huffman_lookup(16#07, 16#0) -> {more, 16#63, 16#03};
+dec_huffman_lookup(16#07, 16#1) -> {more, 16#63, 16#06};
+dec_huffman_lookup(16#07, 16#2) -> {more, 16#63, 16#0a};
+dec_huffman_lookup(16#07, 16#3) -> {more, 16#63, 16#0f};
+dec_huffman_lookup(16#07, 16#4) -> {more, 16#63, 16#18};
+dec_huffman_lookup(16#07, 16#5) -> {more, 16#63, 16#1f};
+dec_huffman_lookup(16#07, 16#6) -> {more, 16#63, 16#29};
+dec_huffman_lookup(16#07, 16#7) -> {ok, 16#63, 16#38};
+dec_huffman_lookup(16#07, 16#8) -> {more, 16#65, 16#03};
+dec_huffman_lookup(16#07, 16#9) -> {more, 16#65, 16#06};
+dec_huffman_lookup(16#07, 16#a) -> {more, 16#65, 16#0a};
+dec_huffman_lookup(16#07, 16#b) -> {more, 16#65, 16#0f};
+dec_huffman_lookup(16#07, 16#c) -> {more, 16#65, 16#18};
+dec_huffman_lookup(16#07, 16#d) -> {more, 16#65, 16#1f};
+dec_huffman_lookup(16#07, 16#e) -> {more, 16#65, 16#29};
+dec_huffman_lookup(16#07, 16#f) -> {ok, 16#65, 16#38};
+dec_huffman_lookup(16#08, 16#0) -> {more, 16#69, 16#03};
+dec_huffman_lookup(16#08, 16#1) -> {more, 16#69, 16#06};
+dec_huffman_lookup(16#08, 16#2) -> {more, 16#69, 16#0a};
+dec_huffman_lookup(16#08, 16#3) -> {more, 16#69, 16#0f};
+dec_huffman_lookup(16#08, 16#4) -> {more, 16#69, 16#18};
+dec_huffman_lookup(16#08, 16#5) -> {more, 16#69, 16#1f};
+dec_huffman_lookup(16#08, 16#6) -> {more, 16#69, 16#29};
+dec_huffman_lookup(16#08, 16#7) -> {ok, 16#69, 16#38};
+dec_huffman_lookup(16#08, 16#8) -> {more, 16#6f, 16#03};
+dec_huffman_lookup(16#08, 16#9) -> {more, 16#6f, 16#06};
+dec_huffman_lookup(16#08, 16#a) -> {more, 16#6f, 16#0a};
+dec_huffman_lookup(16#08, 16#b) -> {more, 16#6f, 16#0f};
+dec_huffman_lookup(16#08, 16#c) -> {more, 16#6f, 16#18};
+dec_huffman_lookup(16#08, 16#d) -> {more, 16#6f, 16#1f};
+dec_huffman_lookup(16#08, 16#e) -> {more, 16#6f, 16#29};
+dec_huffman_lookup(16#08, 16#f) -> {ok, 16#6f, 16#38};
+dec_huffman_lookup(16#09, 16#0) -> {more, 16#73, 16#01};
+dec_huffman_lookup(16#09, 16#1) -> {ok, 16#73, 16#16};
+dec_huffman_lookup(16#09, 16#2) -> {more, 16#74, 16#01};
+dec_huffman_lookup(16#09, 16#3) -> {ok, 16#74, 16#16};
+dec_huffman_lookup(16#09, 16#4) -> {ok, 16#20, 16#00};
+dec_huffman_lookup(16#09, 16#5) -> {ok, 16#25, 16#00};
+dec_huffman_lookup(16#09, 16#6) -> {ok, 16#2d, 16#00};
+dec_huffman_lookup(16#09, 16#7) -> {ok, 16#2e, 16#00};
+dec_huffman_lookup(16#09, 16#8) -> {ok, 16#2f, 16#00};
+dec_huffman_lookup(16#09, 16#9) -> {ok, 16#33, 16#00};
+dec_huffman_lookup(16#09, 16#a) -> {ok, 16#34, 16#00};
+dec_huffman_lookup(16#09, 16#b) -> {ok, 16#35, 16#00};
+dec_huffman_lookup(16#09, 16#c) -> {ok, 16#36, 16#00};
+dec_huffman_lookup(16#09, 16#d) -> {ok, 16#37, 16#00};
+dec_huffman_lookup(16#09, 16#e) -> {ok, 16#38, 16#00};
+dec_huffman_lookup(16#09, 16#f) -> {ok, 16#39, 16#00};
+dec_huffman_lookup(16#0a, 16#0) -> {more, 16#73, 16#02};
+dec_huffman_lookup(16#0a, 16#1) -> {more, 16#73, 16#09};
+dec_huffman_lookup(16#0a, 16#2) -> {more, 16#73, 16#17};
+dec_huffman_lookup(16#0a, 16#3) -> {ok, 16#73, 16#28};
+dec_huffman_lookup(16#0a, 16#4) -> {more, 16#74, 16#02};
+dec_huffman_lookup(16#0a, 16#5) -> {more, 16#74, 16#09};
+dec_huffman_lookup(16#0a, 16#6) -> {more, 16#74, 16#17};
+dec_huffman_lookup(16#0a, 16#7) -> {ok, 16#74, 16#28};
+dec_huffman_lookup(16#0a, 16#8) -> {more, 16#20, 16#01};
+dec_huffman_lookup(16#0a, 16#9) -> {ok, 16#20, 16#16};
+dec_huffman_lookup(16#0a, 16#a) -> {more, 16#25, 16#01};
+dec_huffman_lookup(16#0a, 16#b) -> {ok, 16#25, 16#16};
+dec_huffman_lookup(16#0a, 16#c) -> {more, 16#2d, 16#01};
+dec_huffman_lookup(16#0a, 16#d) -> {ok, 16#2d, 16#16};
+dec_huffman_lookup(16#0a, 16#e) -> {more, 16#2e, 16#01};
+dec_huffman_lookup(16#0a, 16#f) -> {ok, 16#2e, 16#16};
+dec_huffman_lookup(16#0b, 16#0) -> {more, 16#73, 16#03};
+dec_huffman_lookup(16#0b, 16#1) -> {more, 16#73, 16#06};
+dec_huffman_lookup(16#0b, 16#2) -> {more, 16#73, 16#0a};
+dec_huffman_lookup(16#0b, 16#3) -> {more, 16#73, 16#0f};
+dec_huffman_lookup(16#0b, 16#4) -> {more, 16#73, 16#18};
+dec_huffman_lookup(16#0b, 16#5) -> {more, 16#73, 16#1f};
+dec_huffman_lookup(16#0b, 16#6) -> {more, 16#73, 16#29};
+dec_huffman_lookup(16#0b, 16#7) -> {ok, 16#73, 16#38};
+dec_huffman_lookup(16#0b, 16#8) -> {more, 16#74, 16#03};
+dec_huffman_lookup(16#0b, 16#9) -> {more, 16#74, 16#06};
+dec_huffman_lookup(16#0b, 16#a) -> {more, 16#74, 16#0a};
+dec_huffman_lookup(16#0b, 16#b) -> {more, 16#74, 16#0f};
+dec_huffman_lookup(16#0b, 16#c) -> {more, 16#74, 16#18};
+dec_huffman_lookup(16#0b, 16#d) -> {more, 16#74, 16#1f};
+dec_huffman_lookup(16#0b, 16#e) -> {more, 16#74, 16#29};
+dec_huffman_lookup(16#0b, 16#f) -> {ok, 16#74, 16#38};
+dec_huffman_lookup(16#0c, 16#0) -> {more, 16#20, 16#02};
+dec_huffman_lookup(16#0c, 16#1) -> {more, 16#20, 16#09};
+dec_huffman_lookup(16#0c, 16#2) -> {more, 16#20, 16#17};
+dec_huffman_lookup(16#0c, 16#3) -> {ok, 16#20, 16#28};
+dec_huffman_lookup(16#0c, 16#4) -> {more, 16#25, 16#02};
+dec_huffman_lookup(16#0c, 16#5) -> {more, 16#25, 16#09};
+dec_huffman_lookup(16#0c, 16#6) -> {more, 16#25, 16#17};
+dec_huffman_lookup(16#0c, 16#7) -> {ok, 16#25, 16#28};
+dec_huffman_lookup(16#0c, 16#8) -> {more, 16#2d, 16#02};
+dec_huffman_lookup(16#0c, 16#9) -> {more, 16#2d, 16#09};
+dec_huffman_lookup(16#0c, 16#a) -> {more, 16#2d, 16#17};
+dec_huffman_lookup(16#0c, 16#b) -> {ok, 16#2d, 16#28};
+dec_huffman_lookup(16#0c, 16#c) -> {more, 16#2e, 16#02};
+dec_huffman_lookup(16#0c, 16#d) -> {more, 16#2e, 16#09};
+dec_huffman_lookup(16#0c, 16#e) -> {more, 16#2e, 16#17};
+dec_huffman_lookup(16#0c, 16#f) -> {ok, 16#2e, 16#28};
+dec_huffman_lookup(16#0d, 16#0) -> {more, 16#20, 16#03};
+dec_huffman_lookup(16#0d, 16#1) -> {more, 16#20, 16#06};
+dec_huffman_lookup(16#0d, 16#2) -> {more, 16#20, 16#0a};
+dec_huffman_lookup(16#0d, 16#3) -> {more, 16#20, 16#0f};
+dec_huffman_lookup(16#0d, 16#4) -> {more, 16#20, 16#18};
+dec_huffman_lookup(16#0d, 16#5) -> {more, 16#20, 16#1f};
+dec_huffman_lookup(16#0d, 16#6) -> {more, 16#20, 16#29};
+dec_huffman_lookup(16#0d, 16#7) -> {ok, 16#20, 16#38};
+dec_huffman_lookup(16#0d, 16#8) -> {more, 16#25, 16#03};
+dec_huffman_lookup(16#0d, 16#9) -> {more, 16#25, 16#06};
+dec_huffman_lookup(16#0d, 16#a) -> {more, 16#25, 16#0a};
+dec_huffman_lookup(16#0d, 16#b) -> {more, 16#25, 16#0f};
+dec_huffman_lookup(16#0d, 16#c) -> {more, 16#25, 16#18};
+dec_huffman_lookup(16#0d, 16#d) -> {more, 16#25, 16#1f};
+dec_huffman_lookup(16#0d, 16#e) -> {more, 16#25, 16#29};
+dec_huffman_lookup(16#0d, 16#f) -> {ok, 16#25, 16#38};
+dec_huffman_lookup(16#0e, 16#0) -> {more, 16#2d, 16#03};
+dec_huffman_lookup(16#0e, 16#1) -> {more, 16#2d, 16#06};
+dec_huffman_lookup(16#0e, 16#2) -> {more, 16#2d, 16#0a};
+dec_huffman_lookup(16#0e, 16#3) -> {more, 16#2d, 16#0f};
+dec_huffman_lookup(16#0e, 16#4) -> {more, 16#2d, 16#18};
+dec_huffman_lookup(16#0e, 16#5) -> {more, 16#2d, 16#1f};
+dec_huffman_lookup(16#0e, 16#6) -> {more, 16#2d, 16#29};
+dec_huffman_lookup(16#0e, 16#7) -> {ok, 16#2d, 16#38};
+dec_huffman_lookup(16#0e, 16#8) -> {more, 16#2e, 16#03};
+dec_huffman_lookup(16#0e, 16#9) -> {more, 16#2e, 16#06};
+dec_huffman_lookup(16#0e, 16#a) -> {more, 16#2e, 16#0a};
+dec_huffman_lookup(16#0e, 16#b) -> {more, 16#2e, 16#0f};
+dec_huffman_lookup(16#0e, 16#c) -> {more, 16#2e, 16#18};
+dec_huffman_lookup(16#0e, 16#d) -> {more, 16#2e, 16#1f};
+dec_huffman_lookup(16#0e, 16#e) -> {more, 16#2e, 16#29};
+dec_huffman_lookup(16#0e, 16#f) -> {ok, 16#2e, 16#38};
+dec_huffman_lookup(16#0f, 16#0) -> {more, 16#2f, 16#01};
+dec_huffman_lookup(16#0f, 16#1) -> {ok, 16#2f, 16#16};
+dec_huffman_lookup(16#0f, 16#2) -> {more, 16#33, 16#01};
+dec_huffman_lookup(16#0f, 16#3) -> {ok, 16#33, 16#16};
+dec_huffman_lookup(16#0f, 16#4) -> {more, 16#34, 16#01};
+dec_huffman_lookup(16#0f, 16#5) -> {ok, 16#34, 16#16};
+dec_huffman_lookup(16#0f, 16#6) -> {more, 16#35, 16#01};
+dec_huffman_lookup(16#0f, 16#7) -> {ok, 16#35, 16#16};
+dec_huffman_lookup(16#0f, 16#8) -> {more, 16#36, 16#01};
+dec_huffman_lookup(16#0f, 16#9) -> {ok, 16#36, 16#16};
+dec_huffman_lookup(16#0f, 16#a) -> {more, 16#37, 16#01};
+dec_huffman_lookup(16#0f, 16#b) -> {ok, 16#37, 16#16};
+dec_huffman_lookup(16#0f, 16#c) -> {more, 16#38, 16#01};
+dec_huffman_lookup(16#0f, 16#d) -> {ok, 16#38, 16#16};
+dec_huffman_lookup(16#0f, 16#e) -> {more, 16#39, 16#01};
+dec_huffman_lookup(16#0f, 16#f) -> {ok, 16#39, 16#16};
+dec_huffman_lookup(16#10, 16#0) -> {more, 16#2f, 16#02};
+dec_huffman_lookup(16#10, 16#1) -> {more, 16#2f, 16#09};
+dec_huffman_lookup(16#10, 16#2) -> {more, 16#2f, 16#17};
+dec_huffman_lookup(16#10, 16#3) -> {ok, 16#2f, 16#28};
+dec_huffman_lookup(16#10, 16#4) -> {more, 16#33, 16#02};
+dec_huffman_lookup(16#10, 16#5) -> {more, 16#33, 16#09};
+dec_huffman_lookup(16#10, 16#6) -> {more, 16#33, 16#17};
+dec_huffman_lookup(16#10, 16#7) -> {ok, 16#33, 16#28};
+dec_huffman_lookup(16#10, 16#8) -> {more, 16#34, 16#02};
+dec_huffman_lookup(16#10, 16#9) -> {more, 16#34, 16#09};
+dec_huffman_lookup(16#10, 16#a) -> {more, 16#34, 16#17};
+dec_huffman_lookup(16#10, 16#b) -> {ok, 16#34, 16#28};
+dec_huffman_lookup(16#10, 16#c) -> {more, 16#35, 16#02};
+dec_huffman_lookup(16#10, 16#d) -> {more, 16#35, 16#09};
+dec_huffman_lookup(16#10, 16#e) -> {more, 16#35, 16#17};
+dec_huffman_lookup(16#10, 16#f) -> {ok, 16#35, 16#28};
+dec_huffman_lookup(16#11, 16#0) -> {more, 16#2f, 16#03};
+dec_huffman_lookup(16#11, 16#1) -> {more, 16#2f, 16#06};
+dec_huffman_lookup(16#11, 16#2) -> {more, 16#2f, 16#0a};
+dec_huffman_lookup(16#11, 16#3) -> {more, 16#2f, 16#0f};
+dec_huffman_lookup(16#11, 16#4) -> {more, 16#2f, 16#18};
+dec_huffman_lookup(16#11, 16#5) -> {more, 16#2f, 16#1f};
+dec_huffman_lookup(16#11, 16#6) -> {more, 16#2f, 16#29};
+dec_huffman_lookup(16#11, 16#7) -> {ok, 16#2f, 16#38};
+dec_huffman_lookup(16#11, 16#8) -> {more, 16#33, 16#03};
+dec_huffman_lookup(16#11, 16#9) -> {more, 16#33, 16#06};
+dec_huffman_lookup(16#11, 16#a) -> {more, 16#33, 16#0a};
+dec_huffman_lookup(16#11, 16#b) -> {more, 16#33, 16#0f};
+dec_huffman_lookup(16#11, 16#c) -> {more, 16#33, 16#18};
+dec_huffman_lookup(16#11, 16#d) -> {more, 16#33, 16#1f};
+dec_huffman_lookup(16#11, 16#e) -> {more, 16#33, 16#29};
+dec_huffman_lookup(16#11, 16#f) -> {ok, 16#33, 16#38};
+dec_huffman_lookup(16#12, 16#0) -> {more, 16#34, 16#03};
+dec_huffman_lookup(16#12, 16#1) -> {more, 16#34, 16#06};
+dec_huffman_lookup(16#12, 16#2) -> {more, 16#34, 16#0a};
+dec_huffman_lookup(16#12, 16#3) -> {more, 16#34, 16#0f};
+dec_huffman_lookup(16#12, 16#4) -> {more, 16#34, 16#18};
+dec_huffman_lookup(16#12, 16#5) -> {more, 16#34, 16#1f};
+dec_huffman_lookup(16#12, 16#6) -> {more, 16#34, 16#29};
+dec_huffman_lookup(16#12, 16#7) -> {ok, 16#34, 16#38};
+dec_huffman_lookup(16#12, 16#8) -> {more, 16#35, 16#03};
+dec_huffman_lookup(16#12, 16#9) -> {more, 16#35, 16#06};
+dec_huffman_lookup(16#12, 16#a) -> {more, 16#35, 16#0a};
+dec_huffman_lookup(16#12, 16#b) -> {more, 16#35, 16#0f};
+dec_huffman_lookup(16#12, 16#c) -> {more, 16#35, 16#18};
+dec_huffman_lookup(16#12, 16#d) -> {more, 16#35, 16#1f};
+dec_huffman_lookup(16#12, 16#e) -> {more, 16#35, 16#29};
+dec_huffman_lookup(16#12, 16#f) -> {ok, 16#35, 16#38};
+dec_huffman_lookup(16#13, 16#0) -> {more, 16#36, 16#02};
+dec_huffman_lookup(16#13, 16#1) -> {more, 16#36, 16#09};
+dec_huffman_lookup(16#13, 16#2) -> {more, 16#36, 16#17};
+dec_huffman_lookup(16#13, 16#3) -> {ok, 16#36, 16#28};
+dec_huffman_lookup(16#13, 16#4) -> {more, 16#37, 16#02};
+dec_huffman_lookup(16#13, 16#5) -> {more, 16#37, 16#09};
+dec_huffman_lookup(16#13, 16#6) -> {more, 16#37, 16#17};
+dec_huffman_lookup(16#13, 16#7) -> {ok, 16#37, 16#28};
+dec_huffman_lookup(16#13, 16#8) -> {more, 16#38, 16#02};
+dec_huffman_lookup(16#13, 16#9) -> {more, 16#38, 16#09};
+dec_huffman_lookup(16#13, 16#a) -> {more, 16#38, 16#17};
+dec_huffman_lookup(16#13, 16#b) -> {ok, 16#38, 16#28};
+dec_huffman_lookup(16#13, 16#c) -> {more, 16#39, 16#02};
+dec_huffman_lookup(16#13, 16#d) -> {more, 16#39, 16#09};
+dec_huffman_lookup(16#13, 16#e) -> {more, 16#39, 16#17};
+dec_huffman_lookup(16#13, 16#f) -> {ok, 16#39, 16#28};
+dec_huffman_lookup(16#14, 16#0) -> {more, 16#36, 16#03};
+dec_huffman_lookup(16#14, 16#1) -> {more, 16#36, 16#06};
+dec_huffman_lookup(16#14, 16#2) -> {more, 16#36, 16#0a};
+dec_huffman_lookup(16#14, 16#3) -> {more, 16#36, 16#0f};
+dec_huffman_lookup(16#14, 16#4) -> {more, 16#36, 16#18};
+dec_huffman_lookup(16#14, 16#5) -> {more, 16#36, 16#1f};
+dec_huffman_lookup(16#14, 16#6) -> {more, 16#36, 16#29};
+dec_huffman_lookup(16#14, 16#7) -> {ok, 16#36, 16#38};
+dec_huffman_lookup(16#14, 16#8) -> {more, 16#37, 16#03};
+dec_huffman_lookup(16#14, 16#9) -> {more, 16#37, 16#06};
+dec_huffman_lookup(16#14, 16#a) -> {more, 16#37, 16#0a};
+dec_huffman_lookup(16#14, 16#b) -> {more, 16#37, 16#0f};
+dec_huffman_lookup(16#14, 16#c) -> {more, 16#37, 16#18};
+dec_huffman_lookup(16#14, 16#d) -> {more, 16#37, 16#1f};
+dec_huffman_lookup(16#14, 16#e) -> {more, 16#37, 16#29};
+dec_huffman_lookup(16#14, 16#f) -> {ok, 16#37, 16#38};
+dec_huffman_lookup(16#15, 16#0) -> {more, 16#38, 16#03};
+dec_huffman_lookup(16#15, 16#1) -> {more, 16#38, 16#06};
+dec_huffman_lookup(16#15, 16#2) -> {more, 16#38, 16#0a};
+dec_huffman_lookup(16#15, 16#3) -> {more, 16#38, 16#0f};
+dec_huffman_lookup(16#15, 16#4) -> {more, 16#38, 16#18};
+dec_huffman_lookup(16#15, 16#5) -> {more, 16#38, 16#1f};
+dec_huffman_lookup(16#15, 16#6) -> {more, 16#38, 16#29};
+dec_huffman_lookup(16#15, 16#7) -> {ok, 16#38, 16#38};
+dec_huffman_lookup(16#15, 16#8) -> {more, 16#39, 16#03};
+dec_huffman_lookup(16#15, 16#9) -> {more, 16#39, 16#06};
+dec_huffman_lookup(16#15, 16#a) -> {more, 16#39, 16#0a};
+dec_huffman_lookup(16#15, 16#b) -> {more, 16#39, 16#0f};
+dec_huffman_lookup(16#15, 16#c) -> {more, 16#39, 16#18};
+dec_huffman_lookup(16#15, 16#d) -> {more, 16#39, 16#1f};
+dec_huffman_lookup(16#15, 16#e) -> {more, 16#39, 16#29};
+dec_huffman_lookup(16#15, 16#f) -> {ok, 16#39, 16#38};
+dec_huffman_lookup(16#16, 16#0) -> {more, undefined, 16#1a};
+dec_huffman_lookup(16#16, 16#1) -> {more, undefined, 16#1b};
+dec_huffman_lookup(16#16, 16#2) -> {more, undefined, 16#1d};
+dec_huffman_lookup(16#16, 16#3) -> {more, undefined, 16#1e};
+dec_huffman_lookup(16#16, 16#4) -> {more, undefined, 16#21};
+dec_huffman_lookup(16#16, 16#5) -> {more, undefined, 16#22};
+dec_huffman_lookup(16#16, 16#6) -> {more, undefined, 16#24};
+dec_huffman_lookup(16#16, 16#7) -> {more, undefined, 16#25};
+dec_huffman_lookup(16#16, 16#8) -> {more, undefined, 16#2b};
+dec_huffman_lookup(16#16, 16#9) -> {more, undefined, 16#2e};
+dec_huffman_lookup(16#16, 16#a) -> {more, undefined, 16#32};
+dec_huffman_lookup(16#16, 16#b) -> {more, undefined, 16#35};
+dec_huffman_lookup(16#16, 16#c) -> {more, undefined, 16#3a};
+dec_huffman_lookup(16#16, 16#d) -> {more, undefined, 16#3d};
+dec_huffman_lookup(16#16, 16#e) -> {more, undefined, 16#41};
+dec_huffman_lookup(16#16, 16#f) -> {ok, undefined, 16#44};
+dec_huffman_lookup(16#17, 16#0) -> {ok, 16#3d, 16#00};
+dec_huffman_lookup(16#17, 16#1) -> {ok, 16#41, 16#00};
+dec_huffman_lookup(16#17, 16#2) -> {ok, 16#5f, 16#00};
+dec_huffman_lookup(16#17, 16#3) -> {ok, 16#62, 16#00};
+dec_huffman_lookup(16#17, 16#4) -> {ok, 16#64, 16#00};
+dec_huffman_lookup(16#17, 16#5) -> {ok, 16#66, 16#00};
+dec_huffman_lookup(16#17, 16#6) -> {ok, 16#67, 16#00};
+dec_huffman_lookup(16#17, 16#7) -> {ok, 16#68, 16#00};
+dec_huffman_lookup(16#17, 16#8) -> {ok, 16#6c, 16#00};
+dec_huffman_lookup(16#17, 16#9) -> {ok, 16#6d, 16#00};
+dec_huffman_lookup(16#17, 16#a) -> {ok, 16#6e, 16#00};
+dec_huffman_lookup(16#17, 16#b) -> {ok, 16#70, 16#00};
+dec_huffman_lookup(16#17, 16#c) -> {ok, 16#72, 16#00};
+dec_huffman_lookup(16#17, 16#d) -> {ok, 16#75, 16#00};
+dec_huffman_lookup(16#17, 16#e) -> {more, undefined, 16#26};
+dec_huffman_lookup(16#17, 16#f) -> {more, undefined, 16#27};
+dec_huffman_lookup(16#18, 16#0) -> {more, 16#3d, 16#01};
+dec_huffman_lookup(16#18, 16#1) -> {ok, 16#3d, 16#16};
+dec_huffman_lookup(16#18, 16#2) -> {more, 16#41, 16#01};
+dec_huffman_lookup(16#18, 16#3) -> {ok, 16#41, 16#16};
+dec_huffman_lookup(16#18, 16#4) -> {more, 16#5f, 16#01};
+dec_huffman_lookup(16#18, 16#5) -> {ok, 16#5f, 16#16};
+dec_huffman_lookup(16#18, 16#6) -> {more, 16#62, 16#01};
+dec_huffman_lookup(16#18, 16#7) -> {ok, 16#62, 16#16};
+dec_huffman_lookup(16#18, 16#8) -> {more, 16#64, 16#01};
+dec_huffman_lookup(16#18, 16#9) -> {ok, 16#64, 16#16};
+dec_huffman_lookup(16#18, 16#a) -> {more, 16#66, 16#01};
+dec_huffman_lookup(16#18, 16#b) -> {ok, 16#66, 16#16};
+dec_huffman_lookup(16#18, 16#c) -> {more, 16#67, 16#01};
+dec_huffman_lookup(16#18, 16#d) -> {ok, 16#67, 16#16};
+dec_huffman_lookup(16#18, 16#e) -> {more, 16#68, 16#01};
+dec_huffman_lookup(16#18, 16#f) -> {ok, 16#68, 16#16};
+dec_huffman_lookup(16#19, 16#0) -> {more, 16#3d, 16#02};
+dec_huffman_lookup(16#19, 16#1) -> {more, 16#3d, 16#09};
+dec_huffman_lookup(16#19, 16#2) -> {more, 16#3d, 16#17};
+dec_huffman_lookup(16#19, 16#3) -> {ok, 16#3d, 16#28};
+dec_huffman_lookup(16#19, 16#4) -> {more, 16#41, 16#02};
+dec_huffman_lookup(16#19, 16#5) -> {more, 16#41, 16#09};
+dec_huffman_lookup(16#19, 16#6) -> {more, 16#41, 16#17};
+dec_huffman_lookup(16#19, 16#7) -> {ok, 16#41, 16#28};
+dec_huffman_lookup(16#19, 16#8) -> {more, 16#5f, 16#02};
+dec_huffman_lookup(16#19, 16#9) -> {more, 16#5f, 16#09};
+dec_huffman_lookup(16#19, 16#a) -> {more, 16#5f, 16#17};
+dec_huffman_lookup(16#19, 16#b) -> {ok, 16#5f, 16#28};
+dec_huffman_lookup(16#19, 16#c) -> {more, 16#62, 16#02};
+dec_huffman_lookup(16#19, 16#d) -> {more, 16#62, 16#09};
+dec_huffman_lookup(16#19, 16#e) -> {more, 16#62, 16#17};
+dec_huffman_lookup(16#19, 16#f) -> {ok, 16#62, 16#28};
+dec_huffman_lookup(16#1a, 16#0) -> {more, 16#3d, 16#03};
+dec_huffman_lookup(16#1a, 16#1) -> {more, 16#3d, 16#06};
+dec_huffman_lookup(16#1a, 16#2) -> {more, 16#3d, 16#0a};
+dec_huffman_lookup(16#1a, 16#3) -> {more, 16#3d, 16#0f};
+dec_huffman_lookup(16#1a, 16#4) -> {more, 16#3d, 16#18};
+dec_huffman_lookup(16#1a, 16#5) -> {more, 16#3d, 16#1f};
+dec_huffman_lookup(16#1a, 16#6) -> {more, 16#3d, 16#29};
+dec_huffman_lookup(16#1a, 16#7) -> {ok, 16#3d, 16#38};
+dec_huffman_lookup(16#1a, 16#8) -> {more, 16#41, 16#03};
+dec_huffman_lookup(16#1a, 16#9) -> {more, 16#41, 16#06};
+dec_huffman_lookup(16#1a, 16#a) -> {more, 16#41, 16#0a};
+dec_huffman_lookup(16#1a, 16#b) -> {more, 16#41, 16#0f};
+dec_huffman_lookup(16#1a, 16#c) -> {more, 16#41, 16#18};
+dec_huffman_lookup(16#1a, 16#d) -> {more, 16#41, 16#1f};
+dec_huffman_lookup(16#1a, 16#e) -> {more, 16#41, 16#29};
+dec_huffman_lookup(16#1a, 16#f) -> {ok, 16#41, 16#38};
+dec_huffman_lookup(16#1b, 16#0) -> {more, 16#5f, 16#03};
+dec_huffman_lookup(16#1b, 16#1) -> {more, 16#5f, 16#06};
+dec_huffman_lookup(16#1b, 16#2) -> {more, 16#5f, 16#0a};
+dec_huffman_lookup(16#1b, 16#3) -> {more, 16#5f, 16#0f};
+dec_huffman_lookup(16#1b, 16#4) -> {more, 16#5f, 16#18};
+dec_huffman_lookup(16#1b, 16#5) -> {more, 16#5f, 16#1f};
+dec_huffman_lookup(16#1b, 16#6) -> {more, 16#5f, 16#29};
+dec_huffman_lookup(16#1b, 16#7) -> {ok, 16#5f, 16#38};
+dec_huffman_lookup(16#1b, 16#8) -> {more, 16#62, 16#03};
+dec_huffman_lookup(16#1b, 16#9) -> {more, 16#62, 16#06};
+dec_huffman_lookup(16#1b, 16#a) -> {more, 16#62, 16#0a};
+dec_huffman_lookup(16#1b, 16#b) -> {more, 16#62, 16#0f};
+dec_huffman_lookup(16#1b, 16#c) -> {more, 16#62, 16#18};
+dec_huffman_lookup(16#1b, 16#d) -> {more, 16#62, 16#1f};
+dec_huffman_lookup(16#1b, 16#e) -> {more, 16#62, 16#29};
+dec_huffman_lookup(16#1b, 16#f) -> {ok, 16#62, 16#38};
+dec_huffman_lookup(16#1c, 16#0) -> {more, 16#64, 16#02};
+dec_huffman_lookup(16#1c, 16#1) -> {more, 16#64, 16#09};
+dec_huffman_lookup(16#1c, 16#2) -> {more, 16#64, 16#17};
+dec_huffman_lookup(16#1c, 16#3) -> {ok, 16#64, 16#28};
+dec_huffman_lookup(16#1c, 16#4) -> {more, 16#66, 16#02};
+dec_huffman_lookup(16#1c, 16#5) -> {more, 16#66, 16#09};
+dec_huffman_lookup(16#1c, 16#6) -> {more, 16#66, 16#17};
+dec_huffman_lookup(16#1c, 16#7) -> {ok, 16#66, 16#28};
+dec_huffman_lookup(16#1c, 16#8) -> {more, 16#67, 16#02};
+dec_huffman_lookup(16#1c, 16#9) -> {more, 16#67, 16#09};
+dec_huffman_lookup(16#1c, 16#a) -> {more, 16#67, 16#17};
+dec_huffman_lookup(16#1c, 16#b) -> {ok, 16#67, 16#28};
+dec_huffman_lookup(16#1c, 16#c) -> {more, 16#68, 16#02};
+dec_huffman_lookup(16#1c, 16#d) -> {more, 16#68, 16#09};
+dec_huffman_lookup(16#1c, 16#e) -> {more, 16#68, 16#17};
+dec_huffman_lookup(16#1c, 16#f) -> {ok, 16#68, 16#28};
+dec_huffman_lookup(16#1d, 16#0) -> {more, 16#64, 16#03};
+dec_huffman_lookup(16#1d, 16#1) -> {more, 16#64, 16#06};
+dec_huffman_lookup(16#1d, 16#2) -> {more, 16#64, 16#0a};
+dec_huffman_lookup(16#1d, 16#3) -> {more, 16#64, 16#0f};
+dec_huffman_lookup(16#1d, 16#4) -> {more, 16#64, 16#18};
+dec_huffman_lookup(16#1d, 16#5) -> {more, 16#64, 16#1f};
+dec_huffman_lookup(16#1d, 16#6) -> {more, 16#64, 16#29};
+dec_huffman_lookup(16#1d, 16#7) -> {ok, 16#64, 16#38};
+dec_huffman_lookup(16#1d, 16#8) -> {more, 16#66, 16#03};
+dec_huffman_lookup(16#1d, 16#9) -> {more, 16#66, 16#06};
+dec_huffman_lookup(16#1d, 16#a) -> {more, 16#66, 16#0a};
+dec_huffman_lookup(16#1d, 16#b) -> {more, 16#66, 16#0f};
+dec_huffman_lookup(16#1d, 16#c) -> {more, 16#66, 16#18};
+dec_huffman_lookup(16#1d, 16#d) -> {more, 16#66, 16#1f};
+dec_huffman_lookup(16#1d, 16#e) -> {more, 16#66, 16#29};
+dec_huffman_lookup(16#1d, 16#f) -> {ok, 16#66, 16#38};
+dec_huffman_lookup(16#1e, 16#0) -> {more, 16#67, 16#03};
+dec_huffman_lookup(16#1e, 16#1) -> {more, 16#67, 16#06};
+dec_huffman_lookup(16#1e, 16#2) -> {more, 16#67, 16#0a};
+dec_huffman_lookup(16#1e, 16#3) -> {more, 16#67, 16#0f};
+dec_huffman_lookup(16#1e, 16#4) -> {more, 16#67, 16#18};
+dec_huffman_lookup(16#1e, 16#5) -> {more, 16#67, 16#1f};
+dec_huffman_lookup(16#1e, 16#6) -> {more, 16#67, 16#29};
+dec_huffman_lookup(16#1e, 16#7) -> {ok, 16#67, 16#38};
+dec_huffman_lookup(16#1e, 16#8) -> {more, 16#68, 16#03};
+dec_huffman_lookup(16#1e, 16#9) -> {more, 16#68, 16#06};
+dec_huffman_lookup(16#1e, 16#a) -> {more, 16#68, 16#0a};
+dec_huffman_lookup(16#1e, 16#b) -> {more, 16#68, 16#0f};
+dec_huffman_lookup(16#1e, 16#c) -> {more, 16#68, 16#18};
+dec_huffman_lookup(16#1e, 16#d) -> {more, 16#68, 16#1f};
+dec_huffman_lookup(16#1e, 16#e) -> {more, 16#68, 16#29};
+dec_huffman_lookup(16#1e, 16#f) -> {ok, 16#68, 16#38};
+dec_huffman_lookup(16#1f, 16#0) -> {more, 16#6c, 16#01};
+dec_huffman_lookup(16#1f, 16#1) -> {ok, 16#6c, 16#16};
+dec_huffman_lookup(16#1f, 16#2) -> {more, 16#6d, 16#01};
+dec_huffman_lookup(16#1f, 16#3) -> {ok, 16#6d, 16#16};
+dec_huffman_lookup(16#1f, 16#4) -> {more, 16#6e, 16#01};
+dec_huffman_lookup(16#1f, 16#5) -> {ok, 16#6e, 16#16};
+dec_huffman_lookup(16#1f, 16#6) -> {more, 16#70, 16#01};
+dec_huffman_lookup(16#1f, 16#7) -> {ok, 16#70, 16#16};
+dec_huffman_lookup(16#1f, 16#8) -> {more, 16#72, 16#01};
+dec_huffman_lookup(16#1f, 16#9) -> {ok, 16#72, 16#16};
+dec_huffman_lookup(16#1f, 16#a) -> {more, 16#75, 16#01};
+dec_huffman_lookup(16#1f, 16#b) -> {ok, 16#75, 16#16};
+dec_huffman_lookup(16#1f, 16#c) -> {ok, 16#3a, 16#00};
+dec_huffman_lookup(16#1f, 16#d) -> {ok, 16#42, 16#00};
+dec_huffman_lookup(16#1f, 16#e) -> {ok, 16#43, 16#00};
+dec_huffman_lookup(16#1f, 16#f) -> {ok, 16#44, 16#00};
+dec_huffman_lookup(16#20, 16#0) -> {more, 16#6c, 16#02};
+dec_huffman_lookup(16#20, 16#1) -> {more, 16#6c, 16#09};
+dec_huffman_lookup(16#20, 16#2) -> {more, 16#6c, 16#17};
+dec_huffman_lookup(16#20, 16#3) -> {ok, 16#6c, 16#28};
+dec_huffman_lookup(16#20, 16#4) -> {more, 16#6d, 16#02};
+dec_huffman_lookup(16#20, 16#5) -> {more, 16#6d, 16#09};
+dec_huffman_lookup(16#20, 16#6) -> {more, 16#6d, 16#17};
+dec_huffman_lookup(16#20, 16#7) -> {ok, 16#6d, 16#28};
+dec_huffman_lookup(16#20, 16#8) -> {more, 16#6e, 16#02};
+dec_huffman_lookup(16#20, 16#9) -> {more, 16#6e, 16#09};
+dec_huffman_lookup(16#20, 16#a) -> {more, 16#6e, 16#17};
+dec_huffman_lookup(16#20, 16#b) -> {ok, 16#6e, 16#28};
+dec_huffman_lookup(16#20, 16#c) -> {more, 16#70, 16#02};
+dec_huffman_lookup(16#20, 16#d) -> {more, 16#70, 16#09};
+dec_huffman_lookup(16#20, 16#e) -> {more, 16#70, 16#17};
+dec_huffman_lookup(16#20, 16#f) -> {ok, 16#70, 16#28};
+dec_huffman_lookup(16#21, 16#0) -> {more, 16#6c, 16#03};
+dec_huffman_lookup(16#21, 16#1) -> {more, 16#6c, 16#06};
+dec_huffman_lookup(16#21, 16#2) -> {more, 16#6c, 16#0a};
+dec_huffman_lookup(16#21, 16#3) -> {more, 16#6c, 16#0f};
+dec_huffman_lookup(16#21, 16#4) -> {more, 16#6c, 16#18};
+dec_huffman_lookup(16#21, 16#5) -> {more, 16#6c, 16#1f};
+dec_huffman_lookup(16#21, 16#6) -> {more, 16#6c, 16#29};
+dec_huffman_lookup(16#21, 16#7) -> {ok, 16#6c, 16#38};
+dec_huffman_lookup(16#21, 16#8) -> {more, 16#6d, 16#03};
+dec_huffman_lookup(16#21, 16#9) -> {more, 16#6d, 16#06};
+dec_huffman_lookup(16#21, 16#a) -> {more, 16#6d, 16#0a};
+dec_huffman_lookup(16#21, 16#b) -> {more, 16#6d, 16#0f};
+dec_huffman_lookup(16#21, 16#c) -> {more, 16#6d, 16#18};
+dec_huffman_lookup(16#21, 16#d) -> {more, 16#6d, 16#1f};
+dec_huffman_lookup(16#21, 16#e) -> {more, 16#6d, 16#29};
+dec_huffman_lookup(16#21, 16#f) -> {ok, 16#6d, 16#38};
+dec_huffman_lookup(16#22, 16#0) -> {more, 16#6e, 16#03};
+dec_huffman_lookup(16#22, 16#1) -> {more, 16#6e, 16#06};
+dec_huffman_lookup(16#22, 16#2) -> {more, 16#6e, 16#0a};
+dec_huffman_lookup(16#22, 16#3) -> {more, 16#6e, 16#0f};
+dec_huffman_lookup(16#22, 16#4) -> {more, 16#6e, 16#18};
+dec_huffman_lookup(16#22, 16#5) -> {more, 16#6e, 16#1f};
+dec_huffman_lookup(16#22, 16#6) -> {more, 16#6e, 16#29};
+dec_huffman_lookup(16#22, 16#7) -> {ok, 16#6e, 16#38};
+dec_huffman_lookup(16#22, 16#8) -> {more, 16#70, 16#03};
+dec_huffman_lookup(16#22, 16#9) -> {more, 16#70, 16#06};
+dec_huffman_lookup(16#22, 16#a) -> {more, 16#70, 16#0a};
+dec_huffman_lookup(16#22, 16#b) -> {more, 16#70, 16#0f};
+dec_huffman_lookup(16#22, 16#c) -> {more, 16#70, 16#18};
+dec_huffman_lookup(16#22, 16#d) -> {more, 16#70, 16#1f};
+dec_huffman_lookup(16#22, 16#e) -> {more, 16#70, 16#29};
+dec_huffman_lookup(16#22, 16#f) -> {ok, 16#70, 16#38};
+dec_huffman_lookup(16#23, 16#0) -> {more, 16#72, 16#02};
+dec_huffman_lookup(16#23, 16#1) -> {more, 16#72, 16#09};
+dec_huffman_lookup(16#23, 16#2) -> {more, 16#72, 16#17};
+dec_huffman_lookup(16#23, 16#3) -> {ok, 16#72, 16#28};
+dec_huffman_lookup(16#23, 16#4) -> {more, 16#75, 16#02};
+dec_huffman_lookup(16#23, 16#5) -> {more, 16#75, 16#09};
+dec_huffman_lookup(16#23, 16#6) -> {more, 16#75, 16#17};
+dec_huffman_lookup(16#23, 16#7) -> {ok, 16#75, 16#28};
+dec_huffman_lookup(16#23, 16#8) -> {more, 16#3a, 16#01};
+dec_huffman_lookup(16#23, 16#9) -> {ok, 16#3a, 16#16};
+dec_huffman_lookup(16#23, 16#a) -> {more, 16#42, 16#01};
+dec_huffman_lookup(16#23, 16#b) -> {ok, 16#42, 16#16};
+dec_huffman_lookup(16#23, 16#c) -> {more, 16#43, 16#01};
+dec_huffman_lookup(16#23, 16#d) -> {ok, 16#43, 16#16};
+dec_huffman_lookup(16#23, 16#e) -> {more, 16#44, 16#01};
+dec_huffman_lookup(16#23, 16#f) -> {ok, 16#44, 16#16};
+dec_huffman_lookup(16#24, 16#0) -> {more, 16#72, 16#03};
+dec_huffman_lookup(16#24, 16#1) -> {more, 16#72, 16#06};
+dec_huffman_lookup(16#24, 16#2) -> {more, 16#72, 16#0a};
+dec_huffman_lookup(16#24, 16#3) -> {more, 16#72, 16#0f};
+dec_huffman_lookup(16#24, 16#4) -> {more, 16#72, 16#18};
+dec_huffman_lookup(16#24, 16#5) -> {more, 16#72, 16#1f};
+dec_huffman_lookup(16#24, 16#6) -> {more, 16#72, 16#29};
+dec_huffman_lookup(16#24, 16#7) -> {ok, 16#72, 16#38};
+dec_huffman_lookup(16#24, 16#8) -> {more, 16#75, 16#03};
+dec_huffman_lookup(16#24, 16#9) -> {more, 16#75, 16#06};
+dec_huffman_lookup(16#24, 16#a) -> {more, 16#75, 16#0a};
+dec_huffman_lookup(16#24, 16#b) -> {more, 16#75, 16#0f};
+dec_huffman_lookup(16#24, 16#c) -> {more, 16#75, 16#18};
+dec_huffman_lookup(16#24, 16#d) -> {more, 16#75, 16#1f};
+dec_huffman_lookup(16#24, 16#e) -> {more, 16#75, 16#29};
+dec_huffman_lookup(16#24, 16#f) -> {ok, 16#75, 16#38};
+dec_huffman_lookup(16#25, 16#0) -> {more, 16#3a, 16#02};
+dec_huffman_lookup(16#25, 16#1) -> {more, 16#3a, 16#09};
+dec_huffman_lookup(16#25, 16#2) -> {more, 16#3a, 16#17};
+dec_huffman_lookup(16#25, 16#3) -> {ok, 16#3a, 16#28};
+dec_huffman_lookup(16#25, 16#4) -> {more, 16#42, 16#02};
+dec_huffman_lookup(16#25, 16#5) -> {more, 16#42, 16#09};
+dec_huffman_lookup(16#25, 16#6) -> {more, 16#42, 16#17};
+dec_huffman_lookup(16#25, 16#7) -> {ok, 16#42, 16#28};
+dec_huffman_lookup(16#25, 16#8) -> {more, 16#43, 16#02};
+dec_huffman_lookup(16#25, 16#9) -> {more, 16#43, 16#09};
+dec_huffman_lookup(16#25, 16#a) -> {more, 16#43, 16#17};
+dec_huffman_lookup(16#25, 16#b) -> {ok, 16#43, 16#28};
+dec_huffman_lookup(16#25, 16#c) -> {more, 16#44, 16#02};
+dec_huffman_lookup(16#25, 16#d) -> {more, 16#44, 16#09};
+dec_huffman_lookup(16#25, 16#e) -> {more, 16#44, 16#17};
+dec_huffman_lookup(16#25, 16#f) -> {ok, 16#44, 16#28};
+dec_huffman_lookup(16#26, 16#0) -> {more, 16#3a, 16#03};
+dec_huffman_lookup(16#26, 16#1) -> {more, 16#3a, 16#06};
+dec_huffman_lookup(16#26, 16#2) -> {more, 16#3a, 16#0a};
+dec_huffman_lookup(16#26, 16#3) -> {more, 16#3a, 16#0f};
+dec_huffman_lookup(16#26, 16#4) -> {more, 16#3a, 16#18};
+dec_huffman_lookup(16#26, 16#5) -> {more, 16#3a, 16#1f};
+dec_huffman_lookup(16#26, 16#6) -> {more, 16#3a, 16#29};
+dec_huffman_lookup(16#26, 16#7) -> {ok, 16#3a, 16#38};
+dec_huffman_lookup(16#26, 16#8) -> {more, 16#42, 16#03};
+dec_huffman_lookup(16#26, 16#9) -> {more, 16#42, 16#06};
+dec_huffman_lookup(16#26, 16#a) -> {more, 16#42, 16#0a};
+dec_huffman_lookup(16#26, 16#b) -> {more, 16#42, 16#0f};
+dec_huffman_lookup(16#26, 16#c) -> {more, 16#42, 16#18};
+dec_huffman_lookup(16#26, 16#d) -> {more, 16#42, 16#1f};
+dec_huffman_lookup(16#26, 16#e) -> {more, 16#42, 16#29};
+dec_huffman_lookup(16#26, 16#f) -> {ok, 16#42, 16#38};
+dec_huffman_lookup(16#27, 16#0) -> {more, 16#43, 16#03};
+dec_huffman_lookup(16#27, 16#1) -> {more, 16#43, 16#06};
+dec_huffman_lookup(16#27, 16#2) -> {more, 16#43, 16#0a};
+dec_huffman_lookup(16#27, 16#3) -> {more, 16#43, 16#0f};
+dec_huffman_lookup(16#27, 16#4) -> {more, 16#43, 16#18};
+dec_huffman_lookup(16#27, 16#5) -> {more, 16#43, 16#1f};
+dec_huffman_lookup(16#27, 16#6) -> {more, 16#43, 16#29};
+dec_huffman_lookup(16#27, 16#7) -> {ok, 16#43, 16#38};
+dec_huffman_lookup(16#27, 16#8) -> {more, 16#44, 16#03};
+dec_huffman_lookup(16#27, 16#9) -> {more, 16#44, 16#06};
+dec_huffman_lookup(16#27, 16#a) -> {more, 16#44, 16#0a};
+dec_huffman_lookup(16#27, 16#b) -> {more, 16#44, 16#0f};
+dec_huffman_lookup(16#27, 16#c) -> {more, 16#44, 16#18};
+dec_huffman_lookup(16#27, 16#d) -> {more, 16#44, 16#1f};
+dec_huffman_lookup(16#27, 16#e) -> {more, 16#44, 16#29};
+dec_huffman_lookup(16#27, 16#f) -> {ok, 16#44, 16#38};
+dec_huffman_lookup(16#28, 16#0) -> {more, undefined, 16#2c};
+dec_huffman_lookup(16#28, 16#1) -> {more, undefined, 16#2d};
+dec_huffman_lookup(16#28, 16#2) -> {more, undefined, 16#2f};
+dec_huffman_lookup(16#28, 16#3) -> {more, undefined, 16#30};
+dec_huffman_lookup(16#28, 16#4) -> {more, undefined, 16#33};
+dec_huffman_lookup(16#28, 16#5) -> {more, undefined, 16#34};
+dec_huffman_lookup(16#28, 16#6) -> {more, undefined, 16#36};
+dec_huffman_lookup(16#28, 16#7) -> {more, undefined, 16#37};
+dec_huffman_lookup(16#28, 16#8) -> {more, undefined, 16#3b};
+dec_huffman_lookup(16#28, 16#9) -> {more, undefined, 16#3c};
+dec_huffman_lookup(16#28, 16#a) -> {more, undefined, 16#3e};
+dec_huffman_lookup(16#28, 16#b) -> {more, undefined, 16#3f};
+dec_huffman_lookup(16#28, 16#c) -> {more, undefined, 16#42};
+dec_huffman_lookup(16#28, 16#d) -> {more, undefined, 16#43};
+dec_huffman_lookup(16#28, 16#e) -> {more, undefined, 16#45};
+dec_huffman_lookup(16#28, 16#f) -> {ok, undefined, 16#48};
+dec_huffman_lookup(16#29, 16#0) -> {ok, 16#45, 16#00};
+dec_huffman_lookup(16#29, 16#1) -> {ok, 16#46, 16#00};
+dec_huffman_lookup(16#29, 16#2) -> {ok, 16#47, 16#00};
+dec_huffman_lookup(16#29, 16#3) -> {ok, 16#48, 16#00};
+dec_huffman_lookup(16#29, 16#4) -> {ok, 16#49, 16#00};
+dec_huffman_lookup(16#29, 16#5) -> {ok, 16#4a, 16#00};
+dec_huffman_lookup(16#29, 16#6) -> {ok, 16#4b, 16#00};
+dec_huffman_lookup(16#29, 16#7) -> {ok, 16#4c, 16#00};
+dec_huffman_lookup(16#29, 16#8) -> {ok, 16#4d, 16#00};
+dec_huffman_lookup(16#29, 16#9) -> {ok, 16#4e, 16#00};
+dec_huffman_lookup(16#29, 16#a) -> {ok, 16#4f, 16#00};
+dec_huffman_lookup(16#29, 16#b) -> {ok, 16#50, 16#00};
+dec_huffman_lookup(16#29, 16#c) -> {ok, 16#51, 16#00};
+dec_huffman_lookup(16#29, 16#d) -> {ok, 16#52, 16#00};
+dec_huffman_lookup(16#29, 16#e) -> {ok, 16#53, 16#00};
+dec_huffman_lookup(16#29, 16#f) -> {ok, 16#54, 16#00};
+dec_huffman_lookup(16#2a, 16#0) -> {more, 16#45, 16#01};
+dec_huffman_lookup(16#2a, 16#1) -> {ok, 16#45, 16#16};
+dec_huffman_lookup(16#2a, 16#2) -> {more, 16#46, 16#01};
+dec_huffman_lookup(16#2a, 16#3) -> {ok, 16#46, 16#16};
+dec_huffman_lookup(16#2a, 16#4) -> {more, 16#47, 16#01};
+dec_huffman_lookup(16#2a, 16#5) -> {ok, 16#47, 16#16};
+dec_huffman_lookup(16#2a, 16#6) -> {more, 16#48, 16#01};
+dec_huffman_lookup(16#2a, 16#7) -> {ok, 16#48, 16#16};
+dec_huffman_lookup(16#2a, 16#8) -> {more, 16#49, 16#01};
+dec_huffman_lookup(16#2a, 16#9) -> {ok, 16#49, 16#16};
+dec_huffman_lookup(16#2a, 16#a) -> {more, 16#4a, 16#01};
+dec_huffman_lookup(16#2a, 16#b) -> {ok, 16#4a, 16#16};
+dec_huffman_lookup(16#2a, 16#c) -> {more, 16#4b, 16#01};
+dec_huffman_lookup(16#2a, 16#d) -> {ok, 16#4b, 16#16};
+dec_huffman_lookup(16#2a, 16#e) -> {more, 16#4c, 16#01};
+dec_huffman_lookup(16#2a, 16#f) -> {ok, 16#4c, 16#16};
+dec_huffman_lookup(16#2b, 16#0) -> {more, 16#45, 16#02};
+dec_huffman_lookup(16#2b, 16#1) -> {more, 16#45, 16#09};
+dec_huffman_lookup(16#2b, 16#2) -> {more, 16#45, 16#17};
+dec_huffman_lookup(16#2b, 16#3) -> {ok, 16#45, 16#28};
+dec_huffman_lookup(16#2b, 16#4) -> {more, 16#46, 16#02};
+dec_huffman_lookup(16#2b, 16#5) -> {more, 16#46, 16#09};
+dec_huffman_lookup(16#2b, 16#6) -> {more, 16#46, 16#17};
+dec_huffman_lookup(16#2b, 16#7) -> {ok, 16#46, 16#28};
+dec_huffman_lookup(16#2b, 16#8) -> {more, 16#47, 16#02};
+dec_huffman_lookup(16#2b, 16#9) -> {more, 16#47, 16#09};
+dec_huffman_lookup(16#2b, 16#a) -> {more, 16#47, 16#17};
+dec_huffman_lookup(16#2b, 16#b) -> {ok, 16#47, 16#28};
+dec_huffman_lookup(16#2b, 16#c) -> {more, 16#48, 16#02};
+dec_huffman_lookup(16#2b, 16#d) -> {more, 16#48, 16#09};
+dec_huffman_lookup(16#2b, 16#e) -> {more, 16#48, 16#17};
+dec_huffman_lookup(16#2b, 16#f) -> {ok, 16#48, 16#28};
+dec_huffman_lookup(16#2c, 16#0) -> {more, 16#45, 16#03};
+dec_huffman_lookup(16#2c, 16#1) -> {more, 16#45, 16#06};
+dec_huffman_lookup(16#2c, 16#2) -> {more, 16#45, 16#0a};
+dec_huffman_lookup(16#2c, 16#3) -> {more, 16#45, 16#0f};
+dec_huffman_lookup(16#2c, 16#4) -> {more, 16#45, 16#18};
+dec_huffman_lookup(16#2c, 16#5) -> {more, 16#45, 16#1f};
+dec_huffman_lookup(16#2c, 16#6) -> {more, 16#45, 16#29};
+dec_huffman_lookup(16#2c, 16#7) -> {ok, 16#45, 16#38};
+dec_huffman_lookup(16#2c, 16#8) -> {more, 16#46, 16#03};
+dec_huffman_lookup(16#2c, 16#9) -> {more, 16#46, 16#06};
+dec_huffman_lookup(16#2c, 16#a) -> {more, 16#46, 16#0a};
+dec_huffman_lookup(16#2c, 16#b) -> {more, 16#46, 16#0f};
+dec_huffman_lookup(16#2c, 16#c) -> {more, 16#46, 16#18};
+dec_huffman_lookup(16#2c, 16#d) -> {more, 16#46, 16#1f};
+dec_huffman_lookup(16#2c, 16#e) -> {more, 16#46, 16#29};
+dec_huffman_lookup(16#2c, 16#f) -> {ok, 16#46, 16#38};
+dec_huffman_lookup(16#2d, 16#0) -> {more, 16#47, 16#03};
+dec_huffman_lookup(16#2d, 16#1) -> {more, 16#47, 16#06};
+dec_huffman_lookup(16#2d, 16#2) -> {more, 16#47, 16#0a};
+dec_huffman_lookup(16#2d, 16#3) -> {more, 16#47, 16#0f};
+dec_huffman_lookup(16#2d, 16#4) -> {more, 16#47, 16#18};
+dec_huffman_lookup(16#2d, 16#5) -> {more, 16#47, 16#1f};
+dec_huffman_lookup(16#2d, 16#6) -> {more, 16#47, 16#29};
+dec_huffman_lookup(16#2d, 16#7) -> {ok, 16#47, 16#38};
+dec_huffman_lookup(16#2d, 16#8) -> {more, 16#48, 16#03};
+dec_huffman_lookup(16#2d, 16#9) -> {more, 16#48, 16#06};
+dec_huffman_lookup(16#2d, 16#a) -> {more, 16#48, 16#0a};
+dec_huffman_lookup(16#2d, 16#b) -> {more, 16#48, 16#0f};
+dec_huffman_lookup(16#2d, 16#c) -> {more, 16#48, 16#18};
+dec_huffman_lookup(16#2d, 16#d) -> {more, 16#48, 16#1f};
+dec_huffman_lookup(16#2d, 16#e) -> {more, 16#48, 16#29};
+dec_huffman_lookup(16#2d, 16#f) -> {ok, 16#48, 16#38};
+dec_huffman_lookup(16#2e, 16#0) -> {more, 16#49, 16#02};
+dec_huffman_lookup(16#2e, 16#1) -> {more, 16#49, 16#09};
+dec_huffman_lookup(16#2e, 16#2) -> {more, 16#49, 16#17};
+dec_huffman_lookup(16#2e, 16#3) -> {ok, 16#49, 16#28};
+dec_huffman_lookup(16#2e, 16#4) -> {more, 16#4a, 16#02};
+dec_huffman_lookup(16#2e, 16#5) -> {more, 16#4a, 16#09};
+dec_huffman_lookup(16#2e, 16#6) -> {more, 16#4a, 16#17};
+dec_huffman_lookup(16#2e, 16#7) -> {ok, 16#4a, 16#28};
+dec_huffman_lookup(16#2e, 16#8) -> {more, 16#4b, 16#02};
+dec_huffman_lookup(16#2e, 16#9) -> {more, 16#4b, 16#09};
+dec_huffman_lookup(16#2e, 16#a) -> {more, 16#4b, 16#17};
+dec_huffman_lookup(16#2e, 16#b) -> {ok, 16#4b, 16#28};
+dec_huffman_lookup(16#2e, 16#c) -> {more, 16#4c, 16#02};
+dec_huffman_lookup(16#2e, 16#d) -> {more, 16#4c, 16#09};
+dec_huffman_lookup(16#2e, 16#e) -> {more, 16#4c, 16#17};
+dec_huffman_lookup(16#2e, 16#f) -> {ok, 16#4c, 16#28};
+dec_huffman_lookup(16#2f, 16#0) -> {more, 16#49, 16#03};
+dec_huffman_lookup(16#2f, 16#1) -> {more, 16#49, 16#06};
+dec_huffman_lookup(16#2f, 16#2) -> {more, 16#49, 16#0a};
+dec_huffman_lookup(16#2f, 16#3) -> {more, 16#49, 16#0f};
+dec_huffman_lookup(16#2f, 16#4) -> {more, 16#49, 16#18};
+dec_huffman_lookup(16#2f, 16#5) -> {more, 16#49, 16#1f};
+dec_huffman_lookup(16#2f, 16#6) -> {more, 16#49, 16#29};
+dec_huffman_lookup(16#2f, 16#7) -> {ok, 16#49, 16#38};
+dec_huffman_lookup(16#2f, 16#8) -> {more, 16#4a, 16#03};
+dec_huffman_lookup(16#2f, 16#9) -> {more, 16#4a, 16#06};
+dec_huffman_lookup(16#2f, 16#a) -> {more, 16#4a, 16#0a};
+dec_huffman_lookup(16#2f, 16#b) -> {more, 16#4a, 16#0f};
+dec_huffman_lookup(16#2f, 16#c) -> {more, 16#4a, 16#18};
+dec_huffman_lookup(16#2f, 16#d) -> {more, 16#4a, 16#1f};
+dec_huffman_lookup(16#2f, 16#e) -> {more, 16#4a, 16#29};
+dec_huffman_lookup(16#2f, 16#f) -> {ok, 16#4a, 16#38};
+dec_huffman_lookup(16#30, 16#0) -> {more, 16#4b, 16#03};
+dec_huffman_lookup(16#30, 16#1) -> {more, 16#4b, 16#06};
+dec_huffman_lookup(16#30, 16#2) -> {more, 16#4b, 16#0a};
+dec_huffman_lookup(16#30, 16#3) -> {more, 16#4b, 16#0f};
+dec_huffman_lookup(16#30, 16#4) -> {more, 16#4b, 16#18};
+dec_huffman_lookup(16#30, 16#5) -> {more, 16#4b, 16#1f};
+dec_huffman_lookup(16#30, 16#6) -> {more, 16#4b, 16#29};
+dec_huffman_lookup(16#30, 16#7) -> {ok, 16#4b, 16#38};
+dec_huffman_lookup(16#30, 16#8) -> {more, 16#4c, 16#03};
+dec_huffman_lookup(16#30, 16#9) -> {more, 16#4c, 16#06};
+dec_huffman_lookup(16#30, 16#a) -> {more, 16#4c, 16#0a};
+dec_huffman_lookup(16#30, 16#b) -> {more, 16#4c, 16#0f};
+dec_huffman_lookup(16#30, 16#c) -> {more, 16#4c, 16#18};
+dec_huffman_lookup(16#30, 16#d) -> {more, 16#4c, 16#1f};
+dec_huffman_lookup(16#30, 16#e) -> {more, 16#4c, 16#29};
+dec_huffman_lookup(16#30, 16#f) -> {ok, 16#4c, 16#38};
+dec_huffman_lookup(16#31, 16#0) -> {more, 16#4d, 16#01};
+dec_huffman_lookup(16#31, 16#1) -> {ok, 16#4d, 16#16};
+dec_huffman_lookup(16#31, 16#2) -> {more, 16#4e, 16#01};
+dec_huffman_lookup(16#31, 16#3) -> {ok, 16#4e, 16#16};
+dec_huffman_lookup(16#31, 16#4) -> {more, 16#4f, 16#01};
+dec_huffman_lookup(16#31, 16#5) -> {ok, 16#4f, 16#16};
+dec_huffman_lookup(16#31, 16#6) -> {more, 16#50, 16#01};
+dec_huffman_lookup(16#31, 16#7) -> {ok, 16#50, 16#16};
+dec_huffman_lookup(16#31, 16#8) -> {more, 16#51, 16#01};
+dec_huffman_lookup(16#31, 16#9) -> {ok, 16#51, 16#16};
+dec_huffman_lookup(16#31, 16#a) -> {more, 16#52, 16#01};
+dec_huffman_lookup(16#31, 16#b) -> {ok, 16#52, 16#16};
+dec_huffman_lookup(16#31, 16#c) -> {more, 16#53, 16#01};
+dec_huffman_lookup(16#31, 16#d) -> {ok, 16#53, 16#16};
+dec_huffman_lookup(16#31, 16#e) -> {more, 16#54, 16#01};
+dec_huffman_lookup(16#31, 16#f) -> {ok, 16#54, 16#16};
+dec_huffman_lookup(16#32, 16#0) -> {more, 16#4d, 16#02};
+dec_huffman_lookup(16#32, 16#1) -> {more, 16#4d, 16#09};
+dec_huffman_lookup(16#32, 16#2) -> {more, 16#4d, 16#17};
+dec_huffman_lookup(16#32, 16#3) -> {ok, 16#4d, 16#28};
+dec_huffman_lookup(16#32, 16#4) -> {more, 16#4e, 16#02};
+dec_huffman_lookup(16#32, 16#5) -> {more, 16#4e, 16#09};
+dec_huffman_lookup(16#32, 16#6) -> {more, 16#4e, 16#17};
+dec_huffman_lookup(16#32, 16#7) -> {ok, 16#4e, 16#28};
+dec_huffman_lookup(16#32, 16#8) -> {more, 16#4f, 16#02};
+dec_huffman_lookup(16#32, 16#9) -> {more, 16#4f, 16#09};
+dec_huffman_lookup(16#32, 16#a) -> {more, 16#4f, 16#17};
+dec_huffman_lookup(16#32, 16#b) -> {ok, 16#4f, 16#28};
+dec_huffman_lookup(16#32, 16#c) -> {more, 16#50, 16#02};
+dec_huffman_lookup(16#32, 16#d) -> {more, 16#50, 16#09};
+dec_huffman_lookup(16#32, 16#e) -> {more, 16#50, 16#17};
+dec_huffman_lookup(16#32, 16#f) -> {ok, 16#50, 16#28};
+dec_huffman_lookup(16#33, 16#0) -> {more, 16#4d, 16#03};
+dec_huffman_lookup(16#33, 16#1) -> {more, 16#4d, 16#06};
+dec_huffman_lookup(16#33, 16#2) -> {more, 16#4d, 16#0a};
+dec_huffman_lookup(16#33, 16#3) -> {more, 16#4d, 16#0f};
+dec_huffman_lookup(16#33, 16#4) -> {more, 16#4d, 16#18};
+dec_huffman_lookup(16#33, 16#5) -> {more, 16#4d, 16#1f};
+dec_huffman_lookup(16#33, 16#6) -> {more, 16#4d, 16#29};
+dec_huffman_lookup(16#33, 16#7) -> {ok, 16#4d, 16#38};
+dec_huffman_lookup(16#33, 16#8) -> {more, 16#4e, 16#03};
+dec_huffman_lookup(16#33, 16#9) -> {more, 16#4e, 16#06};
+dec_huffman_lookup(16#33, 16#a) -> {more, 16#4e, 16#0a};
+dec_huffman_lookup(16#33, 16#b) -> {more, 16#4e, 16#0f};
+dec_huffman_lookup(16#33, 16#c) -> {more, 16#4e, 16#18};
+dec_huffman_lookup(16#33, 16#d) -> {more, 16#4e, 16#1f};
+dec_huffman_lookup(16#33, 16#e) -> {more, 16#4e, 16#29};
+dec_huffman_lookup(16#33, 16#f) -> {ok, 16#4e, 16#38};
+dec_huffman_lookup(16#34, 16#0) -> {more, 16#4f, 16#03};
+dec_huffman_lookup(16#34, 16#1) -> {more, 16#4f, 16#06};
+dec_huffman_lookup(16#34, 16#2) -> {more, 16#4f, 16#0a};
+dec_huffman_lookup(16#34, 16#3) -> {more, 16#4f, 16#0f};
+dec_huffman_lookup(16#34, 16#4) -> {more, 16#4f, 16#18};
+dec_huffman_lookup(16#34, 16#5) -> {more, 16#4f, 16#1f};
+dec_huffman_lookup(16#34, 16#6) -> {more, 16#4f, 16#29};
+dec_huffman_lookup(16#34, 16#7) -> {ok, 16#4f, 16#38};
+dec_huffman_lookup(16#34, 16#8) -> {more, 16#50, 16#03};
+dec_huffman_lookup(16#34, 16#9) -> {more, 16#50, 16#06};
+dec_huffman_lookup(16#34, 16#a) -> {more, 16#50, 16#0a};
+dec_huffman_lookup(16#34, 16#b) -> {more, 16#50, 16#0f};
+dec_huffman_lookup(16#34, 16#c) -> {more, 16#50, 16#18};
+dec_huffman_lookup(16#34, 16#d) -> {more, 16#50, 16#1f};
+dec_huffman_lookup(16#34, 16#e) -> {more, 16#50, 16#29};
+dec_huffman_lookup(16#34, 16#f) -> {ok, 16#50, 16#38};
+dec_huffman_lookup(16#35, 16#0) -> {more, 16#51, 16#02};
+dec_huffman_lookup(16#35, 16#1) -> {more, 16#51, 16#09};
+dec_huffman_lookup(16#35, 16#2) -> {more, 16#51, 16#17};
+dec_huffman_lookup(16#35, 16#3) -> {ok, 16#51, 16#28};
+dec_huffman_lookup(16#35, 16#4) -> {more, 16#52, 16#02};
+dec_huffman_lookup(16#35, 16#5) -> {more, 16#52, 16#09};
+dec_huffman_lookup(16#35, 16#6) -> {more, 16#52, 16#17};
+dec_huffman_lookup(16#35, 16#7) -> {ok, 16#52, 16#28};
+dec_huffman_lookup(16#35, 16#8) -> {more, 16#53, 16#02};
+dec_huffman_lookup(16#35, 16#9) -> {more, 16#53, 16#09};
+dec_huffman_lookup(16#35, 16#a) -> {more, 16#53, 16#17};
+dec_huffman_lookup(16#35, 16#b) -> {ok, 16#53, 16#28};
+dec_huffman_lookup(16#35, 16#c) -> {more, 16#54, 16#02};
+dec_huffman_lookup(16#35, 16#d) -> {more, 16#54, 16#09};
+dec_huffman_lookup(16#35, 16#e) -> {more, 16#54, 16#17};
+dec_huffman_lookup(16#35, 16#f) -> {ok, 16#54, 16#28};
+dec_huffman_lookup(16#36, 16#0) -> {more, 16#51, 16#03};
+dec_huffman_lookup(16#36, 16#1) -> {more, 16#51, 16#06};
+dec_huffman_lookup(16#36, 16#2) -> {more, 16#51, 16#0a};
+dec_huffman_lookup(16#36, 16#3) -> {more, 16#51, 16#0f};
+dec_huffman_lookup(16#36, 16#4) -> {more, 16#51, 16#18};
+dec_huffman_lookup(16#36, 16#5) -> {more, 16#51, 16#1f};
+dec_huffman_lookup(16#36, 16#6) -> {more, 16#51, 16#29};
+dec_huffman_lookup(16#36, 16#7) -> {ok, 16#51, 16#38};
+dec_huffman_lookup(16#36, 16#8) -> {more, 16#52, 16#03};
+dec_huffman_lookup(16#36, 16#9) -> {more, 16#52, 16#06};
+dec_huffman_lookup(16#36, 16#a) -> {more, 16#52, 16#0a};
+dec_huffman_lookup(16#36, 16#b) -> {more, 16#52, 16#0f};
+dec_huffman_lookup(16#36, 16#c) -> {more, 16#52, 16#18};
+dec_huffman_lookup(16#36, 16#d) -> {more, 16#52, 16#1f};
+dec_huffman_lookup(16#36, 16#e) -> {more, 16#52, 16#29};
+dec_huffman_lookup(16#36, 16#f) -> {ok, 16#52, 16#38};
+dec_huffman_lookup(16#37, 16#0) -> {more, 16#53, 16#03};
+dec_huffman_lookup(16#37, 16#1) -> {more, 16#53, 16#06};
+dec_huffman_lookup(16#37, 16#2) -> {more, 16#53, 16#0a};
+dec_huffman_lookup(16#37, 16#3) -> {more, 16#53, 16#0f};
+dec_huffman_lookup(16#37, 16#4) -> {more, 16#53, 16#18};
+dec_huffman_lookup(16#37, 16#5) -> {more, 16#53, 16#1f};
+dec_huffman_lookup(16#37, 16#6) -> {more, 16#53, 16#29};
+dec_huffman_lookup(16#37, 16#7) -> {ok, 16#53, 16#38};
+dec_huffman_lookup(16#37, 16#8) -> {more, 16#54, 16#03};
+dec_huffman_lookup(16#37, 16#9) -> {more, 16#54, 16#06};
+dec_huffman_lookup(16#37, 16#a) -> {more, 16#54, 16#0a};
+dec_huffman_lookup(16#37, 16#b) -> {more, 16#54, 16#0f};
+dec_huffman_lookup(16#37, 16#c) -> {more, 16#54, 16#18};
+dec_huffman_lookup(16#37, 16#d) -> {more, 16#54, 16#1f};
+dec_huffman_lookup(16#37, 16#e) -> {more, 16#54, 16#29};
+dec_huffman_lookup(16#37, 16#f) -> {ok, 16#54, 16#38};
+dec_huffman_lookup(16#38, 16#0) -> {ok, 16#55, 16#00};
+dec_huffman_lookup(16#38, 16#1) -> {ok, 16#56, 16#00};
+dec_huffman_lookup(16#38, 16#2) -> {ok, 16#57, 16#00};
+dec_huffman_lookup(16#38, 16#3) -> {ok, 16#59, 16#00};
+dec_huffman_lookup(16#38, 16#4) -> {ok, 16#6a, 16#00};
+dec_huffman_lookup(16#38, 16#5) -> {ok, 16#6b, 16#00};
+dec_huffman_lookup(16#38, 16#6) -> {ok, 16#71, 16#00};
+dec_huffman_lookup(16#38, 16#7) -> {ok, 16#76, 16#00};
+dec_huffman_lookup(16#38, 16#8) -> {ok, 16#77, 16#00};
+dec_huffman_lookup(16#38, 16#9) -> {ok, 16#78, 16#00};
+dec_huffman_lookup(16#38, 16#a) -> {ok, 16#79, 16#00};
+dec_huffman_lookup(16#38, 16#b) -> {ok, 16#7a, 16#00};
+dec_huffman_lookup(16#38, 16#c) -> {more, undefined, 16#46};
+dec_huffman_lookup(16#38, 16#d) -> {more, undefined, 16#47};
+dec_huffman_lookup(16#38, 16#e) -> {more, undefined, 16#49};
+dec_huffman_lookup(16#38, 16#f) -> {ok, undefined, 16#4a};
+dec_huffman_lookup(16#39, 16#0) -> {more, 16#55, 16#01};
+dec_huffman_lookup(16#39, 16#1) -> {ok, 16#55, 16#16};
+dec_huffman_lookup(16#39, 16#2) -> {more, 16#56, 16#01};
+dec_huffman_lookup(16#39, 16#3) -> {ok, 16#56, 16#16};
+dec_huffman_lookup(16#39, 16#4) -> {more, 16#57, 16#01};
+dec_huffman_lookup(16#39, 16#5) -> {ok, 16#57, 16#16};
+dec_huffman_lookup(16#39, 16#6) -> {more, 16#59, 16#01};
+dec_huffman_lookup(16#39, 16#7) -> {ok, 16#59, 16#16};
+dec_huffman_lookup(16#39, 16#8) -> {more, 16#6a, 16#01};
+dec_huffman_lookup(16#39, 16#9) -> {ok, 16#6a, 16#16};
+dec_huffman_lookup(16#39, 16#a) -> {more, 16#6b, 16#01};
+dec_huffman_lookup(16#39, 16#b) -> {ok, 16#6b, 16#16};
+dec_huffman_lookup(16#39, 16#c) -> {more, 16#71, 16#01};
+dec_huffman_lookup(16#39, 16#d) -> {ok, 16#71, 16#16};
+dec_huffman_lookup(16#39, 16#e) -> {more, 16#76, 16#01};
+dec_huffman_lookup(16#39, 16#f) -> {ok, 16#76, 16#16};
+dec_huffman_lookup(16#3a, 16#0) -> {more, 16#55, 16#02};
+dec_huffman_lookup(16#3a, 16#1) -> {more, 16#55, 16#09};
+dec_huffman_lookup(16#3a, 16#2) -> {more, 16#55, 16#17};
+dec_huffman_lookup(16#3a, 16#3) -> {ok, 16#55, 16#28};
+dec_huffman_lookup(16#3a, 16#4) -> {more, 16#56, 16#02};
+dec_huffman_lookup(16#3a, 16#5) -> {more, 16#56, 16#09};
+dec_huffman_lookup(16#3a, 16#6) -> {more, 16#56, 16#17};
+dec_huffman_lookup(16#3a, 16#7) -> {ok, 16#56, 16#28};
+dec_huffman_lookup(16#3a, 16#8) -> {more, 16#57, 16#02};
+dec_huffman_lookup(16#3a, 16#9) -> {more, 16#57, 16#09};
+dec_huffman_lookup(16#3a, 16#a) -> {more, 16#57, 16#17};
+dec_huffman_lookup(16#3a, 16#b) -> {ok, 16#57, 16#28};
+dec_huffman_lookup(16#3a, 16#c) -> {more, 16#59, 16#02};
+dec_huffman_lookup(16#3a, 16#d) -> {more, 16#59, 16#09};
+dec_huffman_lookup(16#3a, 16#e) -> {more, 16#59, 16#17};
+dec_huffman_lookup(16#3a, 16#f) -> {ok, 16#59, 16#28};
+dec_huffman_lookup(16#3b, 16#0) -> {more, 16#55, 16#03};
+dec_huffman_lookup(16#3b, 16#1) -> {more, 16#55, 16#06};
+dec_huffman_lookup(16#3b, 16#2) -> {more, 16#55, 16#0a};
+dec_huffman_lookup(16#3b, 16#3) -> {more, 16#55, 16#0f};
+dec_huffman_lookup(16#3b, 16#4) -> {more, 16#55, 16#18};
+dec_huffman_lookup(16#3b, 16#5) -> {more, 16#55, 16#1f};
+dec_huffman_lookup(16#3b, 16#6) -> {more, 16#55, 16#29};
+dec_huffman_lookup(16#3b, 16#7) -> {ok, 16#55, 16#38};
+dec_huffman_lookup(16#3b, 16#8) -> {more, 16#56, 16#03};
+dec_huffman_lookup(16#3b, 16#9) -> {more, 16#56, 16#06};
+dec_huffman_lookup(16#3b, 16#a) -> {more, 16#56, 16#0a};
+dec_huffman_lookup(16#3b, 16#b) -> {more, 16#56, 16#0f};
+dec_huffman_lookup(16#3b, 16#c) -> {more, 16#56, 16#18};
+dec_huffman_lookup(16#3b, 16#d) -> {more, 16#56, 16#1f};
+dec_huffman_lookup(16#3b, 16#e) -> {more, 16#56, 16#29};
+dec_huffman_lookup(16#3b, 16#f) -> {ok, 16#56, 16#38};
+dec_huffman_lookup(16#3c, 16#0) -> {more, 16#57, 16#03};
+dec_huffman_lookup(16#3c, 16#1) -> {more, 16#57, 16#06};
+dec_huffman_lookup(16#3c, 16#2) -> {more, 16#57, 16#0a};
+dec_huffman_lookup(16#3c, 16#3) -> {more, 16#57, 16#0f};
+dec_huffman_lookup(16#3c, 16#4) -> {more, 16#57, 16#18};
+dec_huffman_lookup(16#3c, 16#5) -> {more, 16#57, 16#1f};
+dec_huffman_lookup(16#3c, 16#6) -> {more, 16#57, 16#29};
+dec_huffman_lookup(16#3c, 16#7) -> {ok, 16#57, 16#38};
+dec_huffman_lookup(16#3c, 16#8) -> {more, 16#59, 16#03};
+dec_huffman_lookup(16#3c, 16#9) -> {more, 16#59, 16#06};
+dec_huffman_lookup(16#3c, 16#a) -> {more, 16#59, 16#0a};
+dec_huffman_lookup(16#3c, 16#b) -> {more, 16#59, 16#0f};
+dec_huffman_lookup(16#3c, 16#c) -> {more, 16#59, 16#18};
+dec_huffman_lookup(16#3c, 16#d) -> {more, 16#59, 16#1f};
+dec_huffman_lookup(16#3c, 16#e) -> {more, 16#59, 16#29};
+dec_huffman_lookup(16#3c, 16#f) -> {ok, 16#59, 16#38};
+dec_huffman_lookup(16#3d, 16#0) -> {more, 16#6a, 16#02};
+dec_huffman_lookup(16#3d, 16#1) -> {more, 16#6a, 16#09};
+dec_huffman_lookup(16#3d, 16#2) -> {more, 16#6a, 16#17};
+dec_huffman_lookup(16#3d, 16#3) -> {ok, 16#6a, 16#28};
+dec_huffman_lookup(16#3d, 16#4) -> {more, 16#6b, 16#02};
+dec_huffman_lookup(16#3d, 16#5) -> {more, 16#6b, 16#09};
+dec_huffman_lookup(16#3d, 16#6) -> {more, 16#6b, 16#17};
+dec_huffman_lookup(16#3d, 16#7) -> {ok, 16#6b, 16#28};
+dec_huffman_lookup(16#3d, 16#8) -> {more, 16#71, 16#02};
+dec_huffman_lookup(16#3d, 16#9) -> {more, 16#71, 16#09};
+dec_huffman_lookup(16#3d, 16#a) -> {more, 16#71, 16#17};
+dec_huffman_lookup(16#3d, 16#b) -> {ok, 16#71, 16#28};
+dec_huffman_lookup(16#3d, 16#c) -> {more, 16#76, 16#02};
+dec_huffman_lookup(16#3d, 16#d) -> {more, 16#76, 16#09};
+dec_huffman_lookup(16#3d, 16#e) -> {more, 16#76, 16#17};
+dec_huffman_lookup(16#3d, 16#f) -> {ok, 16#76, 16#28};
+dec_huffman_lookup(16#3e, 16#0) -> {more, 16#6a, 16#03};
+dec_huffman_lookup(16#3e, 16#1) -> {more, 16#6a, 16#06};
+dec_huffman_lookup(16#3e, 16#2) -> {more, 16#6a, 16#0a};
+dec_huffman_lookup(16#3e, 16#3) -> {more, 16#6a, 16#0f};
+dec_huffman_lookup(16#3e, 16#4) -> {more, 16#6a, 16#18};
+dec_huffman_lookup(16#3e, 16#5) -> {more, 16#6a, 16#1f};
+dec_huffman_lookup(16#3e, 16#6) -> {more, 16#6a, 16#29};
+dec_huffman_lookup(16#3e, 16#7) -> {ok, 16#6a, 16#38};
+dec_huffman_lookup(16#3e, 16#8) -> {more, 16#6b, 16#03};
+dec_huffman_lookup(16#3e, 16#9) -> {more, 16#6b, 16#06};
+dec_huffman_lookup(16#3e, 16#a) -> {more, 16#6b, 16#0a};
+dec_huffman_lookup(16#3e, 16#b) -> {more, 16#6b, 16#0f};
+dec_huffman_lookup(16#3e, 16#c) -> {more, 16#6b, 16#18};
+dec_huffman_lookup(16#3e, 16#d) -> {more, 16#6b, 16#1f};
+dec_huffman_lookup(16#3e, 16#e) -> {more, 16#6b, 16#29};
+dec_huffman_lookup(16#3e, 16#f) -> {ok, 16#6b, 16#38};
+dec_huffman_lookup(16#3f, 16#0) -> {more, 16#71, 16#03};
+dec_huffman_lookup(16#3f, 16#1) -> {more, 16#71, 16#06};
+dec_huffman_lookup(16#3f, 16#2) -> {more, 16#71, 16#0a};
+dec_huffman_lookup(16#3f, 16#3) -> {more, 16#71, 16#0f};
+dec_huffman_lookup(16#3f, 16#4) -> {more, 16#71, 16#18};
+dec_huffman_lookup(16#3f, 16#5) -> {more, 16#71, 16#1f};
+dec_huffman_lookup(16#3f, 16#6) -> {more, 16#71, 16#29};
+dec_huffman_lookup(16#3f, 16#7) -> {ok, 16#71, 16#38};
+dec_huffman_lookup(16#3f, 16#8) -> {more, 16#76, 16#03};
+dec_huffman_lookup(16#3f, 16#9) -> {more, 16#76, 16#06};
+dec_huffman_lookup(16#3f, 16#a) -> {more, 16#76, 16#0a};
+dec_huffman_lookup(16#3f, 16#b) -> {more, 16#76, 16#0f};
+dec_huffman_lookup(16#3f, 16#c) -> {more, 16#76, 16#18};
+dec_huffman_lookup(16#3f, 16#d) -> {more, 16#76, 16#1f};
+dec_huffman_lookup(16#3f, 16#e) -> {more, 16#76, 16#29};
+dec_huffman_lookup(16#3f, 16#f) -> {ok, 16#76, 16#38};
+dec_huffman_lookup(16#40, 16#0) -> {more, 16#77, 16#01};
+dec_huffman_lookup(16#40, 16#1) -> {ok, 16#77, 16#16};
+dec_huffman_lookup(16#40, 16#2) -> {more, 16#78, 16#01};
+dec_huffman_lookup(16#40, 16#3) -> {ok, 16#78, 16#16};
+dec_huffman_lookup(16#40, 16#4) -> {more, 16#79, 16#01};
+dec_huffman_lookup(16#40, 16#5) -> {ok, 16#79, 16#16};
+dec_huffman_lookup(16#40, 16#6) -> {more, 16#7a, 16#01};
+dec_huffman_lookup(16#40, 16#7) -> {ok, 16#7a, 16#16};
+dec_huffman_lookup(16#40, 16#8) -> {ok, 16#26, 16#00};
+dec_huffman_lookup(16#40, 16#9) -> {ok, 16#2a, 16#00};
+dec_huffman_lookup(16#40, 16#a) -> {ok, 16#2c, 16#00};
+dec_huffman_lookup(16#40, 16#b) -> {ok, 16#3b, 16#00};
+dec_huffman_lookup(16#40, 16#c) -> {ok, 16#58, 16#00};
+dec_huffman_lookup(16#40, 16#d) -> {ok, 16#5a, 16#00};
+dec_huffman_lookup(16#40, 16#e) -> {more, undefined, 16#4b};
+dec_huffman_lookup(16#40, 16#f) -> {ok, undefined, 16#4e};
+dec_huffman_lookup(16#41, 16#0) -> {more, 16#77, 16#02};
+dec_huffman_lookup(16#41, 16#1) -> {more, 16#77, 16#09};
+dec_huffman_lookup(16#41, 16#2) -> {more, 16#77, 16#17};
+dec_huffman_lookup(16#41, 16#3) -> {ok, 16#77, 16#28};
+dec_huffman_lookup(16#41, 16#4) -> {more, 16#78, 16#02};
+dec_huffman_lookup(16#41, 16#5) -> {more, 16#78, 16#09};
+dec_huffman_lookup(16#41, 16#6) -> {more, 16#78, 16#17};
+dec_huffman_lookup(16#41, 16#7) -> {ok, 16#78, 16#28};
+dec_huffman_lookup(16#41, 16#8) -> {more, 16#79, 16#02};
+dec_huffman_lookup(16#41, 16#9) -> {more, 16#79, 16#09};
+dec_huffman_lookup(16#41, 16#a) -> {more, 16#79, 16#17};
+dec_huffman_lookup(16#41, 16#b) -> {ok, 16#79, 16#28};
+dec_huffman_lookup(16#41, 16#c) -> {more, 16#7a, 16#02};
+dec_huffman_lookup(16#41, 16#d) -> {more, 16#7a, 16#09};
+dec_huffman_lookup(16#41, 16#e) -> {more, 16#7a, 16#17};
+dec_huffman_lookup(16#41, 16#f) -> {ok, 16#7a, 16#28};
+dec_huffman_lookup(16#42, 16#0) -> {more, 16#77, 16#03};
+dec_huffman_lookup(16#42, 16#1) -> {more, 16#77, 16#06};
+dec_huffman_lookup(16#42, 16#2) -> {more, 16#77, 16#0a};
+dec_huffman_lookup(16#42, 16#3) -> {more, 16#77, 16#0f};
+dec_huffman_lookup(16#42, 16#4) -> {more, 16#77, 16#18};
+dec_huffman_lookup(16#42, 16#5) -> {more, 16#77, 16#1f};
+dec_huffman_lookup(16#42, 16#6) -> {more, 16#77, 16#29};
+dec_huffman_lookup(16#42, 16#7) -> {ok, 16#77, 16#38};
+dec_huffman_lookup(16#42, 16#8) -> {more, 16#78, 16#03};
+dec_huffman_lookup(16#42, 16#9) -> {more, 16#78, 16#06};
+dec_huffman_lookup(16#42, 16#a) -> {more, 16#78, 16#0a};
+dec_huffman_lookup(16#42, 16#b) -> {more, 16#78, 16#0f};
+dec_huffman_lookup(16#42, 16#c) -> {more, 16#78, 16#18};
+dec_huffman_lookup(16#42, 16#d) -> {more, 16#78, 16#1f};
+dec_huffman_lookup(16#42, 16#e) -> {more, 16#78, 16#29};
+dec_huffman_lookup(16#42, 16#f) -> {ok, 16#78, 16#38};
+dec_huffman_lookup(16#43, 16#0) -> {more, 16#79, 16#03};
+dec_huffman_lookup(16#43, 16#1) -> {more, 16#79, 16#06};
+dec_huffman_lookup(16#43, 16#2) -> {more, 16#79, 16#0a};
+dec_huffman_lookup(16#43, 16#3) -> {more, 16#79, 16#0f};
+dec_huffman_lookup(16#43, 16#4) -> {more, 16#79, 16#18};
+dec_huffman_lookup(16#43, 16#5) -> {more, 16#79, 16#1f};
+dec_huffman_lookup(16#43, 16#6) -> {more, 16#79, 16#29};
+dec_huffman_lookup(16#43, 16#7) -> {ok, 16#79, 16#38};
+dec_huffman_lookup(16#43, 16#8) -> {more, 16#7a, 16#03};
+dec_huffman_lookup(16#43, 16#9) -> {more, 16#7a, 16#06};
+dec_huffman_lookup(16#43, 16#a) -> {more, 16#7a, 16#0a};
+dec_huffman_lookup(16#43, 16#b) -> {more, 16#7a, 16#0f};
+dec_huffman_lookup(16#43, 16#c) -> {more, 16#7a, 16#18};
+dec_huffman_lookup(16#43, 16#d) -> {more, 16#7a, 16#1f};
+dec_huffman_lookup(16#43, 16#e) -> {more, 16#7a, 16#29};
+dec_huffman_lookup(16#43, 16#f) -> {ok, 16#7a, 16#38};
+dec_huffman_lookup(16#44, 16#0) -> {more, 16#26, 16#01};
+dec_huffman_lookup(16#44, 16#1) -> {ok, 16#26, 16#16};
+dec_huffman_lookup(16#44, 16#2) -> {more, 16#2a, 16#01};
+dec_huffman_lookup(16#44, 16#3) -> {ok, 16#2a, 16#16};
+dec_huffman_lookup(16#44, 16#4) -> {more, 16#2c, 16#01};
+dec_huffman_lookup(16#44, 16#5) -> {ok, 16#2c, 16#16};
+dec_huffman_lookup(16#44, 16#6) -> {more, 16#3b, 16#01};
+dec_huffman_lookup(16#44, 16#7) -> {ok, 16#3b, 16#16};
+dec_huffman_lookup(16#44, 16#8) -> {more, 16#58, 16#01};
+dec_huffman_lookup(16#44, 16#9) -> {ok, 16#58, 16#16};
+dec_huffman_lookup(16#44, 16#a) -> {more, 16#5a, 16#01};
+dec_huffman_lookup(16#44, 16#b) -> {ok, 16#5a, 16#16};
+dec_huffman_lookup(16#44, 16#c) -> {more, undefined, 16#4c};
+dec_huffman_lookup(16#44, 16#d) -> {more, undefined, 16#4d};
+dec_huffman_lookup(16#44, 16#e) -> {more, undefined, 16#4f};
+dec_huffman_lookup(16#44, 16#f) -> {ok, undefined, 16#51};
+dec_huffman_lookup(16#45, 16#0) -> {more, 16#26, 16#02};
+dec_huffman_lookup(16#45, 16#1) -> {more, 16#26, 16#09};
+dec_huffman_lookup(16#45, 16#2) -> {more, 16#26, 16#17};
+dec_huffman_lookup(16#45, 16#3) -> {ok, 16#26, 16#28};
+dec_huffman_lookup(16#45, 16#4) -> {more, 16#2a, 16#02};
+dec_huffman_lookup(16#45, 16#5) -> {more, 16#2a, 16#09};
+dec_huffman_lookup(16#45, 16#6) -> {more, 16#2a, 16#17};
+dec_huffman_lookup(16#45, 16#7) -> {ok, 16#2a, 16#28};
+dec_huffman_lookup(16#45, 16#8) -> {more, 16#2c, 16#02};
+dec_huffman_lookup(16#45, 16#9) -> {more, 16#2c, 16#09};
+dec_huffman_lookup(16#45, 16#a) -> {more, 16#2c, 16#17};
+dec_huffman_lookup(16#45, 16#b) -> {ok, 16#2c, 16#28};
+dec_huffman_lookup(16#45, 16#c) -> {more, 16#3b, 16#02};
+dec_huffman_lookup(16#45, 16#d) -> {more, 16#3b, 16#09};
+dec_huffman_lookup(16#45, 16#e) -> {more, 16#3b, 16#17};
+dec_huffman_lookup(16#45, 16#f) -> {ok, 16#3b, 16#28};
+dec_huffman_lookup(16#46, 16#0) -> {more, 16#26, 16#03};
+dec_huffman_lookup(16#46, 16#1) -> {more, 16#26, 16#06};
+dec_huffman_lookup(16#46, 16#2) -> {more, 16#26, 16#0a};
+dec_huffman_lookup(16#46, 16#3) -> {more, 16#26, 16#0f};
+dec_huffman_lookup(16#46, 16#4) -> {more, 16#26, 16#18};
+dec_huffman_lookup(16#46, 16#5) -> {more, 16#26, 16#1f};
+dec_huffman_lookup(16#46, 16#6) -> {more, 16#26, 16#29};
+dec_huffman_lookup(16#46, 16#7) -> {ok, 16#26, 16#38};
+dec_huffman_lookup(16#46, 16#8) -> {more, 16#2a, 16#03};
+dec_huffman_lookup(16#46, 16#9) -> {more, 16#2a, 16#06};
+dec_huffman_lookup(16#46, 16#a) -> {more, 16#2a, 16#0a};
+dec_huffman_lookup(16#46, 16#b) -> {more, 16#2a, 16#0f};
+dec_huffman_lookup(16#46, 16#c) -> {more, 16#2a, 16#18};
+dec_huffman_lookup(16#46, 16#d) -> {more, 16#2a, 16#1f};
+dec_huffman_lookup(16#46, 16#e) -> {more, 16#2a, 16#29};
+dec_huffman_lookup(16#46, 16#f) -> {ok, 16#2a, 16#38};
+dec_huffman_lookup(16#47, 16#0) -> {more, 16#2c, 16#03};
+dec_huffman_lookup(16#47, 16#1) -> {more, 16#2c, 16#06};
+dec_huffman_lookup(16#47, 16#2) -> {more, 16#2c, 16#0a};
+dec_huffman_lookup(16#47, 16#3) -> {more, 16#2c, 16#0f};
+dec_huffman_lookup(16#47, 16#4) -> {more, 16#2c, 16#18};
+dec_huffman_lookup(16#47, 16#5) -> {more, 16#2c, 16#1f};
+dec_huffman_lookup(16#47, 16#6) -> {more, 16#2c, 16#29};
+dec_huffman_lookup(16#47, 16#7) -> {ok, 16#2c, 16#38};
+dec_huffman_lookup(16#47, 16#8) -> {more, 16#3b, 16#03};
+dec_huffman_lookup(16#47, 16#9) -> {more, 16#3b, 16#06};
+dec_huffman_lookup(16#47, 16#a) -> {more, 16#3b, 16#0a};
+dec_huffman_lookup(16#47, 16#b) -> {more, 16#3b, 16#0f};
+dec_huffman_lookup(16#47, 16#c) -> {more, 16#3b, 16#18};
+dec_huffman_lookup(16#47, 16#d) -> {more, 16#3b, 16#1f};
+dec_huffman_lookup(16#47, 16#e) -> {more, 16#3b, 16#29};
+dec_huffman_lookup(16#47, 16#f) -> {ok, 16#3b, 16#38};
+dec_huffman_lookup(16#48, 16#0) -> {more, 16#58, 16#02};
+dec_huffman_lookup(16#48, 16#1) -> {more, 16#58, 16#09};
+dec_huffman_lookup(16#48, 16#2) -> {more, 16#58, 16#17};
+dec_huffman_lookup(16#48, 16#3) -> {ok, 16#58, 16#28};
+dec_huffman_lookup(16#48, 16#4) -> {more, 16#5a, 16#02};
+dec_huffman_lookup(16#48, 16#5) -> {more, 16#5a, 16#09};
+dec_huffman_lookup(16#48, 16#6) -> {more, 16#5a, 16#17};
+dec_huffman_lookup(16#48, 16#7) -> {ok, 16#5a, 16#28};
+dec_huffman_lookup(16#48, 16#8) -> {ok, 16#21, 16#00};
+dec_huffman_lookup(16#48, 16#9) -> {ok, 16#22, 16#00};
+dec_huffman_lookup(16#48, 16#a) -> {ok, 16#28, 16#00};
+dec_huffman_lookup(16#48, 16#b) -> {ok, 16#29, 16#00};
+dec_huffman_lookup(16#48, 16#c) -> {ok, 16#3f, 16#00};
+dec_huffman_lookup(16#48, 16#d) -> {more, undefined, 16#50};
+dec_huffman_lookup(16#48, 16#e) -> {more, undefined, 16#52};
+dec_huffman_lookup(16#48, 16#f) -> {ok, undefined, 16#54};
+dec_huffman_lookup(16#49, 16#0) -> {more, 16#58, 16#03};
+dec_huffman_lookup(16#49, 16#1) -> {more, 16#58, 16#06};
+dec_huffman_lookup(16#49, 16#2) -> {more, 16#58, 16#0a};
+dec_huffman_lookup(16#49, 16#3) -> {more, 16#58, 16#0f};
+dec_huffman_lookup(16#49, 16#4) -> {more, 16#58, 16#18};
+dec_huffman_lookup(16#49, 16#5) -> {more, 16#58, 16#1f};
+dec_huffman_lookup(16#49, 16#6) -> {more, 16#58, 16#29};
+dec_huffman_lookup(16#49, 16#7) -> {ok, 16#58, 16#38};
+dec_huffman_lookup(16#49, 16#8) -> {more, 16#5a, 16#03};
+dec_huffman_lookup(16#49, 16#9) -> {more, 16#5a, 16#06};
+dec_huffman_lookup(16#49, 16#a) -> {more, 16#5a, 16#0a};
+dec_huffman_lookup(16#49, 16#b) -> {more, 16#5a, 16#0f};
+dec_huffman_lookup(16#49, 16#c) -> {more, 16#5a, 16#18};
+dec_huffman_lookup(16#49, 16#d) -> {more, 16#5a, 16#1f};
+dec_huffman_lookup(16#49, 16#e) -> {more, 16#5a, 16#29};
+dec_huffman_lookup(16#49, 16#f) -> {ok, 16#5a, 16#38};
+dec_huffman_lookup(16#4a, 16#0) -> {more, 16#21, 16#01};
+dec_huffman_lookup(16#4a, 16#1) -> {ok, 16#21, 16#16};
+dec_huffman_lookup(16#4a, 16#2) -> {more, 16#22, 16#01};
+dec_huffman_lookup(16#4a, 16#3) -> {ok, 16#22, 16#16};
+dec_huffman_lookup(16#4a, 16#4) -> {more, 16#28, 16#01};
+dec_huffman_lookup(16#4a, 16#5) -> {ok, 16#28, 16#16};
+dec_huffman_lookup(16#4a, 16#6) -> {more, 16#29, 16#01};
+dec_huffman_lookup(16#4a, 16#7) -> {ok, 16#29, 16#16};
+dec_huffman_lookup(16#4a, 16#8) -> {more, 16#3f, 16#01};
+dec_huffman_lookup(16#4a, 16#9) -> {ok, 16#3f, 16#16};
+dec_huffman_lookup(16#4a, 16#a) -> {ok, 16#27, 16#00};
+dec_huffman_lookup(16#4a, 16#b) -> {ok, 16#2b, 16#00};
+dec_huffman_lookup(16#4a, 16#c) -> {ok, 16#7c, 16#00};
+dec_huffman_lookup(16#4a, 16#d) -> {more, undefined, 16#53};
+dec_huffman_lookup(16#4a, 16#e) -> {more, undefined, 16#55};
+dec_huffman_lookup(16#4a, 16#f) -> {ok, undefined, 16#58};
+dec_huffman_lookup(16#4b, 16#0) -> {more, 16#21, 16#02};
+dec_huffman_lookup(16#4b, 16#1) -> {more, 16#21, 16#09};
+dec_huffman_lookup(16#4b, 16#2) -> {more, 16#21, 16#17};
+dec_huffman_lookup(16#4b, 16#3) -> {ok, 16#21, 16#28};
+dec_huffman_lookup(16#4b, 16#4) -> {more, 16#22, 16#02};
+dec_huffman_lookup(16#4b, 16#5) -> {more, 16#22, 16#09};
+dec_huffman_lookup(16#4b, 16#6) -> {more, 16#22, 16#17};
+dec_huffman_lookup(16#4b, 16#7) -> {ok, 16#22, 16#28};
+dec_huffman_lookup(16#4b, 16#8) -> {more, 16#28, 16#02};
+dec_huffman_lookup(16#4b, 16#9) -> {more, 16#28, 16#09};
+dec_huffman_lookup(16#4b, 16#a) -> {more, 16#28, 16#17};
+dec_huffman_lookup(16#4b, 16#b) -> {ok, 16#28, 16#28};
+dec_huffman_lookup(16#4b, 16#c) -> {more, 16#29, 16#02};
+dec_huffman_lookup(16#4b, 16#d) -> {more, 16#29, 16#09};
+dec_huffman_lookup(16#4b, 16#e) -> {more, 16#29, 16#17};
+dec_huffman_lookup(16#4b, 16#f) -> {ok, 16#29, 16#28};
+dec_huffman_lookup(16#4c, 16#0) -> {more, 16#21, 16#03};
+dec_huffman_lookup(16#4c, 16#1) -> {more, 16#21, 16#06};
+dec_huffman_lookup(16#4c, 16#2) -> {more, 16#21, 16#0a};
+dec_huffman_lookup(16#4c, 16#3) -> {more, 16#21, 16#0f};
+dec_huffman_lookup(16#4c, 16#4) -> {more, 16#21, 16#18};
+dec_huffman_lookup(16#4c, 16#5) -> {more, 16#21, 16#1f};
+dec_huffman_lookup(16#4c, 16#6) -> {more, 16#21, 16#29};
+dec_huffman_lookup(16#4c, 16#7) -> {ok, 16#21, 16#38};
+dec_huffman_lookup(16#4c, 16#8) -> {more, 16#22, 16#03};
+dec_huffman_lookup(16#4c, 16#9) -> {more, 16#22, 16#06};
+dec_huffman_lookup(16#4c, 16#a) -> {more, 16#22, 16#0a};
+dec_huffman_lookup(16#4c, 16#b) -> {more, 16#22, 16#0f};
+dec_huffman_lookup(16#4c, 16#c) -> {more, 16#22, 16#18};
+dec_huffman_lookup(16#4c, 16#d) -> {more, 16#22, 16#1f};
+dec_huffman_lookup(16#4c, 16#e) -> {more, 16#22, 16#29};
+dec_huffman_lookup(16#4c, 16#f) -> {ok, 16#22, 16#38};
+dec_huffman_lookup(16#4d, 16#0) -> {more, 16#28, 16#03};
+dec_huffman_lookup(16#4d, 16#1) -> {more, 16#28, 16#06};
+dec_huffman_lookup(16#4d, 16#2) -> {more, 16#28, 16#0a};
+dec_huffman_lookup(16#4d, 16#3) -> {more, 16#28, 16#0f};
+dec_huffman_lookup(16#4d, 16#4) -> {more, 16#28, 16#18};
+dec_huffman_lookup(16#4d, 16#5) -> {more, 16#28, 16#1f};
+dec_huffman_lookup(16#4d, 16#6) -> {more, 16#28, 16#29};
+dec_huffman_lookup(16#4d, 16#7) -> {ok, 16#28, 16#38};
+dec_huffman_lookup(16#4d, 16#8) -> {more, 16#29, 16#03};
+dec_huffman_lookup(16#4d, 16#9) -> {more, 16#29, 16#06};
+dec_huffman_lookup(16#4d, 16#a) -> {more, 16#29, 16#0a};
+dec_huffman_lookup(16#4d, 16#b) -> {more, 16#29, 16#0f};
+dec_huffman_lookup(16#4d, 16#c) -> {more, 16#29, 16#18};
+dec_huffman_lookup(16#4d, 16#d) -> {more, 16#29, 16#1f};
+dec_huffman_lookup(16#4d, 16#e) -> {more, 16#29, 16#29};
+dec_huffman_lookup(16#4d, 16#f) -> {ok, 16#29, 16#38};
+dec_huffman_lookup(16#4e, 16#0) -> {more, 16#3f, 16#02};
+dec_huffman_lookup(16#4e, 16#1) -> {more, 16#3f, 16#09};
+dec_huffman_lookup(16#4e, 16#2) -> {more, 16#3f, 16#17};
+dec_huffman_lookup(16#4e, 16#3) -> {ok, 16#3f, 16#28};
+dec_huffman_lookup(16#4e, 16#4) -> {more, 16#27, 16#01};
+dec_huffman_lookup(16#4e, 16#5) -> {ok, 16#27, 16#16};
+dec_huffman_lookup(16#4e, 16#6) -> {more, 16#2b, 16#01};
+dec_huffman_lookup(16#4e, 16#7) -> {ok, 16#2b, 16#16};
+dec_huffman_lookup(16#4e, 16#8) -> {more, 16#7c, 16#01};
+dec_huffman_lookup(16#4e, 16#9) -> {ok, 16#7c, 16#16};
+dec_huffman_lookup(16#4e, 16#a) -> {ok, 16#23, 16#00};
+dec_huffman_lookup(16#4e, 16#b) -> {ok, 16#3e, 16#00};
+dec_huffman_lookup(16#4e, 16#c) -> {more, undefined, 16#56};
+dec_huffman_lookup(16#4e, 16#d) -> {more, undefined, 16#57};
+dec_huffman_lookup(16#4e, 16#e) -> {more, undefined, 16#59};
+dec_huffman_lookup(16#4e, 16#f) -> {ok, undefined, 16#5a};
+dec_huffman_lookup(16#4f, 16#0) -> {more, 16#3f, 16#03};
+dec_huffman_lookup(16#4f, 16#1) -> {more, 16#3f, 16#06};
+dec_huffman_lookup(16#4f, 16#2) -> {more, 16#3f, 16#0a};
+dec_huffman_lookup(16#4f, 16#3) -> {more, 16#3f, 16#0f};
+dec_huffman_lookup(16#4f, 16#4) -> {more, 16#3f, 16#18};
+dec_huffman_lookup(16#4f, 16#5) -> {more, 16#3f, 16#1f};
+dec_huffman_lookup(16#4f, 16#6) -> {more, 16#3f, 16#29};
+dec_huffman_lookup(16#4f, 16#7) -> {ok, 16#3f, 16#38};
+dec_huffman_lookup(16#4f, 16#8) -> {more, 16#27, 16#02};
+dec_huffman_lookup(16#4f, 16#9) -> {more, 16#27, 16#09};
+dec_huffman_lookup(16#4f, 16#a) -> {more, 16#27, 16#17};
+dec_huffman_lookup(16#4f, 16#b) -> {ok, 16#27, 16#28};
+dec_huffman_lookup(16#4f, 16#c) -> {more, 16#2b, 16#02};
+dec_huffman_lookup(16#4f, 16#d) -> {more, 16#2b, 16#09};
+dec_huffman_lookup(16#4f, 16#e) -> {more, 16#2b, 16#17};
+dec_huffman_lookup(16#4f, 16#f) -> {ok, 16#2b, 16#28};
+dec_huffman_lookup(16#50, 16#0) -> {more, 16#27, 16#03};
+dec_huffman_lookup(16#50, 16#1) -> {more, 16#27, 16#06};
+dec_huffman_lookup(16#50, 16#2) -> {more, 16#27, 16#0a};
+dec_huffman_lookup(16#50, 16#3) -> {more, 16#27, 16#0f};
+dec_huffman_lookup(16#50, 16#4) -> {more, 16#27, 16#18};
+dec_huffman_lookup(16#50, 16#5) -> {more, 16#27, 16#1f};
+dec_huffman_lookup(16#50, 16#6) -> {more, 16#27, 16#29};
+dec_huffman_lookup(16#50, 16#7) -> {ok, 16#27, 16#38};
+dec_huffman_lookup(16#50, 16#8) -> {more, 16#2b, 16#03};
+dec_huffman_lookup(16#50, 16#9) -> {more, 16#2b, 16#06};
+dec_huffman_lookup(16#50, 16#a) -> {more, 16#2b, 16#0a};
+dec_huffman_lookup(16#50, 16#b) -> {more, 16#2b, 16#0f};
+dec_huffman_lookup(16#50, 16#c) -> {more, 16#2b, 16#18};
+dec_huffman_lookup(16#50, 16#d) -> {more, 16#2b, 16#1f};
+dec_huffman_lookup(16#50, 16#e) -> {more, 16#2b, 16#29};
+dec_huffman_lookup(16#50, 16#f) -> {ok, 16#2b, 16#38};
+dec_huffman_lookup(16#51, 16#0) -> {more, 16#7c, 16#02};
+dec_huffman_lookup(16#51, 16#1) -> {more, 16#7c, 16#09};
+dec_huffman_lookup(16#51, 16#2) -> {more, 16#7c, 16#17};
+dec_huffman_lookup(16#51, 16#3) -> {ok, 16#7c, 16#28};
+dec_huffman_lookup(16#51, 16#4) -> {more, 16#23, 16#01};
+dec_huffman_lookup(16#51, 16#5) -> {ok, 16#23, 16#16};
+dec_huffman_lookup(16#51, 16#6) -> {more, 16#3e, 16#01};
+dec_huffman_lookup(16#51, 16#7) -> {ok, 16#3e, 16#16};
+dec_huffman_lookup(16#51, 16#8) -> {ok, 16#00, 16#00};
+dec_huffman_lookup(16#51, 16#9) -> {ok, 16#24, 16#00};
+dec_huffman_lookup(16#51, 16#a) -> {ok, 16#40, 16#00};
+dec_huffman_lookup(16#51, 16#b) -> {ok, 16#5b, 16#00};
+dec_huffman_lookup(16#51, 16#c) -> {ok, 16#5d, 16#00};
+dec_huffman_lookup(16#51, 16#d) -> {ok, 16#7e, 16#00};
+dec_huffman_lookup(16#51, 16#e) -> {more, undefined, 16#5b};
+dec_huffman_lookup(16#51, 16#f) -> {ok, undefined, 16#5c};
+dec_huffman_lookup(16#52, 16#0) -> {more, 16#7c, 16#03};
+dec_huffman_lookup(16#52, 16#1) -> {more, 16#7c, 16#06};
+dec_huffman_lookup(16#52, 16#2) -> {more, 16#7c, 16#0a};
+dec_huffman_lookup(16#52, 16#3) -> {more, 16#7c, 16#0f};
+dec_huffman_lookup(16#52, 16#4) -> {more, 16#7c, 16#18};
+dec_huffman_lookup(16#52, 16#5) -> {more, 16#7c, 16#1f};
+dec_huffman_lookup(16#52, 16#6) -> {more, 16#7c, 16#29};
+dec_huffman_lookup(16#52, 16#7) -> {ok, 16#7c, 16#38};
+dec_huffman_lookup(16#52, 16#8) -> {more, 16#23, 16#02};
+dec_huffman_lookup(16#52, 16#9) -> {more, 16#23, 16#09};
+dec_huffman_lookup(16#52, 16#a) -> {more, 16#23, 16#17};
+dec_huffman_lookup(16#52, 16#b) -> {ok, 16#23, 16#28};
+dec_huffman_lookup(16#52, 16#c) -> {more, 16#3e, 16#02};
+dec_huffman_lookup(16#52, 16#d) -> {more, 16#3e, 16#09};
+dec_huffman_lookup(16#52, 16#e) -> {more, 16#3e, 16#17};
+dec_huffman_lookup(16#52, 16#f) -> {ok, 16#3e, 16#28};
+dec_huffman_lookup(16#53, 16#0) -> {more, 16#23, 16#03};
+dec_huffman_lookup(16#53, 16#1) -> {more, 16#23, 16#06};
+dec_huffman_lookup(16#53, 16#2) -> {more, 16#23, 16#0a};
+dec_huffman_lookup(16#53, 16#3) -> {more, 16#23, 16#0f};
+dec_huffman_lookup(16#53, 16#4) -> {more, 16#23, 16#18};
+dec_huffman_lookup(16#53, 16#5) -> {more, 16#23, 16#1f};
+dec_huffman_lookup(16#53, 16#6) -> {more, 16#23, 16#29};
+dec_huffman_lookup(16#53, 16#7) -> {ok, 16#23, 16#38};
+dec_huffman_lookup(16#53, 16#8) -> {more, 16#3e, 16#03};
+dec_huffman_lookup(16#53, 16#9) -> {more, 16#3e, 16#06};
+dec_huffman_lookup(16#53, 16#a) -> {more, 16#3e, 16#0a};
+dec_huffman_lookup(16#53, 16#b) -> {more, 16#3e, 16#0f};
+dec_huffman_lookup(16#53, 16#c) -> {more, 16#3e, 16#18};
+dec_huffman_lookup(16#53, 16#d) -> {more, 16#3e, 16#1f};
+dec_huffman_lookup(16#53, 16#e) -> {more, 16#3e, 16#29};
+dec_huffman_lookup(16#53, 16#f) -> {ok, 16#3e, 16#38};
+dec_huffman_lookup(16#54, 16#0) -> {more, 16#00, 16#01};
+dec_huffman_lookup(16#54, 16#1) -> {ok, 16#00, 16#16};
+dec_huffman_lookup(16#54, 16#2) -> {more, 16#24, 16#01};
+dec_huffman_lookup(16#54, 16#3) -> {ok, 16#24, 16#16};
+dec_huffman_lookup(16#54, 16#4) -> {more, 16#40, 16#01};
+dec_huffman_lookup(16#54, 16#5) -> {ok, 16#40, 16#16};
+dec_huffman_lookup(16#54, 16#6) -> {more, 16#5b, 16#01};
+dec_huffman_lookup(16#54, 16#7) -> {ok, 16#5b, 16#16};
+dec_huffman_lookup(16#54, 16#8) -> {more, 16#5d, 16#01};
+dec_huffman_lookup(16#54, 16#9) -> {ok, 16#5d, 16#16};
+dec_huffman_lookup(16#54, 16#a) -> {more, 16#7e, 16#01};
+dec_huffman_lookup(16#54, 16#b) -> {ok, 16#7e, 16#16};
+dec_huffman_lookup(16#54, 16#c) -> {ok, 16#5e, 16#00};
+dec_huffman_lookup(16#54, 16#d) -> {ok, 16#7d, 16#00};
+dec_huffman_lookup(16#54, 16#e) -> {more, undefined, 16#5d};
+dec_huffman_lookup(16#54, 16#f) -> {ok, undefined, 16#5e};
+dec_huffman_lookup(16#55, 16#0) -> {more, 16#00, 16#02};
+dec_huffman_lookup(16#55, 16#1) -> {more, 16#00, 16#09};
+dec_huffman_lookup(16#55, 16#2) -> {more, 16#00, 16#17};
+dec_huffman_lookup(16#55, 16#3) -> {ok, 16#00, 16#28};
+dec_huffman_lookup(16#55, 16#4) -> {more, 16#24, 16#02};
+dec_huffman_lookup(16#55, 16#5) -> {more, 16#24, 16#09};
+dec_huffman_lookup(16#55, 16#6) -> {more, 16#24, 16#17};
+dec_huffman_lookup(16#55, 16#7) -> {ok, 16#24, 16#28};
+dec_huffman_lookup(16#55, 16#8) -> {more, 16#40, 16#02};
+dec_huffman_lookup(16#55, 16#9) -> {more, 16#40, 16#09};
+dec_huffman_lookup(16#55, 16#a) -> {more, 16#40, 16#17};
+dec_huffman_lookup(16#55, 16#b) -> {ok, 16#40, 16#28};
+dec_huffman_lookup(16#55, 16#c) -> {more, 16#5b, 16#02};
+dec_huffman_lookup(16#55, 16#d) -> {more, 16#5b, 16#09};
+dec_huffman_lookup(16#55, 16#e) -> {more, 16#5b, 16#17};
+dec_huffman_lookup(16#55, 16#f) -> {ok, 16#5b, 16#28};
+dec_huffman_lookup(16#56, 16#0) -> {more, 16#00, 16#03};
+dec_huffman_lookup(16#56, 16#1) -> {more, 16#00, 16#06};
+dec_huffman_lookup(16#56, 16#2) -> {more, 16#00, 16#0a};
+dec_huffman_lookup(16#56, 16#3) -> {more, 16#00, 16#0f};
+dec_huffman_lookup(16#56, 16#4) -> {more, 16#00, 16#18};
+dec_huffman_lookup(16#56, 16#5) -> {more, 16#00, 16#1f};
+dec_huffman_lookup(16#56, 16#6) -> {more, 16#00, 16#29};
+dec_huffman_lookup(16#56, 16#7) -> {ok, 16#00, 16#38};
+dec_huffman_lookup(16#56, 16#8) -> {more, 16#24, 16#03};
+dec_huffman_lookup(16#56, 16#9) -> {more, 16#24, 16#06};
+dec_huffman_lookup(16#56, 16#a) -> {more, 16#24, 16#0a};
+dec_huffman_lookup(16#56, 16#b) -> {more, 16#24, 16#0f};
+dec_huffman_lookup(16#56, 16#c) -> {more, 16#24, 16#18};
+dec_huffman_lookup(16#56, 16#d) -> {more, 16#24, 16#1f};
+dec_huffman_lookup(16#56, 16#e) -> {more, 16#24, 16#29};
+dec_huffman_lookup(16#56, 16#f) -> {ok, 16#24, 16#38};
+dec_huffman_lookup(16#57, 16#0) -> {more, 16#40, 16#03};
+dec_huffman_lookup(16#57, 16#1) -> {more, 16#40, 16#06};
+dec_huffman_lookup(16#57, 16#2) -> {more, 16#40, 16#0a};
+dec_huffman_lookup(16#57, 16#3) -> {more, 16#40, 16#0f};
+dec_huffman_lookup(16#57, 16#4) -> {more, 16#40, 16#18};
+dec_huffman_lookup(16#57, 16#5) -> {more, 16#40, 16#1f};
+dec_huffman_lookup(16#57, 16#6) -> {more, 16#40, 16#29};
+dec_huffman_lookup(16#57, 16#7) -> {ok, 16#40, 16#38};
+dec_huffman_lookup(16#57, 16#8) -> {more, 16#5b, 16#03};
+dec_huffman_lookup(16#57, 16#9) -> {more, 16#5b, 16#06};
+dec_huffman_lookup(16#57, 16#a) -> {more, 16#5b, 16#0a};
+dec_huffman_lookup(16#57, 16#b) -> {more, 16#5b, 16#0f};
+dec_huffman_lookup(16#57, 16#c) -> {more, 16#5b, 16#18};
+dec_huffman_lookup(16#57, 16#d) -> {more, 16#5b, 16#1f};
+dec_huffman_lookup(16#57, 16#e) -> {more, 16#5b, 16#29};
+dec_huffman_lookup(16#57, 16#f) -> {ok, 16#5b, 16#38};
+dec_huffman_lookup(16#58, 16#0) -> {more, 16#5d, 16#02};
+dec_huffman_lookup(16#58, 16#1) -> {more, 16#5d, 16#09};
+dec_huffman_lookup(16#58, 16#2) -> {more, 16#5d, 16#17};
+dec_huffman_lookup(16#58, 16#3) -> {ok, 16#5d, 16#28};
+dec_huffman_lookup(16#58, 16#4) -> {more, 16#7e, 16#02};
+dec_huffman_lookup(16#58, 16#5) -> {more, 16#7e, 16#09};
+dec_huffman_lookup(16#58, 16#6) -> {more, 16#7e, 16#17};
+dec_huffman_lookup(16#58, 16#7) -> {ok, 16#7e, 16#28};
+dec_huffman_lookup(16#58, 16#8) -> {more, 16#5e, 16#01};
+dec_huffman_lookup(16#58, 16#9) -> {ok, 16#5e, 16#16};
+dec_huffman_lookup(16#58, 16#a) -> {more, 16#7d, 16#01};
+dec_huffman_lookup(16#58, 16#b) -> {ok, 16#7d, 16#16};
+dec_huffman_lookup(16#58, 16#c) -> {ok, 16#3c, 16#00};
+dec_huffman_lookup(16#58, 16#d) -> {ok, 16#60, 16#00};
+dec_huffman_lookup(16#58, 16#e) -> {ok, 16#7b, 16#00};
+dec_huffman_lookup(16#58, 16#f) -> {ok, undefined, 16#5f};
+dec_huffman_lookup(16#59, 16#0) -> {more, 16#5d, 16#03};
+dec_huffman_lookup(16#59, 16#1) -> {more, 16#5d, 16#06};
+dec_huffman_lookup(16#59, 16#2) -> {more, 16#5d, 16#0a};
+dec_huffman_lookup(16#59, 16#3) -> {more, 16#5d, 16#0f};
+dec_huffman_lookup(16#59, 16#4) -> {more, 16#5d, 16#18};
+dec_huffman_lookup(16#59, 16#5) -> {more, 16#5d, 16#1f};
+dec_huffman_lookup(16#59, 16#6) -> {more, 16#5d, 16#29};
+dec_huffman_lookup(16#59, 16#7) -> {ok, 16#5d, 16#38};
+dec_huffman_lookup(16#59, 16#8) -> {more, 16#7e, 16#03};
+dec_huffman_lookup(16#59, 16#9) -> {more, 16#7e, 16#06};
+dec_huffman_lookup(16#59, 16#a) -> {more, 16#7e, 16#0a};
+dec_huffman_lookup(16#59, 16#b) -> {more, 16#7e, 16#0f};
+dec_huffman_lookup(16#59, 16#c) -> {more, 16#7e, 16#18};
+dec_huffman_lookup(16#59, 16#d) -> {more, 16#7e, 16#1f};
+dec_huffman_lookup(16#59, 16#e) -> {more, 16#7e, 16#29};
+dec_huffman_lookup(16#59, 16#f) -> {ok, 16#7e, 16#38};
+dec_huffman_lookup(16#5a, 16#0) -> {more, 16#5e, 16#02};
+dec_huffman_lookup(16#5a, 16#1) -> {more, 16#5e, 16#09};
+dec_huffman_lookup(16#5a, 16#2) -> {more, 16#5e, 16#17};
+dec_huffman_lookup(16#5a, 16#3) -> {ok, 16#5e, 16#28};
+dec_huffman_lookup(16#5a, 16#4) -> {more, 16#7d, 16#02};
+dec_huffman_lookup(16#5a, 16#5) -> {more, 16#7d, 16#09};
+dec_huffman_lookup(16#5a, 16#6) -> {more, 16#7d, 16#17};
+dec_huffman_lookup(16#5a, 16#7) -> {ok, 16#7d, 16#28};
+dec_huffman_lookup(16#5a, 16#8) -> {more, 16#3c, 16#01};
+dec_huffman_lookup(16#5a, 16#9) -> {ok, 16#3c, 16#16};
+dec_huffman_lookup(16#5a, 16#a) -> {more, 16#60, 16#01};
+dec_huffman_lookup(16#5a, 16#b) -> {ok, 16#60, 16#16};
+dec_huffman_lookup(16#5a, 16#c) -> {more, 16#7b, 16#01};
+dec_huffman_lookup(16#5a, 16#d) -> {ok, 16#7b, 16#16};
+dec_huffman_lookup(16#5a, 16#e) -> {more, undefined, 16#60};
+dec_huffman_lookup(16#5a, 16#f) -> {ok, undefined, 16#6e};
+dec_huffman_lookup(16#5b, 16#0) -> {more, 16#5e, 16#03};
+dec_huffman_lookup(16#5b, 16#1) -> {more, 16#5e, 16#06};
+dec_huffman_lookup(16#5b, 16#2) -> {more, 16#5e, 16#0a};
+dec_huffman_lookup(16#5b, 16#3) -> {more, 16#5e, 16#0f};
+dec_huffman_lookup(16#5b, 16#4) -> {more, 16#5e, 16#18};
+dec_huffman_lookup(16#5b, 16#5) -> {more, 16#5e, 16#1f};
+dec_huffman_lookup(16#5b, 16#6) -> {more, 16#5e, 16#29};
+dec_huffman_lookup(16#5b, 16#7) -> {ok, 16#5e, 16#38};
+dec_huffman_lookup(16#5b, 16#8) -> {more, 16#7d, 16#03};
+dec_huffman_lookup(16#5b, 16#9) -> {more, 16#7d, 16#06};
+dec_huffman_lookup(16#5b, 16#a) -> {more, 16#7d, 16#0a};
+dec_huffman_lookup(16#5b, 16#b) -> {more, 16#7d, 16#0f};
+dec_huffman_lookup(16#5b, 16#c) -> {more, 16#7d, 16#18};
+dec_huffman_lookup(16#5b, 16#d) -> {more, 16#7d, 16#1f};
+dec_huffman_lookup(16#5b, 16#e) -> {more, 16#7d, 16#29};
+dec_huffman_lookup(16#5b, 16#f) -> {ok, 16#7d, 16#38};
+dec_huffman_lookup(16#5c, 16#0) -> {more, 16#3c, 16#02};
+dec_huffman_lookup(16#5c, 16#1) -> {more, 16#3c, 16#09};
+dec_huffman_lookup(16#5c, 16#2) -> {more, 16#3c, 16#17};
+dec_huffman_lookup(16#5c, 16#3) -> {ok, 16#3c, 16#28};
+dec_huffman_lookup(16#5c, 16#4) -> {more, 16#60, 16#02};
+dec_huffman_lookup(16#5c, 16#5) -> {more, 16#60, 16#09};
+dec_huffman_lookup(16#5c, 16#6) -> {more, 16#60, 16#17};
+dec_huffman_lookup(16#5c, 16#7) -> {ok, 16#60, 16#28};
+dec_huffman_lookup(16#5c, 16#8) -> {more, 16#7b, 16#02};
+dec_huffman_lookup(16#5c, 16#9) -> {more, 16#7b, 16#09};
+dec_huffman_lookup(16#5c, 16#a) -> {more, 16#7b, 16#17};
+dec_huffman_lookup(16#5c, 16#b) -> {ok, 16#7b, 16#28};
+dec_huffman_lookup(16#5c, 16#c) -> {more, undefined, 16#61};
+dec_huffman_lookup(16#5c, 16#d) -> {more, undefined, 16#65};
+dec_huffman_lookup(16#5c, 16#e) -> {more, undefined, 16#6f};
+dec_huffman_lookup(16#5c, 16#f) -> {ok, undefined, 16#85};
+dec_huffman_lookup(16#5d, 16#0) -> {more, 16#3c, 16#03};
+dec_huffman_lookup(16#5d, 16#1) -> {more, 16#3c, 16#06};
+dec_huffman_lookup(16#5d, 16#2) -> {more, 16#3c, 16#0a};
+dec_huffman_lookup(16#5d, 16#3) -> {more, 16#3c, 16#0f};
+dec_huffman_lookup(16#5d, 16#4) -> {more, 16#3c, 16#18};
+dec_huffman_lookup(16#5d, 16#5) -> {more, 16#3c, 16#1f};
+dec_huffman_lookup(16#5d, 16#6) -> {more, 16#3c, 16#29};
+dec_huffman_lookup(16#5d, 16#7) -> {ok, 16#3c, 16#38};
+dec_huffman_lookup(16#5d, 16#8) -> {more, 16#60, 16#03};
+dec_huffman_lookup(16#5d, 16#9) -> {more, 16#60, 16#06};
+dec_huffman_lookup(16#5d, 16#a) -> {more, 16#60, 16#0a};
+dec_huffman_lookup(16#5d, 16#b) -> {more, 16#60, 16#0f};
+dec_huffman_lookup(16#5d, 16#c) -> {more, 16#60, 16#18};
+dec_huffman_lookup(16#5d, 16#d) -> {more, 16#60, 16#1f};
+dec_huffman_lookup(16#5d, 16#e) -> {more, 16#60, 16#29};
+dec_huffman_lookup(16#5d, 16#f) -> {ok, 16#60, 16#38};
+dec_huffman_lookup(16#5e, 16#0) -> {more, 16#7b, 16#03};
+dec_huffman_lookup(16#5e, 16#1) -> {more, 16#7b, 16#06};
+dec_huffman_lookup(16#5e, 16#2) -> {more, 16#7b, 16#0a};
+dec_huffman_lookup(16#5e, 16#3) -> {more, 16#7b, 16#0f};
+dec_huffman_lookup(16#5e, 16#4) -> {more, 16#7b, 16#18};
+dec_huffman_lookup(16#5e, 16#5) -> {more, 16#7b, 16#1f};
+dec_huffman_lookup(16#5e, 16#6) -> {more, 16#7b, 16#29};
+dec_huffman_lookup(16#5e, 16#7) -> {ok, 16#7b, 16#38};
+dec_huffman_lookup(16#5e, 16#8) -> {more, undefined, 16#62};
+dec_huffman_lookup(16#5e, 16#9) -> {more, undefined, 16#63};
+dec_huffman_lookup(16#5e, 16#a) -> {more, undefined, 16#66};
+dec_huffman_lookup(16#5e, 16#b) -> {more, undefined, 16#69};
+dec_huffman_lookup(16#5e, 16#c) -> {more, undefined, 16#70};
+dec_huffman_lookup(16#5e, 16#d) -> {more, undefined, 16#77};
+dec_huffman_lookup(16#5e, 16#e) -> {more, undefined, 16#86};
+dec_huffman_lookup(16#5e, 16#f) -> {ok, undefined, 16#99};
+dec_huffman_lookup(16#5f, 16#0) -> {ok, 16#5c, 16#00};
+dec_huffman_lookup(16#5f, 16#1) -> {ok, 16#c3, 16#00};
+dec_huffman_lookup(16#5f, 16#2) -> {ok, 16#d0, 16#00};
+dec_huffman_lookup(16#5f, 16#3) -> {more, undefined, 16#64};
+dec_huffman_lookup(16#5f, 16#4) -> {more, undefined, 16#67};
+dec_huffman_lookup(16#5f, 16#5) -> {more, undefined, 16#68};
+dec_huffman_lookup(16#5f, 16#6) -> {more, undefined, 16#6a};
+dec_huffman_lookup(16#5f, 16#7) -> {more, undefined, 16#6b};
+dec_huffman_lookup(16#5f, 16#8) -> {more, undefined, 16#71};
+dec_huffman_lookup(16#5f, 16#9) -> {more, undefined, 16#74};
+dec_huffman_lookup(16#5f, 16#a) -> {more, undefined, 16#78};
+dec_huffman_lookup(16#5f, 16#b) -> {more, undefined, 16#7e};
+dec_huffman_lookup(16#5f, 16#c) -> {more, undefined, 16#87};
+dec_huffman_lookup(16#5f, 16#d) -> {more, undefined, 16#8e};
+dec_huffman_lookup(16#5f, 16#e) -> {more, undefined, 16#9a};
+dec_huffman_lookup(16#5f, 16#f) -> {ok, undefined, 16#a9};
+dec_huffman_lookup(16#60, 16#0) -> {more, 16#5c, 16#01};
+dec_huffman_lookup(16#60, 16#1) -> {ok, 16#5c, 16#16};
+dec_huffman_lookup(16#60, 16#2) -> {more, 16#c3, 16#01};
+dec_huffman_lookup(16#60, 16#3) -> {ok, 16#c3, 16#16};
+dec_huffman_lookup(16#60, 16#4) -> {more, 16#d0, 16#01};
+dec_huffman_lookup(16#60, 16#5) -> {ok, 16#d0, 16#16};
+dec_huffman_lookup(16#60, 16#6) -> {ok, 16#80, 16#00};
+dec_huffman_lookup(16#60, 16#7) -> {ok, 16#82, 16#00};
+dec_huffman_lookup(16#60, 16#8) -> {ok, 16#83, 16#00};
+dec_huffman_lookup(16#60, 16#9) -> {ok, 16#a2, 16#00};
+dec_huffman_lookup(16#60, 16#a) -> {ok, 16#b8, 16#00};
+dec_huffman_lookup(16#60, 16#b) -> {ok, 16#c2, 16#00};
+dec_huffman_lookup(16#60, 16#c) -> {ok, 16#e0, 16#00};
+dec_huffman_lookup(16#60, 16#d) -> {ok, 16#e2, 16#00};
+dec_huffman_lookup(16#60, 16#e) -> {more, undefined, 16#6c};
+dec_huffman_lookup(16#60, 16#f) -> {more, undefined, 16#6d};
+dec_huffman_lookup(16#61, 16#0) -> {more, 16#5c, 16#02};
+dec_huffman_lookup(16#61, 16#1) -> {more, 16#5c, 16#09};
+dec_huffman_lookup(16#61, 16#2) -> {more, 16#5c, 16#17};
+dec_huffman_lookup(16#61, 16#3) -> {ok, 16#5c, 16#28};
+dec_huffman_lookup(16#61, 16#4) -> {more, 16#c3, 16#02};
+dec_huffman_lookup(16#61, 16#5) -> {more, 16#c3, 16#09};
+dec_huffman_lookup(16#61, 16#6) -> {more, 16#c3, 16#17};
+dec_huffman_lookup(16#61, 16#7) -> {ok, 16#c3, 16#28};
+dec_huffman_lookup(16#61, 16#8) -> {more, 16#d0, 16#02};
+dec_huffman_lookup(16#61, 16#9) -> {more, 16#d0, 16#09};
+dec_huffman_lookup(16#61, 16#a) -> {more, 16#d0, 16#17};
+dec_huffman_lookup(16#61, 16#b) -> {ok, 16#d0, 16#28};
+dec_huffman_lookup(16#61, 16#c) -> {more, 16#80, 16#01};
+dec_huffman_lookup(16#61, 16#d) -> {ok, 16#80, 16#16};
+dec_huffman_lookup(16#61, 16#e) -> {more, 16#82, 16#01};
+dec_huffman_lookup(16#61, 16#f) -> {ok, 16#82, 16#16};
+dec_huffman_lookup(16#62, 16#0) -> {more, 16#5c, 16#03};
+dec_huffman_lookup(16#62, 16#1) -> {more, 16#5c, 16#06};
+dec_huffman_lookup(16#62, 16#2) -> {more, 16#5c, 16#0a};
+dec_huffman_lookup(16#62, 16#3) -> {more, 16#5c, 16#0f};
+dec_huffman_lookup(16#62, 16#4) -> {more, 16#5c, 16#18};
+dec_huffman_lookup(16#62, 16#5) -> {more, 16#5c, 16#1f};
+dec_huffman_lookup(16#62, 16#6) -> {more, 16#5c, 16#29};
+dec_huffman_lookup(16#62, 16#7) -> {ok, 16#5c, 16#38};
+dec_huffman_lookup(16#62, 16#8) -> {more, 16#c3, 16#03};
+dec_huffman_lookup(16#62, 16#9) -> {more, 16#c3, 16#06};
+dec_huffman_lookup(16#62, 16#a) -> {more, 16#c3, 16#0a};
+dec_huffman_lookup(16#62, 16#b) -> {more, 16#c3, 16#0f};
+dec_huffman_lookup(16#62, 16#c) -> {more, 16#c3, 16#18};
+dec_huffman_lookup(16#62, 16#d) -> {more, 16#c3, 16#1f};
+dec_huffman_lookup(16#62, 16#e) -> {more, 16#c3, 16#29};
+dec_huffman_lookup(16#62, 16#f) -> {ok, 16#c3, 16#38};
+dec_huffman_lookup(16#63, 16#0) -> {more, 16#d0, 16#03};
+dec_huffman_lookup(16#63, 16#1) -> {more, 16#d0, 16#06};
+dec_huffman_lookup(16#63, 16#2) -> {more, 16#d0, 16#0a};
+dec_huffman_lookup(16#63, 16#3) -> {more, 16#d0, 16#0f};
+dec_huffman_lookup(16#63, 16#4) -> {more, 16#d0, 16#18};
+dec_huffman_lookup(16#63, 16#5) -> {more, 16#d0, 16#1f};
+dec_huffman_lookup(16#63, 16#6) -> {more, 16#d0, 16#29};
+dec_huffman_lookup(16#63, 16#7) -> {ok, 16#d0, 16#38};
+dec_huffman_lookup(16#63, 16#8) -> {more, 16#80, 16#02};
+dec_huffman_lookup(16#63, 16#9) -> {more, 16#80, 16#09};
+dec_huffman_lookup(16#63, 16#a) -> {more, 16#80, 16#17};
+dec_huffman_lookup(16#63, 16#b) -> {ok, 16#80, 16#28};
+dec_huffman_lookup(16#63, 16#c) -> {more, 16#82, 16#02};
+dec_huffman_lookup(16#63, 16#d) -> {more, 16#82, 16#09};
+dec_huffman_lookup(16#63, 16#e) -> {more, 16#82, 16#17};
+dec_huffman_lookup(16#63, 16#f) -> {ok, 16#82, 16#28};
+dec_huffman_lookup(16#64, 16#0) -> {more, 16#80, 16#03};
+dec_huffman_lookup(16#64, 16#1) -> {more, 16#80, 16#06};
+dec_huffman_lookup(16#64, 16#2) -> {more, 16#80, 16#0a};
+dec_huffman_lookup(16#64, 16#3) -> {more, 16#80, 16#0f};
+dec_huffman_lookup(16#64, 16#4) -> {more, 16#80, 16#18};
+dec_huffman_lookup(16#64, 16#5) -> {more, 16#80, 16#1f};
+dec_huffman_lookup(16#64, 16#6) -> {more, 16#80, 16#29};
+dec_huffman_lookup(16#64, 16#7) -> {ok, 16#80, 16#38};
+dec_huffman_lookup(16#64, 16#8) -> {more, 16#82, 16#03};
+dec_huffman_lookup(16#64, 16#9) -> {more, 16#82, 16#06};
+dec_huffman_lookup(16#64, 16#a) -> {more, 16#82, 16#0a};
+dec_huffman_lookup(16#64, 16#b) -> {more, 16#82, 16#0f};
+dec_huffman_lookup(16#64, 16#c) -> {more, 16#82, 16#18};
+dec_huffman_lookup(16#64, 16#d) -> {more, 16#82, 16#1f};
+dec_huffman_lookup(16#64, 16#e) -> {more, 16#82, 16#29};
+dec_huffman_lookup(16#64, 16#f) -> {ok, 16#82, 16#38};
+dec_huffman_lookup(16#65, 16#0) -> {more, 16#83, 16#01};
+dec_huffman_lookup(16#65, 16#1) -> {ok, 16#83, 16#16};
+dec_huffman_lookup(16#65, 16#2) -> {more, 16#a2, 16#01};
+dec_huffman_lookup(16#65, 16#3) -> {ok, 16#a2, 16#16};
+dec_huffman_lookup(16#65, 16#4) -> {more, 16#b8, 16#01};
+dec_huffman_lookup(16#65, 16#5) -> {ok, 16#b8, 16#16};
+dec_huffman_lookup(16#65, 16#6) -> {more, 16#c2, 16#01};
+dec_huffman_lookup(16#65, 16#7) -> {ok, 16#c2, 16#16};
+dec_huffman_lookup(16#65, 16#8) -> {more, 16#e0, 16#01};
+dec_huffman_lookup(16#65, 16#9) -> {ok, 16#e0, 16#16};
+dec_huffman_lookup(16#65, 16#a) -> {more, 16#e2, 16#01};
+dec_huffman_lookup(16#65, 16#b) -> {ok, 16#e2, 16#16};
+dec_huffman_lookup(16#65, 16#c) -> {ok, 16#99, 16#00};
+dec_huffman_lookup(16#65, 16#d) -> {ok, 16#a1, 16#00};
+dec_huffman_lookup(16#65, 16#e) -> {ok, 16#a7, 16#00};
+dec_huffman_lookup(16#65, 16#f) -> {ok, 16#ac, 16#00};
+dec_huffman_lookup(16#66, 16#0) -> {more, 16#83, 16#02};
+dec_huffman_lookup(16#66, 16#1) -> {more, 16#83, 16#09};
+dec_huffman_lookup(16#66, 16#2) -> {more, 16#83, 16#17};
+dec_huffman_lookup(16#66, 16#3) -> {ok, 16#83, 16#28};
+dec_huffman_lookup(16#66, 16#4) -> {more, 16#a2, 16#02};
+dec_huffman_lookup(16#66, 16#5) -> {more, 16#a2, 16#09};
+dec_huffman_lookup(16#66, 16#6) -> {more, 16#a2, 16#17};
+dec_huffman_lookup(16#66, 16#7) -> {ok, 16#a2, 16#28};
+dec_huffman_lookup(16#66, 16#8) -> {more, 16#b8, 16#02};
+dec_huffman_lookup(16#66, 16#9) -> {more, 16#b8, 16#09};
+dec_huffman_lookup(16#66, 16#a) -> {more, 16#b8, 16#17};
+dec_huffman_lookup(16#66, 16#b) -> {ok, 16#b8, 16#28};
+dec_huffman_lookup(16#66, 16#c) -> {more, 16#c2, 16#02};
+dec_huffman_lookup(16#66, 16#d) -> {more, 16#c2, 16#09};
+dec_huffman_lookup(16#66, 16#e) -> {more, 16#c2, 16#17};
+dec_huffman_lookup(16#66, 16#f) -> {ok, 16#c2, 16#28};
+dec_huffman_lookup(16#67, 16#0) -> {more, 16#83, 16#03};
+dec_huffman_lookup(16#67, 16#1) -> {more, 16#83, 16#06};
+dec_huffman_lookup(16#67, 16#2) -> {more, 16#83, 16#0a};
+dec_huffman_lookup(16#67, 16#3) -> {more, 16#83, 16#0f};
+dec_huffman_lookup(16#67, 16#4) -> {more, 16#83, 16#18};
+dec_huffman_lookup(16#67, 16#5) -> {more, 16#83, 16#1f};
+dec_huffman_lookup(16#67, 16#6) -> {more, 16#83, 16#29};
+dec_huffman_lookup(16#67, 16#7) -> {ok, 16#83, 16#38};
+dec_huffman_lookup(16#67, 16#8) -> {more, 16#a2, 16#03};
+dec_huffman_lookup(16#67, 16#9) -> {more, 16#a2, 16#06};
+dec_huffman_lookup(16#67, 16#a) -> {more, 16#a2, 16#0a};
+dec_huffman_lookup(16#67, 16#b) -> {more, 16#a2, 16#0f};
+dec_huffman_lookup(16#67, 16#c) -> {more, 16#a2, 16#18};
+dec_huffman_lookup(16#67, 16#d) -> {more, 16#a2, 16#1f};
+dec_huffman_lookup(16#67, 16#e) -> {more, 16#a2, 16#29};
+dec_huffman_lookup(16#67, 16#f) -> {ok, 16#a2, 16#38};
+dec_huffman_lookup(16#68, 16#0) -> {more, 16#b8, 16#03};
+dec_huffman_lookup(16#68, 16#1) -> {more, 16#b8, 16#06};
+dec_huffman_lookup(16#68, 16#2) -> {more, 16#b8, 16#0a};
+dec_huffman_lookup(16#68, 16#3) -> {more, 16#b8, 16#0f};
+dec_huffman_lookup(16#68, 16#4) -> {more, 16#b8, 16#18};
+dec_huffman_lookup(16#68, 16#5) -> {more, 16#b8, 16#1f};
+dec_huffman_lookup(16#68, 16#6) -> {more, 16#b8, 16#29};
+dec_huffman_lookup(16#68, 16#7) -> {ok, 16#b8, 16#38};
+dec_huffman_lookup(16#68, 16#8) -> {more, 16#c2, 16#03};
+dec_huffman_lookup(16#68, 16#9) -> {more, 16#c2, 16#06};
+dec_huffman_lookup(16#68, 16#a) -> {more, 16#c2, 16#0a};
+dec_huffman_lookup(16#68, 16#b) -> {more, 16#c2, 16#0f};
+dec_huffman_lookup(16#68, 16#c) -> {more, 16#c2, 16#18};
+dec_huffman_lookup(16#68, 16#d) -> {more, 16#c2, 16#1f};
+dec_huffman_lookup(16#68, 16#e) -> {more, 16#c2, 16#29};
+dec_huffman_lookup(16#68, 16#f) -> {ok, 16#c2, 16#38};
+dec_huffman_lookup(16#69, 16#0) -> {more, 16#e0, 16#02};
+dec_huffman_lookup(16#69, 16#1) -> {more, 16#e0, 16#09};
+dec_huffman_lookup(16#69, 16#2) -> {more, 16#e0, 16#17};
+dec_huffman_lookup(16#69, 16#3) -> {ok, 16#e0, 16#28};
+dec_huffman_lookup(16#69, 16#4) -> {more, 16#e2, 16#02};
+dec_huffman_lookup(16#69, 16#5) -> {more, 16#e2, 16#09};
+dec_huffman_lookup(16#69, 16#6) -> {more, 16#e2, 16#17};
+dec_huffman_lookup(16#69, 16#7) -> {ok, 16#e2, 16#28};
+dec_huffman_lookup(16#69, 16#8) -> {more, 16#99, 16#01};
+dec_huffman_lookup(16#69, 16#9) -> {ok, 16#99, 16#16};
+dec_huffman_lookup(16#69, 16#a) -> {more, 16#a1, 16#01};
+dec_huffman_lookup(16#69, 16#b) -> {ok, 16#a1, 16#16};
+dec_huffman_lookup(16#69, 16#c) -> {more, 16#a7, 16#01};
+dec_huffman_lookup(16#69, 16#d) -> {ok, 16#a7, 16#16};
+dec_huffman_lookup(16#69, 16#e) -> {more, 16#ac, 16#01};
+dec_huffman_lookup(16#69, 16#f) -> {ok, 16#ac, 16#16};
+dec_huffman_lookup(16#6a, 16#0) -> {more, 16#e0, 16#03};
+dec_huffman_lookup(16#6a, 16#1) -> {more, 16#e0, 16#06};
+dec_huffman_lookup(16#6a, 16#2) -> {more, 16#e0, 16#0a};
+dec_huffman_lookup(16#6a, 16#3) -> {more, 16#e0, 16#0f};
+dec_huffman_lookup(16#6a, 16#4) -> {more, 16#e0, 16#18};
+dec_huffman_lookup(16#6a, 16#5) -> {more, 16#e0, 16#1f};
+dec_huffman_lookup(16#6a, 16#6) -> {more, 16#e0, 16#29};
+dec_huffman_lookup(16#6a, 16#7) -> {ok, 16#e0, 16#38};
+dec_huffman_lookup(16#6a, 16#8) -> {more, 16#e2, 16#03};
+dec_huffman_lookup(16#6a, 16#9) -> {more, 16#e2, 16#06};
+dec_huffman_lookup(16#6a, 16#a) -> {more, 16#e2, 16#0a};
+dec_huffman_lookup(16#6a, 16#b) -> {more, 16#e2, 16#0f};
+dec_huffman_lookup(16#6a, 16#c) -> {more, 16#e2, 16#18};
+dec_huffman_lookup(16#6a, 16#d) -> {more, 16#e2, 16#1f};
+dec_huffman_lookup(16#6a, 16#e) -> {more, 16#e2, 16#29};
+dec_huffman_lookup(16#6a, 16#f) -> {ok, 16#e2, 16#38};
+dec_huffman_lookup(16#6b, 16#0) -> {more, 16#99, 16#02};
+dec_huffman_lookup(16#6b, 16#1) -> {more, 16#99, 16#09};
+dec_huffman_lookup(16#6b, 16#2) -> {more, 16#99, 16#17};
+dec_huffman_lookup(16#6b, 16#3) -> {ok, 16#99, 16#28};
+dec_huffman_lookup(16#6b, 16#4) -> {more, 16#a1, 16#02};
+dec_huffman_lookup(16#6b, 16#5) -> {more, 16#a1, 16#09};
+dec_huffman_lookup(16#6b, 16#6) -> {more, 16#a1, 16#17};
+dec_huffman_lookup(16#6b, 16#7) -> {ok, 16#a1, 16#28};
+dec_huffman_lookup(16#6b, 16#8) -> {more, 16#a7, 16#02};
+dec_huffman_lookup(16#6b, 16#9) -> {more, 16#a7, 16#09};
+dec_huffman_lookup(16#6b, 16#a) -> {more, 16#a7, 16#17};
+dec_huffman_lookup(16#6b, 16#b) -> {ok, 16#a7, 16#28};
+dec_huffman_lookup(16#6b, 16#c) -> {more, 16#ac, 16#02};
+dec_huffman_lookup(16#6b, 16#d) -> {more, 16#ac, 16#09};
+dec_huffman_lookup(16#6b, 16#e) -> {more, 16#ac, 16#17};
+dec_huffman_lookup(16#6b, 16#f) -> {ok, 16#ac, 16#28};
+dec_huffman_lookup(16#6c, 16#0) -> {more, 16#99, 16#03};
+dec_huffman_lookup(16#6c, 16#1) -> {more, 16#99, 16#06};
+dec_huffman_lookup(16#6c, 16#2) -> {more, 16#99, 16#0a};
+dec_huffman_lookup(16#6c, 16#3) -> {more, 16#99, 16#0f};
+dec_huffman_lookup(16#6c, 16#4) -> {more, 16#99, 16#18};
+dec_huffman_lookup(16#6c, 16#5) -> {more, 16#99, 16#1f};
+dec_huffman_lookup(16#6c, 16#6) -> {more, 16#99, 16#29};
+dec_huffman_lookup(16#6c, 16#7) -> {ok, 16#99, 16#38};
+dec_huffman_lookup(16#6c, 16#8) -> {more, 16#a1, 16#03};
+dec_huffman_lookup(16#6c, 16#9) -> {more, 16#a1, 16#06};
+dec_huffman_lookup(16#6c, 16#a) -> {more, 16#a1, 16#0a};
+dec_huffman_lookup(16#6c, 16#b) -> {more, 16#a1, 16#0f};
+dec_huffman_lookup(16#6c, 16#c) -> {more, 16#a1, 16#18};
+dec_huffman_lookup(16#6c, 16#d) -> {more, 16#a1, 16#1f};
+dec_huffman_lookup(16#6c, 16#e) -> {more, 16#a1, 16#29};
+dec_huffman_lookup(16#6c, 16#f) -> {ok, 16#a1, 16#38};
+dec_huffman_lookup(16#6d, 16#0) -> {more, 16#a7, 16#03};
+dec_huffman_lookup(16#6d, 16#1) -> {more, 16#a7, 16#06};
+dec_huffman_lookup(16#6d, 16#2) -> {more, 16#a7, 16#0a};
+dec_huffman_lookup(16#6d, 16#3) -> {more, 16#a7, 16#0f};
+dec_huffman_lookup(16#6d, 16#4) -> {more, 16#a7, 16#18};
+dec_huffman_lookup(16#6d, 16#5) -> {more, 16#a7, 16#1f};
+dec_huffman_lookup(16#6d, 16#6) -> {more, 16#a7, 16#29};
+dec_huffman_lookup(16#6d, 16#7) -> {ok, 16#a7, 16#38};
+dec_huffman_lookup(16#6d, 16#8) -> {more, 16#ac, 16#03};
+dec_huffman_lookup(16#6d, 16#9) -> {more, 16#ac, 16#06};
+dec_huffman_lookup(16#6d, 16#a) -> {more, 16#ac, 16#0a};
+dec_huffman_lookup(16#6d, 16#b) -> {more, 16#ac, 16#0f};
+dec_huffman_lookup(16#6d, 16#c) -> {more, 16#ac, 16#18};
+dec_huffman_lookup(16#6d, 16#d) -> {more, 16#ac, 16#1f};
+dec_huffman_lookup(16#6d, 16#e) -> {more, 16#ac, 16#29};
+dec_huffman_lookup(16#6d, 16#f) -> {ok, 16#ac, 16#38};
+dec_huffman_lookup(16#6e, 16#0) -> {more, undefined, 16#72};
+dec_huffman_lookup(16#6e, 16#1) -> {more, undefined, 16#73};
+dec_huffman_lookup(16#6e, 16#2) -> {more, undefined, 16#75};
+dec_huffman_lookup(16#6e, 16#3) -> {more, undefined, 16#76};
+dec_huffman_lookup(16#6e, 16#4) -> {more, undefined, 16#79};
+dec_huffman_lookup(16#6e, 16#5) -> {more, undefined, 16#7b};
+dec_huffman_lookup(16#6e, 16#6) -> {more, undefined, 16#7f};
+dec_huffman_lookup(16#6e, 16#7) -> {more, undefined, 16#82};
+dec_huffman_lookup(16#6e, 16#8) -> {more, undefined, 16#88};
+dec_huffman_lookup(16#6e, 16#9) -> {more, undefined, 16#8b};
+dec_huffman_lookup(16#6e, 16#a) -> {more, undefined, 16#8f};
+dec_huffman_lookup(16#6e, 16#b) -> {more, undefined, 16#92};
+dec_huffman_lookup(16#6e, 16#c) -> {more, undefined, 16#9b};
+dec_huffman_lookup(16#6e, 16#d) -> {more, undefined, 16#a2};
+dec_huffman_lookup(16#6e, 16#e) -> {more, undefined, 16#aa};
+dec_huffman_lookup(16#6e, 16#f) -> {ok, undefined, 16#b4};
+dec_huffman_lookup(16#6f, 16#0) -> {ok, 16#b0, 16#00};
+dec_huffman_lookup(16#6f, 16#1) -> {ok, 16#b1, 16#00};
+dec_huffman_lookup(16#6f, 16#2) -> {ok, 16#b3, 16#00};
+dec_huffman_lookup(16#6f, 16#3) -> {ok, 16#d1, 16#00};
+dec_huffman_lookup(16#6f, 16#4) -> {ok, 16#d8, 16#00};
+dec_huffman_lookup(16#6f, 16#5) -> {ok, 16#d9, 16#00};
+dec_huffman_lookup(16#6f, 16#6) -> {ok, 16#e3, 16#00};
+dec_huffman_lookup(16#6f, 16#7) -> {ok, 16#e5, 16#00};
+dec_huffman_lookup(16#6f, 16#8) -> {ok, 16#e6, 16#00};
+dec_huffman_lookup(16#6f, 16#9) -> {more, undefined, 16#7a};
+dec_huffman_lookup(16#6f, 16#a) -> {more, undefined, 16#7c};
+dec_huffman_lookup(16#6f, 16#b) -> {more, undefined, 16#7d};
+dec_huffman_lookup(16#6f, 16#c) -> {more, undefined, 16#80};
+dec_huffman_lookup(16#6f, 16#d) -> {more, undefined, 16#81};
+dec_huffman_lookup(16#6f, 16#e) -> {more, undefined, 16#83};
+dec_huffman_lookup(16#6f, 16#f) -> {more, undefined, 16#84};
+dec_huffman_lookup(16#70, 16#0) -> {more, 16#b0, 16#01};
+dec_huffman_lookup(16#70, 16#1) -> {ok, 16#b0, 16#16};
+dec_huffman_lookup(16#70, 16#2) -> {more, 16#b1, 16#01};
+dec_huffman_lookup(16#70, 16#3) -> {ok, 16#b1, 16#16};
+dec_huffman_lookup(16#70, 16#4) -> {more, 16#b3, 16#01};
+dec_huffman_lookup(16#70, 16#5) -> {ok, 16#b3, 16#16};
+dec_huffman_lookup(16#70, 16#6) -> {more, 16#d1, 16#01};
+dec_huffman_lookup(16#70, 16#7) -> {ok, 16#d1, 16#16};
+dec_huffman_lookup(16#70, 16#8) -> {more, 16#d8, 16#01};
+dec_huffman_lookup(16#70, 16#9) -> {ok, 16#d8, 16#16};
+dec_huffman_lookup(16#70, 16#a) -> {more, 16#d9, 16#01};
+dec_huffman_lookup(16#70, 16#b) -> {ok, 16#d9, 16#16};
+dec_huffman_lookup(16#70, 16#c) -> {more, 16#e3, 16#01};
+dec_huffman_lookup(16#70, 16#d) -> {ok, 16#e3, 16#16};
+dec_huffman_lookup(16#70, 16#e) -> {more, 16#e5, 16#01};
+dec_huffman_lookup(16#70, 16#f) -> {ok, 16#e5, 16#16};
+dec_huffman_lookup(16#71, 16#0) -> {more, 16#b0, 16#02};
+dec_huffman_lookup(16#71, 16#1) -> {more, 16#b0, 16#09};
+dec_huffman_lookup(16#71, 16#2) -> {more, 16#b0, 16#17};
+dec_huffman_lookup(16#71, 16#3) -> {ok, 16#b0, 16#28};
+dec_huffman_lookup(16#71, 16#4) -> {more, 16#b1, 16#02};
+dec_huffman_lookup(16#71, 16#5) -> {more, 16#b1, 16#09};
+dec_huffman_lookup(16#71, 16#6) -> {more, 16#b1, 16#17};
+dec_huffman_lookup(16#71, 16#7) -> {ok, 16#b1, 16#28};
+dec_huffman_lookup(16#71, 16#8) -> {more, 16#b3, 16#02};
+dec_huffman_lookup(16#71, 16#9) -> {more, 16#b3, 16#09};
+dec_huffman_lookup(16#71, 16#a) -> {more, 16#b3, 16#17};
+dec_huffman_lookup(16#71, 16#b) -> {ok, 16#b3, 16#28};
+dec_huffman_lookup(16#71, 16#c) -> {more, 16#d1, 16#02};
+dec_huffman_lookup(16#71, 16#d) -> {more, 16#d1, 16#09};
+dec_huffman_lookup(16#71, 16#e) -> {more, 16#d1, 16#17};
+dec_huffman_lookup(16#71, 16#f) -> {ok, 16#d1, 16#28};
+dec_huffman_lookup(16#72, 16#0) -> {more, 16#b0, 16#03};
+dec_huffman_lookup(16#72, 16#1) -> {more, 16#b0, 16#06};
+dec_huffman_lookup(16#72, 16#2) -> {more, 16#b0, 16#0a};
+dec_huffman_lookup(16#72, 16#3) -> {more, 16#b0, 16#0f};
+dec_huffman_lookup(16#72, 16#4) -> {more, 16#b0, 16#18};
+dec_huffman_lookup(16#72, 16#5) -> {more, 16#b0, 16#1f};
+dec_huffman_lookup(16#72, 16#6) -> {more, 16#b0, 16#29};
+dec_huffman_lookup(16#72, 16#7) -> {ok, 16#b0, 16#38};
+dec_huffman_lookup(16#72, 16#8) -> {more, 16#b1, 16#03};
+dec_huffman_lookup(16#72, 16#9) -> {more, 16#b1, 16#06};
+dec_huffman_lookup(16#72, 16#a) -> {more, 16#b1, 16#0a};
+dec_huffman_lookup(16#72, 16#b) -> {more, 16#b1, 16#0f};
+dec_huffman_lookup(16#72, 16#c) -> {more, 16#b1, 16#18};
+dec_huffman_lookup(16#72, 16#d) -> {more, 16#b1, 16#1f};
+dec_huffman_lookup(16#72, 16#e) -> {more, 16#b1, 16#29};
+dec_huffman_lookup(16#72, 16#f) -> {ok, 16#b1, 16#38};
+dec_huffman_lookup(16#73, 16#0) -> {more, 16#b3, 16#03};
+dec_huffman_lookup(16#73, 16#1) -> {more, 16#b3, 16#06};
+dec_huffman_lookup(16#73, 16#2) -> {more, 16#b3, 16#0a};
+dec_huffman_lookup(16#73, 16#3) -> {more, 16#b3, 16#0f};
+dec_huffman_lookup(16#73, 16#4) -> {more, 16#b3, 16#18};
+dec_huffman_lookup(16#73, 16#5) -> {more, 16#b3, 16#1f};
+dec_huffman_lookup(16#73, 16#6) -> {more, 16#b3, 16#29};
+dec_huffman_lookup(16#73, 16#7) -> {ok, 16#b3, 16#38};
+dec_huffman_lookup(16#73, 16#8) -> {more, 16#d1, 16#03};
+dec_huffman_lookup(16#73, 16#9) -> {more, 16#d1, 16#06};
+dec_huffman_lookup(16#73, 16#a) -> {more, 16#d1, 16#0a};
+dec_huffman_lookup(16#73, 16#b) -> {more, 16#d1, 16#0f};
+dec_huffman_lookup(16#73, 16#c) -> {more, 16#d1, 16#18};
+dec_huffman_lookup(16#73, 16#d) -> {more, 16#d1, 16#1f};
+dec_huffman_lookup(16#73, 16#e) -> {more, 16#d1, 16#29};
+dec_huffman_lookup(16#73, 16#f) -> {ok, 16#d1, 16#38};
+dec_huffman_lookup(16#74, 16#0) -> {more, 16#d8, 16#02};
+dec_huffman_lookup(16#74, 16#1) -> {more, 16#d8, 16#09};
+dec_huffman_lookup(16#74, 16#2) -> {more, 16#d8, 16#17};
+dec_huffman_lookup(16#74, 16#3) -> {ok, 16#d8, 16#28};
+dec_huffman_lookup(16#74, 16#4) -> {more, 16#d9, 16#02};
+dec_huffman_lookup(16#74, 16#5) -> {more, 16#d9, 16#09};
+dec_huffman_lookup(16#74, 16#6) -> {more, 16#d9, 16#17};
+dec_huffman_lookup(16#74, 16#7) -> {ok, 16#d9, 16#28};
+dec_huffman_lookup(16#74, 16#8) -> {more, 16#e3, 16#02};
+dec_huffman_lookup(16#74, 16#9) -> {more, 16#e3, 16#09};
+dec_huffman_lookup(16#74, 16#a) -> {more, 16#e3, 16#17};
+dec_huffman_lookup(16#74, 16#b) -> {ok, 16#e3, 16#28};
+dec_huffman_lookup(16#74, 16#c) -> {more, 16#e5, 16#02};
+dec_huffman_lookup(16#74, 16#d) -> {more, 16#e5, 16#09};
+dec_huffman_lookup(16#74, 16#e) -> {more, 16#e5, 16#17};
+dec_huffman_lookup(16#74, 16#f) -> {ok, 16#e5, 16#28};
+dec_huffman_lookup(16#75, 16#0) -> {more, 16#d8, 16#03};
+dec_huffman_lookup(16#75, 16#1) -> {more, 16#d8, 16#06};
+dec_huffman_lookup(16#75, 16#2) -> {more, 16#d8, 16#0a};
+dec_huffman_lookup(16#75, 16#3) -> {more, 16#d8, 16#0f};
+dec_huffman_lookup(16#75, 16#4) -> {more, 16#d8, 16#18};
+dec_huffman_lookup(16#75, 16#5) -> {more, 16#d8, 16#1f};
+dec_huffman_lookup(16#75, 16#6) -> {more, 16#d8, 16#29};
+dec_huffman_lookup(16#75, 16#7) -> {ok, 16#d8, 16#38};
+dec_huffman_lookup(16#75, 16#8) -> {more, 16#d9, 16#03};
+dec_huffman_lookup(16#75, 16#9) -> {more, 16#d9, 16#06};
+dec_huffman_lookup(16#75, 16#a) -> {more, 16#d9, 16#0a};
+dec_huffman_lookup(16#75, 16#b) -> {more, 16#d9, 16#0f};
+dec_huffman_lookup(16#75, 16#c) -> {more, 16#d9, 16#18};
+dec_huffman_lookup(16#75, 16#d) -> {more, 16#d9, 16#1f};
+dec_huffman_lookup(16#75, 16#e) -> {more, 16#d9, 16#29};
+dec_huffman_lookup(16#75, 16#f) -> {ok, 16#d9, 16#38};
+dec_huffman_lookup(16#76, 16#0) -> {more, 16#e3, 16#03};
+dec_huffman_lookup(16#76, 16#1) -> {more, 16#e3, 16#06};
+dec_huffman_lookup(16#76, 16#2) -> {more, 16#e3, 16#0a};
+dec_huffman_lookup(16#76, 16#3) -> {more, 16#e3, 16#0f};
+dec_huffman_lookup(16#76, 16#4) -> {more, 16#e3, 16#18};
+dec_huffman_lookup(16#76, 16#5) -> {more, 16#e3, 16#1f};
+dec_huffman_lookup(16#76, 16#6) -> {more, 16#e3, 16#29};
+dec_huffman_lookup(16#76, 16#7) -> {ok, 16#e3, 16#38};
+dec_huffman_lookup(16#76, 16#8) -> {more, 16#e5, 16#03};
+dec_huffman_lookup(16#76, 16#9) -> {more, 16#e5, 16#06};
+dec_huffman_lookup(16#76, 16#a) -> {more, 16#e5, 16#0a};
+dec_huffman_lookup(16#76, 16#b) -> {more, 16#e5, 16#0f};
+dec_huffman_lookup(16#76, 16#c) -> {more, 16#e5, 16#18};
+dec_huffman_lookup(16#76, 16#d) -> {more, 16#e5, 16#1f};
+dec_huffman_lookup(16#76, 16#e) -> {more, 16#e5, 16#29};
+dec_huffman_lookup(16#76, 16#f) -> {ok, 16#e5, 16#38};
+dec_huffman_lookup(16#77, 16#0) -> {more, 16#e6, 16#01};
+dec_huffman_lookup(16#77, 16#1) -> {ok, 16#e6, 16#16};
+dec_huffman_lookup(16#77, 16#2) -> {ok, 16#81, 16#00};
+dec_huffman_lookup(16#77, 16#3) -> {ok, 16#84, 16#00};
+dec_huffman_lookup(16#77, 16#4) -> {ok, 16#85, 16#00};
+dec_huffman_lookup(16#77, 16#5) -> {ok, 16#86, 16#00};
+dec_huffman_lookup(16#77, 16#6) -> {ok, 16#88, 16#00};
+dec_huffman_lookup(16#77, 16#7) -> {ok, 16#92, 16#00};
+dec_huffman_lookup(16#77, 16#8) -> {ok, 16#9a, 16#00};
+dec_huffman_lookup(16#77, 16#9) -> {ok, 16#9c, 16#00};
+dec_huffman_lookup(16#77, 16#a) -> {ok, 16#a0, 16#00};
+dec_huffman_lookup(16#77, 16#b) -> {ok, 16#a3, 16#00};
+dec_huffman_lookup(16#77, 16#c) -> {ok, 16#a4, 16#00};
+dec_huffman_lookup(16#77, 16#d) -> {ok, 16#a9, 16#00};
+dec_huffman_lookup(16#77, 16#e) -> {ok, 16#aa, 16#00};
+dec_huffman_lookup(16#77, 16#f) -> {ok, 16#ad, 16#00};
+dec_huffman_lookup(16#78, 16#0) -> {more, 16#e6, 16#02};
+dec_huffman_lookup(16#78, 16#1) -> {more, 16#e6, 16#09};
+dec_huffman_lookup(16#78, 16#2) -> {more, 16#e6, 16#17};
+dec_huffman_lookup(16#78, 16#3) -> {ok, 16#e6, 16#28};
+dec_huffman_lookup(16#78, 16#4) -> {more, 16#81, 16#01};
+dec_huffman_lookup(16#78, 16#5) -> {ok, 16#81, 16#16};
+dec_huffman_lookup(16#78, 16#6) -> {more, 16#84, 16#01};
+dec_huffman_lookup(16#78, 16#7) -> {ok, 16#84, 16#16};
+dec_huffman_lookup(16#78, 16#8) -> {more, 16#85, 16#01};
+dec_huffman_lookup(16#78, 16#9) -> {ok, 16#85, 16#16};
+dec_huffman_lookup(16#78, 16#a) -> {more, 16#86, 16#01};
+dec_huffman_lookup(16#78, 16#b) -> {ok, 16#86, 16#16};
+dec_huffman_lookup(16#78, 16#c) -> {more, 16#88, 16#01};
+dec_huffman_lookup(16#78, 16#d) -> {ok, 16#88, 16#16};
+dec_huffman_lookup(16#78, 16#e) -> {more, 16#92, 16#01};
+dec_huffman_lookup(16#78, 16#f) -> {ok, 16#92, 16#16};
+dec_huffman_lookup(16#79, 16#0) -> {more, 16#e6, 16#03};
+dec_huffman_lookup(16#79, 16#1) -> {more, 16#e6, 16#06};
+dec_huffman_lookup(16#79, 16#2) -> {more, 16#e6, 16#0a};
+dec_huffman_lookup(16#79, 16#3) -> {more, 16#e6, 16#0f};
+dec_huffman_lookup(16#79, 16#4) -> {more, 16#e6, 16#18};
+dec_huffman_lookup(16#79, 16#5) -> {more, 16#e6, 16#1f};
+dec_huffman_lookup(16#79, 16#6) -> {more, 16#e6, 16#29};
+dec_huffman_lookup(16#79, 16#7) -> {ok, 16#e6, 16#38};
+dec_huffman_lookup(16#79, 16#8) -> {more, 16#81, 16#02};
+dec_huffman_lookup(16#79, 16#9) -> {more, 16#81, 16#09};
+dec_huffman_lookup(16#79, 16#a) -> {more, 16#81, 16#17};
+dec_huffman_lookup(16#79, 16#b) -> {ok, 16#81, 16#28};
+dec_huffman_lookup(16#79, 16#c) -> {more, 16#84, 16#02};
+dec_huffman_lookup(16#79, 16#d) -> {more, 16#84, 16#09};
+dec_huffman_lookup(16#79, 16#e) -> {more, 16#84, 16#17};
+dec_huffman_lookup(16#79, 16#f) -> {ok, 16#84, 16#28};
+dec_huffman_lookup(16#7a, 16#0) -> {more, 16#81, 16#03};
+dec_huffman_lookup(16#7a, 16#1) -> {more, 16#81, 16#06};
+dec_huffman_lookup(16#7a, 16#2) -> {more, 16#81, 16#0a};
+dec_huffman_lookup(16#7a, 16#3) -> {more, 16#81, 16#0f};
+dec_huffman_lookup(16#7a, 16#4) -> {more, 16#81, 16#18};
+dec_huffman_lookup(16#7a, 16#5) -> {more, 16#81, 16#1f};
+dec_huffman_lookup(16#7a, 16#6) -> {more, 16#81, 16#29};
+dec_huffman_lookup(16#7a, 16#7) -> {ok, 16#81, 16#38};
+dec_huffman_lookup(16#7a, 16#8) -> {more, 16#84, 16#03};
+dec_huffman_lookup(16#7a, 16#9) -> {more, 16#84, 16#06};
+dec_huffman_lookup(16#7a, 16#a) -> {more, 16#84, 16#0a};
+dec_huffman_lookup(16#7a, 16#b) -> {more, 16#84, 16#0f};
+dec_huffman_lookup(16#7a, 16#c) -> {more, 16#84, 16#18};
+dec_huffman_lookup(16#7a, 16#d) -> {more, 16#84, 16#1f};
+dec_huffman_lookup(16#7a, 16#e) -> {more, 16#84, 16#29};
+dec_huffman_lookup(16#7a, 16#f) -> {ok, 16#84, 16#38};
+dec_huffman_lookup(16#7b, 16#0) -> {more, 16#85, 16#02};
+dec_huffman_lookup(16#7b, 16#1) -> {more, 16#85, 16#09};
+dec_huffman_lookup(16#7b, 16#2) -> {more, 16#85, 16#17};
+dec_huffman_lookup(16#7b, 16#3) -> {ok, 16#85, 16#28};
+dec_huffman_lookup(16#7b, 16#4) -> {more, 16#86, 16#02};
+dec_huffman_lookup(16#7b, 16#5) -> {more, 16#86, 16#09};
+dec_huffman_lookup(16#7b, 16#6) -> {more, 16#86, 16#17};
+dec_huffman_lookup(16#7b, 16#7) -> {ok, 16#86, 16#28};
+dec_huffman_lookup(16#7b, 16#8) -> {more, 16#88, 16#02};
+dec_huffman_lookup(16#7b, 16#9) -> {more, 16#88, 16#09};
+dec_huffman_lookup(16#7b, 16#a) -> {more, 16#88, 16#17};
+dec_huffman_lookup(16#7b, 16#b) -> {ok, 16#88, 16#28};
+dec_huffman_lookup(16#7b, 16#c) -> {more, 16#92, 16#02};
+dec_huffman_lookup(16#7b, 16#d) -> {more, 16#92, 16#09};
+dec_huffman_lookup(16#7b, 16#e) -> {more, 16#92, 16#17};
+dec_huffman_lookup(16#7b, 16#f) -> {ok, 16#92, 16#28};
+dec_huffman_lookup(16#7c, 16#0) -> {more, 16#85, 16#03};
+dec_huffman_lookup(16#7c, 16#1) -> {more, 16#85, 16#06};
+dec_huffman_lookup(16#7c, 16#2) -> {more, 16#85, 16#0a};
+dec_huffman_lookup(16#7c, 16#3) -> {more, 16#85, 16#0f};
+dec_huffman_lookup(16#7c, 16#4) -> {more, 16#85, 16#18};
+dec_huffman_lookup(16#7c, 16#5) -> {more, 16#85, 16#1f};
+dec_huffman_lookup(16#7c, 16#6) -> {more, 16#85, 16#29};
+dec_huffman_lookup(16#7c, 16#7) -> {ok, 16#85, 16#38};
+dec_huffman_lookup(16#7c, 16#8) -> {more, 16#86, 16#03};
+dec_huffman_lookup(16#7c, 16#9) -> {more, 16#86, 16#06};
+dec_huffman_lookup(16#7c, 16#a) -> {more, 16#86, 16#0a};
+dec_huffman_lookup(16#7c, 16#b) -> {more, 16#86, 16#0f};
+dec_huffman_lookup(16#7c, 16#c) -> {more, 16#86, 16#18};
+dec_huffman_lookup(16#7c, 16#d) -> {more, 16#86, 16#1f};
+dec_huffman_lookup(16#7c, 16#e) -> {more, 16#86, 16#29};
+dec_huffman_lookup(16#7c, 16#f) -> {ok, 16#86, 16#38};
+dec_huffman_lookup(16#7d, 16#0) -> {more, 16#88, 16#03};
+dec_huffman_lookup(16#7d, 16#1) -> {more, 16#88, 16#06};
+dec_huffman_lookup(16#7d, 16#2) -> {more, 16#88, 16#0a};
+dec_huffman_lookup(16#7d, 16#3) -> {more, 16#88, 16#0f};
+dec_huffman_lookup(16#7d, 16#4) -> {more, 16#88, 16#18};
+dec_huffman_lookup(16#7d, 16#5) -> {more, 16#88, 16#1f};
+dec_huffman_lookup(16#7d, 16#6) -> {more, 16#88, 16#29};
+dec_huffman_lookup(16#7d, 16#7) -> {ok, 16#88, 16#38};
+dec_huffman_lookup(16#7d, 16#8) -> {more, 16#92, 16#03};
+dec_huffman_lookup(16#7d, 16#9) -> {more, 16#92, 16#06};
+dec_huffman_lookup(16#7d, 16#a) -> {more, 16#92, 16#0a};
+dec_huffman_lookup(16#7d, 16#b) -> {more, 16#92, 16#0f};
+dec_huffman_lookup(16#7d, 16#c) -> {more, 16#92, 16#18};
+dec_huffman_lookup(16#7d, 16#d) -> {more, 16#92, 16#1f};
+dec_huffman_lookup(16#7d, 16#e) -> {more, 16#92, 16#29};
+dec_huffman_lookup(16#7d, 16#f) -> {ok, 16#92, 16#38};
+dec_huffman_lookup(16#7e, 16#0) -> {more, 16#9a, 16#01};
+dec_huffman_lookup(16#7e, 16#1) -> {ok, 16#9a, 16#16};
+dec_huffman_lookup(16#7e, 16#2) -> {more, 16#9c, 16#01};
+dec_huffman_lookup(16#7e, 16#3) -> {ok, 16#9c, 16#16};
+dec_huffman_lookup(16#7e, 16#4) -> {more, 16#a0, 16#01};
+dec_huffman_lookup(16#7e, 16#5) -> {ok, 16#a0, 16#16};
+dec_huffman_lookup(16#7e, 16#6) -> {more, 16#a3, 16#01};
+dec_huffman_lookup(16#7e, 16#7) -> {ok, 16#a3, 16#16};
+dec_huffman_lookup(16#7e, 16#8) -> {more, 16#a4, 16#01};
+dec_huffman_lookup(16#7e, 16#9) -> {ok, 16#a4, 16#16};
+dec_huffman_lookup(16#7e, 16#a) -> {more, 16#a9, 16#01};
+dec_huffman_lookup(16#7e, 16#b) -> {ok, 16#a9, 16#16};
+dec_huffman_lookup(16#7e, 16#c) -> {more, 16#aa, 16#01};
+dec_huffman_lookup(16#7e, 16#d) -> {ok, 16#aa, 16#16};
+dec_huffman_lookup(16#7e, 16#e) -> {more, 16#ad, 16#01};
+dec_huffman_lookup(16#7e, 16#f) -> {ok, 16#ad, 16#16};
+dec_huffman_lookup(16#7f, 16#0) -> {more, 16#9a, 16#02};
+dec_huffman_lookup(16#7f, 16#1) -> {more, 16#9a, 16#09};
+dec_huffman_lookup(16#7f, 16#2) -> {more, 16#9a, 16#17};
+dec_huffman_lookup(16#7f, 16#3) -> {ok, 16#9a, 16#28};
+dec_huffman_lookup(16#7f, 16#4) -> {more, 16#9c, 16#02};
+dec_huffman_lookup(16#7f, 16#5) -> {more, 16#9c, 16#09};
+dec_huffman_lookup(16#7f, 16#6) -> {more, 16#9c, 16#17};
+dec_huffman_lookup(16#7f, 16#7) -> {ok, 16#9c, 16#28};
+dec_huffman_lookup(16#7f, 16#8) -> {more, 16#a0, 16#02};
+dec_huffman_lookup(16#7f, 16#9) -> {more, 16#a0, 16#09};
+dec_huffman_lookup(16#7f, 16#a) -> {more, 16#a0, 16#17};
+dec_huffman_lookup(16#7f, 16#b) -> {ok, 16#a0, 16#28};
+dec_huffman_lookup(16#7f, 16#c) -> {more, 16#a3, 16#02};
+dec_huffman_lookup(16#7f, 16#d) -> {more, 16#a3, 16#09};
+dec_huffman_lookup(16#7f, 16#e) -> {more, 16#a3, 16#17};
+dec_huffman_lookup(16#7f, 16#f) -> {ok, 16#a3, 16#28};
+dec_huffman_lookup(16#80, 16#0) -> {more, 16#9a, 16#03};
+dec_huffman_lookup(16#80, 16#1) -> {more, 16#9a, 16#06};
+dec_huffman_lookup(16#80, 16#2) -> {more, 16#9a, 16#0a};
+dec_huffman_lookup(16#80, 16#3) -> {more, 16#9a, 16#0f};
+dec_huffman_lookup(16#80, 16#4) -> {more, 16#9a, 16#18};
+dec_huffman_lookup(16#80, 16#5) -> {more, 16#9a, 16#1f};
+dec_huffman_lookup(16#80, 16#6) -> {more, 16#9a, 16#29};
+dec_huffman_lookup(16#80, 16#7) -> {ok, 16#9a, 16#38};
+dec_huffman_lookup(16#80, 16#8) -> {more, 16#9c, 16#03};
+dec_huffman_lookup(16#80, 16#9) -> {more, 16#9c, 16#06};
+dec_huffman_lookup(16#80, 16#a) -> {more, 16#9c, 16#0a};
+dec_huffman_lookup(16#80, 16#b) -> {more, 16#9c, 16#0f};
+dec_huffman_lookup(16#80, 16#c) -> {more, 16#9c, 16#18};
+dec_huffman_lookup(16#80, 16#d) -> {more, 16#9c, 16#1f};
+dec_huffman_lookup(16#80, 16#e) -> {more, 16#9c, 16#29};
+dec_huffman_lookup(16#80, 16#f) -> {ok, 16#9c, 16#38};
+dec_huffman_lookup(16#81, 16#0) -> {more, 16#a0, 16#03};
+dec_huffman_lookup(16#81, 16#1) -> {more, 16#a0, 16#06};
+dec_huffman_lookup(16#81, 16#2) -> {more, 16#a0, 16#0a};
+dec_huffman_lookup(16#81, 16#3) -> {more, 16#a0, 16#0f};
+dec_huffman_lookup(16#81, 16#4) -> {more, 16#a0, 16#18};
+dec_huffman_lookup(16#81, 16#5) -> {more, 16#a0, 16#1f};
+dec_huffman_lookup(16#81, 16#6) -> {more, 16#a0, 16#29};
+dec_huffman_lookup(16#81, 16#7) -> {ok, 16#a0, 16#38};
+dec_huffman_lookup(16#81, 16#8) -> {more, 16#a3, 16#03};
+dec_huffman_lookup(16#81, 16#9) -> {more, 16#a3, 16#06};
+dec_huffman_lookup(16#81, 16#a) -> {more, 16#a3, 16#0a};
+dec_huffman_lookup(16#81, 16#b) -> {more, 16#a3, 16#0f};
+dec_huffman_lookup(16#81, 16#c) -> {more, 16#a3, 16#18};
+dec_huffman_lookup(16#81, 16#d) -> {more, 16#a3, 16#1f};
+dec_huffman_lookup(16#81, 16#e) -> {more, 16#a3, 16#29};
+dec_huffman_lookup(16#81, 16#f) -> {ok, 16#a3, 16#38};
+dec_huffman_lookup(16#82, 16#0) -> {more, 16#a4, 16#02};
+dec_huffman_lookup(16#82, 16#1) -> {more, 16#a4, 16#09};
+dec_huffman_lookup(16#82, 16#2) -> {more, 16#a4, 16#17};
+dec_huffman_lookup(16#82, 16#3) -> {ok, 16#a4, 16#28};
+dec_huffman_lookup(16#82, 16#4) -> {more, 16#a9, 16#02};
+dec_huffman_lookup(16#82, 16#5) -> {more, 16#a9, 16#09};
+dec_huffman_lookup(16#82, 16#6) -> {more, 16#a9, 16#17};
+dec_huffman_lookup(16#82, 16#7) -> {ok, 16#a9, 16#28};
+dec_huffman_lookup(16#82, 16#8) -> {more, 16#aa, 16#02};
+dec_huffman_lookup(16#82, 16#9) -> {more, 16#aa, 16#09};
+dec_huffman_lookup(16#82, 16#a) -> {more, 16#aa, 16#17};
+dec_huffman_lookup(16#82, 16#b) -> {ok, 16#aa, 16#28};
+dec_huffman_lookup(16#82, 16#c) -> {more, 16#ad, 16#02};
+dec_huffman_lookup(16#82, 16#d) -> {more, 16#ad, 16#09};
+dec_huffman_lookup(16#82, 16#e) -> {more, 16#ad, 16#17};
+dec_huffman_lookup(16#82, 16#f) -> {ok, 16#ad, 16#28};
+dec_huffman_lookup(16#83, 16#0) -> {more, 16#a4, 16#03};
+dec_huffman_lookup(16#83, 16#1) -> {more, 16#a4, 16#06};
+dec_huffman_lookup(16#83, 16#2) -> {more, 16#a4, 16#0a};
+dec_huffman_lookup(16#83, 16#3) -> {more, 16#a4, 16#0f};
+dec_huffman_lookup(16#83, 16#4) -> {more, 16#a4, 16#18};
+dec_huffman_lookup(16#83, 16#5) -> {more, 16#a4, 16#1f};
+dec_huffman_lookup(16#83, 16#6) -> {more, 16#a4, 16#29};
+dec_huffman_lookup(16#83, 16#7) -> {ok, 16#a4, 16#38};
+dec_huffman_lookup(16#83, 16#8) -> {more, 16#a9, 16#03};
+dec_huffman_lookup(16#83, 16#9) -> {more, 16#a9, 16#06};
+dec_huffman_lookup(16#83, 16#a) -> {more, 16#a9, 16#0a};
+dec_huffman_lookup(16#83, 16#b) -> {more, 16#a9, 16#0f};
+dec_huffman_lookup(16#83, 16#c) -> {more, 16#a9, 16#18};
+dec_huffman_lookup(16#83, 16#d) -> {more, 16#a9, 16#1f};
+dec_huffman_lookup(16#83, 16#e) -> {more, 16#a9, 16#29};
+dec_huffman_lookup(16#83, 16#f) -> {ok, 16#a9, 16#38};
+dec_huffman_lookup(16#84, 16#0) -> {more, 16#aa, 16#03};
+dec_huffman_lookup(16#84, 16#1) -> {more, 16#aa, 16#06};
+dec_huffman_lookup(16#84, 16#2) -> {more, 16#aa, 16#0a};
+dec_huffman_lookup(16#84, 16#3) -> {more, 16#aa, 16#0f};
+dec_huffman_lookup(16#84, 16#4) -> {more, 16#aa, 16#18};
+dec_huffman_lookup(16#84, 16#5) -> {more, 16#aa, 16#1f};
+dec_huffman_lookup(16#84, 16#6) -> {more, 16#aa, 16#29};
+dec_huffman_lookup(16#84, 16#7) -> {ok, 16#aa, 16#38};
+dec_huffman_lookup(16#84, 16#8) -> {more, 16#ad, 16#03};
+dec_huffman_lookup(16#84, 16#9) -> {more, 16#ad, 16#06};
+dec_huffman_lookup(16#84, 16#a) -> {more, 16#ad, 16#0a};
+dec_huffman_lookup(16#84, 16#b) -> {more, 16#ad, 16#0f};
+dec_huffman_lookup(16#84, 16#c) -> {more, 16#ad, 16#18};
+dec_huffman_lookup(16#84, 16#d) -> {more, 16#ad, 16#1f};
+dec_huffman_lookup(16#84, 16#e) -> {more, 16#ad, 16#29};
+dec_huffman_lookup(16#84, 16#f) -> {ok, 16#ad, 16#38};
+dec_huffman_lookup(16#85, 16#0) -> {more, undefined, 16#89};
+dec_huffman_lookup(16#85, 16#1) -> {more, undefined, 16#8a};
+dec_huffman_lookup(16#85, 16#2) -> {more, undefined, 16#8c};
+dec_huffman_lookup(16#85, 16#3) -> {more, undefined, 16#8d};
+dec_huffman_lookup(16#85, 16#4) -> {more, undefined, 16#90};
+dec_huffman_lookup(16#85, 16#5) -> {more, undefined, 16#91};
+dec_huffman_lookup(16#85, 16#6) -> {more, undefined, 16#93};
+dec_huffman_lookup(16#85, 16#7) -> {more, undefined, 16#96};
+dec_huffman_lookup(16#85, 16#8) -> {more, undefined, 16#9c};
+dec_huffman_lookup(16#85, 16#9) -> {more, undefined, 16#9f};
+dec_huffman_lookup(16#85, 16#a) -> {more, undefined, 16#a3};
+dec_huffman_lookup(16#85, 16#b) -> {more, undefined, 16#a6};
+dec_huffman_lookup(16#85, 16#c) -> {more, undefined, 16#ab};
+dec_huffman_lookup(16#85, 16#d) -> {more, undefined, 16#ae};
+dec_huffman_lookup(16#85, 16#e) -> {more, undefined, 16#b5};
+dec_huffman_lookup(16#85, 16#f) -> {ok, undefined, 16#be};
+dec_huffman_lookup(16#86, 16#0) -> {ok, 16#b2, 16#00};
+dec_huffman_lookup(16#86, 16#1) -> {ok, 16#b5, 16#00};
+dec_huffman_lookup(16#86, 16#2) -> {ok, 16#b9, 16#00};
+dec_huffman_lookup(16#86, 16#3) -> {ok, 16#ba, 16#00};
+dec_huffman_lookup(16#86, 16#4) -> {ok, 16#bb, 16#00};
+dec_huffman_lookup(16#86, 16#5) -> {ok, 16#bd, 16#00};
+dec_huffman_lookup(16#86, 16#6) -> {ok, 16#be, 16#00};
+dec_huffman_lookup(16#86, 16#7) -> {ok, 16#c4, 16#00};
+dec_huffman_lookup(16#86, 16#8) -> {ok, 16#c6, 16#00};
+dec_huffman_lookup(16#86, 16#9) -> {ok, 16#e4, 16#00};
+dec_huffman_lookup(16#86, 16#a) -> {ok, 16#e8, 16#00};
+dec_huffman_lookup(16#86, 16#b) -> {ok, 16#e9, 16#00};
+dec_huffman_lookup(16#86, 16#c) -> {more, undefined, 16#94};
+dec_huffman_lookup(16#86, 16#d) -> {more, undefined, 16#95};
+dec_huffman_lookup(16#86, 16#e) -> {more, undefined, 16#97};
+dec_huffman_lookup(16#86, 16#f) -> {more, undefined, 16#98};
+dec_huffman_lookup(16#87, 16#0) -> {more, 16#b2, 16#01};
+dec_huffman_lookup(16#87, 16#1) -> {ok, 16#b2, 16#16};
+dec_huffman_lookup(16#87, 16#2) -> {more, 16#b5, 16#01};
+dec_huffman_lookup(16#87, 16#3) -> {ok, 16#b5, 16#16};
+dec_huffman_lookup(16#87, 16#4) -> {more, 16#b9, 16#01};
+dec_huffman_lookup(16#87, 16#5) -> {ok, 16#b9, 16#16};
+dec_huffman_lookup(16#87, 16#6) -> {more, 16#ba, 16#01};
+dec_huffman_lookup(16#87, 16#7) -> {ok, 16#ba, 16#16};
+dec_huffman_lookup(16#87, 16#8) -> {more, 16#bb, 16#01};
+dec_huffman_lookup(16#87, 16#9) -> {ok, 16#bb, 16#16};
+dec_huffman_lookup(16#87, 16#a) -> {more, 16#bd, 16#01};
+dec_huffman_lookup(16#87, 16#b) -> {ok, 16#bd, 16#16};
+dec_huffman_lookup(16#87, 16#c) -> {more, 16#be, 16#01};
+dec_huffman_lookup(16#87, 16#d) -> {ok, 16#be, 16#16};
+dec_huffman_lookup(16#87, 16#e) -> {more, 16#c4, 16#01};
+dec_huffman_lookup(16#87, 16#f) -> {ok, 16#c4, 16#16};
+dec_huffman_lookup(16#88, 16#0) -> {more, 16#b2, 16#02};
+dec_huffman_lookup(16#88, 16#1) -> {more, 16#b2, 16#09};
+dec_huffman_lookup(16#88, 16#2) -> {more, 16#b2, 16#17};
+dec_huffman_lookup(16#88, 16#3) -> {ok, 16#b2, 16#28};
+dec_huffman_lookup(16#88, 16#4) -> {more, 16#b5, 16#02};
+dec_huffman_lookup(16#88, 16#5) -> {more, 16#b5, 16#09};
+dec_huffman_lookup(16#88, 16#6) -> {more, 16#b5, 16#17};
+dec_huffman_lookup(16#88, 16#7) -> {ok, 16#b5, 16#28};
+dec_huffman_lookup(16#88, 16#8) -> {more, 16#b9, 16#02};
+dec_huffman_lookup(16#88, 16#9) -> {more, 16#b9, 16#09};
+dec_huffman_lookup(16#88, 16#a) -> {more, 16#b9, 16#17};
+dec_huffman_lookup(16#88, 16#b) -> {ok, 16#b9, 16#28};
+dec_huffman_lookup(16#88, 16#c) -> {more, 16#ba, 16#02};
+dec_huffman_lookup(16#88, 16#d) -> {more, 16#ba, 16#09};
+dec_huffman_lookup(16#88, 16#e) -> {more, 16#ba, 16#17};
+dec_huffman_lookup(16#88, 16#f) -> {ok, 16#ba, 16#28};
+dec_huffman_lookup(16#89, 16#0) -> {more, 16#b2, 16#03};
+dec_huffman_lookup(16#89, 16#1) -> {more, 16#b2, 16#06};
+dec_huffman_lookup(16#89, 16#2) -> {more, 16#b2, 16#0a};
+dec_huffman_lookup(16#89, 16#3) -> {more, 16#b2, 16#0f};
+dec_huffman_lookup(16#89, 16#4) -> {more, 16#b2, 16#18};
+dec_huffman_lookup(16#89, 16#5) -> {more, 16#b2, 16#1f};
+dec_huffman_lookup(16#89, 16#6) -> {more, 16#b2, 16#29};
+dec_huffman_lookup(16#89, 16#7) -> {ok, 16#b2, 16#38};
+dec_huffman_lookup(16#89, 16#8) -> {more, 16#b5, 16#03};
+dec_huffman_lookup(16#89, 16#9) -> {more, 16#b5, 16#06};
+dec_huffman_lookup(16#89, 16#a) -> {more, 16#b5, 16#0a};
+dec_huffman_lookup(16#89, 16#b) -> {more, 16#b5, 16#0f};
+dec_huffman_lookup(16#89, 16#c) -> {more, 16#b5, 16#18};
+dec_huffman_lookup(16#89, 16#d) -> {more, 16#b5, 16#1f};
+dec_huffman_lookup(16#89, 16#e) -> {more, 16#b5, 16#29};
+dec_huffman_lookup(16#89, 16#f) -> {ok, 16#b5, 16#38};
+dec_huffman_lookup(16#8a, 16#0) -> {more, 16#b9, 16#03};
+dec_huffman_lookup(16#8a, 16#1) -> {more, 16#b9, 16#06};
+dec_huffman_lookup(16#8a, 16#2) -> {more, 16#b9, 16#0a};
+dec_huffman_lookup(16#8a, 16#3) -> {more, 16#b9, 16#0f};
+dec_huffman_lookup(16#8a, 16#4) -> {more, 16#b9, 16#18};
+dec_huffman_lookup(16#8a, 16#5) -> {more, 16#b9, 16#1f};
+dec_huffman_lookup(16#8a, 16#6) -> {more, 16#b9, 16#29};
+dec_huffman_lookup(16#8a, 16#7) -> {ok, 16#b9, 16#38};
+dec_huffman_lookup(16#8a, 16#8) -> {more, 16#ba, 16#03};
+dec_huffman_lookup(16#8a, 16#9) -> {more, 16#ba, 16#06};
+dec_huffman_lookup(16#8a, 16#a) -> {more, 16#ba, 16#0a};
+dec_huffman_lookup(16#8a, 16#b) -> {more, 16#ba, 16#0f};
+dec_huffman_lookup(16#8a, 16#c) -> {more, 16#ba, 16#18};
+dec_huffman_lookup(16#8a, 16#d) -> {more, 16#ba, 16#1f};
+dec_huffman_lookup(16#8a, 16#e) -> {more, 16#ba, 16#29};
+dec_huffman_lookup(16#8a, 16#f) -> {ok, 16#ba, 16#38};
+dec_huffman_lookup(16#8b, 16#0) -> {more, 16#bb, 16#02};
+dec_huffman_lookup(16#8b, 16#1) -> {more, 16#bb, 16#09};
+dec_huffman_lookup(16#8b, 16#2) -> {more, 16#bb, 16#17};
+dec_huffman_lookup(16#8b, 16#3) -> {ok, 16#bb, 16#28};
+dec_huffman_lookup(16#8b, 16#4) -> {more, 16#bd, 16#02};
+dec_huffman_lookup(16#8b, 16#5) -> {more, 16#bd, 16#09};
+dec_huffman_lookup(16#8b, 16#6) -> {more, 16#bd, 16#17};
+dec_huffman_lookup(16#8b, 16#7) -> {ok, 16#bd, 16#28};
+dec_huffman_lookup(16#8b, 16#8) -> {more, 16#be, 16#02};
+dec_huffman_lookup(16#8b, 16#9) -> {more, 16#be, 16#09};
+dec_huffman_lookup(16#8b, 16#a) -> {more, 16#be, 16#17};
+dec_huffman_lookup(16#8b, 16#b) -> {ok, 16#be, 16#28};
+dec_huffman_lookup(16#8b, 16#c) -> {more, 16#c4, 16#02};
+dec_huffman_lookup(16#8b, 16#d) -> {more, 16#c4, 16#09};
+dec_huffman_lookup(16#8b, 16#e) -> {more, 16#c4, 16#17};
+dec_huffman_lookup(16#8b, 16#f) -> {ok, 16#c4, 16#28};
+dec_huffman_lookup(16#8c, 16#0) -> {more, 16#bb, 16#03};
+dec_huffman_lookup(16#8c, 16#1) -> {more, 16#bb, 16#06};
+dec_huffman_lookup(16#8c, 16#2) -> {more, 16#bb, 16#0a};
+dec_huffman_lookup(16#8c, 16#3) -> {more, 16#bb, 16#0f};
+dec_huffman_lookup(16#8c, 16#4) -> {more, 16#bb, 16#18};
+dec_huffman_lookup(16#8c, 16#5) -> {more, 16#bb, 16#1f};
+dec_huffman_lookup(16#8c, 16#6) -> {more, 16#bb, 16#29};
+dec_huffman_lookup(16#8c, 16#7) -> {ok, 16#bb, 16#38};
+dec_huffman_lookup(16#8c, 16#8) -> {more, 16#bd, 16#03};
+dec_huffman_lookup(16#8c, 16#9) -> {more, 16#bd, 16#06};
+dec_huffman_lookup(16#8c, 16#a) -> {more, 16#bd, 16#0a};
+dec_huffman_lookup(16#8c, 16#b) -> {more, 16#bd, 16#0f};
+dec_huffman_lookup(16#8c, 16#c) -> {more, 16#bd, 16#18};
+dec_huffman_lookup(16#8c, 16#d) -> {more, 16#bd, 16#1f};
+dec_huffman_lookup(16#8c, 16#e) -> {more, 16#bd, 16#29};
+dec_huffman_lookup(16#8c, 16#f) -> {ok, 16#bd, 16#38};
+dec_huffman_lookup(16#8d, 16#0) -> {more, 16#be, 16#03};
+dec_huffman_lookup(16#8d, 16#1) -> {more, 16#be, 16#06};
+dec_huffman_lookup(16#8d, 16#2) -> {more, 16#be, 16#0a};
+dec_huffman_lookup(16#8d, 16#3) -> {more, 16#be, 16#0f};
+dec_huffman_lookup(16#8d, 16#4) -> {more, 16#be, 16#18};
+dec_huffman_lookup(16#8d, 16#5) -> {more, 16#be, 16#1f};
+dec_huffman_lookup(16#8d, 16#6) -> {more, 16#be, 16#29};
+dec_huffman_lookup(16#8d, 16#7) -> {ok, 16#be, 16#38};
+dec_huffman_lookup(16#8d, 16#8) -> {more, 16#c4, 16#03};
+dec_huffman_lookup(16#8d, 16#9) -> {more, 16#c4, 16#06};
+dec_huffman_lookup(16#8d, 16#a) -> {more, 16#c4, 16#0a};
+dec_huffman_lookup(16#8d, 16#b) -> {more, 16#c4, 16#0f};
+dec_huffman_lookup(16#8d, 16#c) -> {more, 16#c4, 16#18};
+dec_huffman_lookup(16#8d, 16#d) -> {more, 16#c4, 16#1f};
+dec_huffman_lookup(16#8d, 16#e) -> {more, 16#c4, 16#29};
+dec_huffman_lookup(16#8d, 16#f) -> {ok, 16#c4, 16#38};
+dec_huffman_lookup(16#8e, 16#0) -> {more, 16#c6, 16#01};
+dec_huffman_lookup(16#8e, 16#1) -> {ok, 16#c6, 16#16};
+dec_huffman_lookup(16#8e, 16#2) -> {more, 16#e4, 16#01};
+dec_huffman_lookup(16#8e, 16#3) -> {ok, 16#e4, 16#16};
+dec_huffman_lookup(16#8e, 16#4) -> {more, 16#e8, 16#01};
+dec_huffman_lookup(16#8e, 16#5) -> {ok, 16#e8, 16#16};
+dec_huffman_lookup(16#8e, 16#6) -> {more, 16#e9, 16#01};
+dec_huffman_lookup(16#8e, 16#7) -> {ok, 16#e9, 16#16};
+dec_huffman_lookup(16#8e, 16#8) -> {ok, 16#01, 16#00};
+dec_huffman_lookup(16#8e, 16#9) -> {ok, 16#87, 16#00};
+dec_huffman_lookup(16#8e, 16#a) -> {ok, 16#89, 16#00};
+dec_huffman_lookup(16#8e, 16#b) -> {ok, 16#8a, 16#00};
+dec_huffman_lookup(16#8e, 16#c) -> {ok, 16#8b, 16#00};
+dec_huffman_lookup(16#8e, 16#d) -> {ok, 16#8c, 16#00};
+dec_huffman_lookup(16#8e, 16#e) -> {ok, 16#8d, 16#00};
+dec_huffman_lookup(16#8e, 16#f) -> {ok, 16#8f, 16#00};
+dec_huffman_lookup(16#8f, 16#0) -> {more, 16#c6, 16#02};
+dec_huffman_lookup(16#8f, 16#1) -> {more, 16#c6, 16#09};
+dec_huffman_lookup(16#8f, 16#2) -> {more, 16#c6, 16#17};
+dec_huffman_lookup(16#8f, 16#3) -> {ok, 16#c6, 16#28};
+dec_huffman_lookup(16#8f, 16#4) -> {more, 16#e4, 16#02};
+dec_huffman_lookup(16#8f, 16#5) -> {more, 16#e4, 16#09};
+dec_huffman_lookup(16#8f, 16#6) -> {more, 16#e4, 16#17};
+dec_huffman_lookup(16#8f, 16#7) -> {ok, 16#e4, 16#28};
+dec_huffman_lookup(16#8f, 16#8) -> {more, 16#e8, 16#02};
+dec_huffman_lookup(16#8f, 16#9) -> {more, 16#e8, 16#09};
+dec_huffman_lookup(16#8f, 16#a) -> {more, 16#e8, 16#17};
+dec_huffman_lookup(16#8f, 16#b) -> {ok, 16#e8, 16#28};
+dec_huffman_lookup(16#8f, 16#c) -> {more, 16#e9, 16#02};
+dec_huffman_lookup(16#8f, 16#d) -> {more, 16#e9, 16#09};
+dec_huffman_lookup(16#8f, 16#e) -> {more, 16#e9, 16#17};
+dec_huffman_lookup(16#8f, 16#f) -> {ok, 16#e9, 16#28};
+dec_huffman_lookup(16#90, 16#0) -> {more, 16#c6, 16#03};
+dec_huffman_lookup(16#90, 16#1) -> {more, 16#c6, 16#06};
+dec_huffman_lookup(16#90, 16#2) -> {more, 16#c6, 16#0a};
+dec_huffman_lookup(16#90, 16#3) -> {more, 16#c6, 16#0f};
+dec_huffman_lookup(16#90, 16#4) -> {more, 16#c6, 16#18};
+dec_huffman_lookup(16#90, 16#5) -> {more, 16#c6, 16#1f};
+dec_huffman_lookup(16#90, 16#6) -> {more, 16#c6, 16#29};
+dec_huffman_lookup(16#90, 16#7) -> {ok, 16#c6, 16#38};
+dec_huffman_lookup(16#90, 16#8) -> {more, 16#e4, 16#03};
+dec_huffman_lookup(16#90, 16#9) -> {more, 16#e4, 16#06};
+dec_huffman_lookup(16#90, 16#a) -> {more, 16#e4, 16#0a};
+dec_huffman_lookup(16#90, 16#b) -> {more, 16#e4, 16#0f};
+dec_huffman_lookup(16#90, 16#c) -> {more, 16#e4, 16#18};
+dec_huffman_lookup(16#90, 16#d) -> {more, 16#e4, 16#1f};
+dec_huffman_lookup(16#90, 16#e) -> {more, 16#e4, 16#29};
+dec_huffman_lookup(16#90, 16#f) -> {ok, 16#e4, 16#38};
+dec_huffman_lookup(16#91, 16#0) -> {more, 16#e8, 16#03};
+dec_huffman_lookup(16#91, 16#1) -> {more, 16#e8, 16#06};
+dec_huffman_lookup(16#91, 16#2) -> {more, 16#e8, 16#0a};
+dec_huffman_lookup(16#91, 16#3) -> {more, 16#e8, 16#0f};
+dec_huffman_lookup(16#91, 16#4) -> {more, 16#e8, 16#18};
+dec_huffman_lookup(16#91, 16#5) -> {more, 16#e8, 16#1f};
+dec_huffman_lookup(16#91, 16#6) -> {more, 16#e8, 16#29};
+dec_huffman_lookup(16#91, 16#7) -> {ok, 16#e8, 16#38};
+dec_huffman_lookup(16#91, 16#8) -> {more, 16#e9, 16#03};
+dec_huffman_lookup(16#91, 16#9) -> {more, 16#e9, 16#06};
+dec_huffman_lookup(16#91, 16#a) -> {more, 16#e9, 16#0a};
+dec_huffman_lookup(16#91, 16#b) -> {more, 16#e9, 16#0f};
+dec_huffman_lookup(16#91, 16#c) -> {more, 16#e9, 16#18};
+dec_huffman_lookup(16#91, 16#d) -> {more, 16#e9, 16#1f};
+dec_huffman_lookup(16#91, 16#e) -> {more, 16#e9, 16#29};
+dec_huffman_lookup(16#91, 16#f) -> {ok, 16#e9, 16#38};
+dec_huffman_lookup(16#92, 16#0) -> {more, 16#01, 16#01};
+dec_huffman_lookup(16#92, 16#1) -> {ok, 16#01, 16#16};
+dec_huffman_lookup(16#92, 16#2) -> {more, 16#87, 16#01};
+dec_huffman_lookup(16#92, 16#3) -> {ok, 16#87, 16#16};
+dec_huffman_lookup(16#92, 16#4) -> {more, 16#89, 16#01};
+dec_huffman_lookup(16#92, 16#5) -> {ok, 16#89, 16#16};
+dec_huffman_lookup(16#92, 16#6) -> {more, 16#8a, 16#01};
+dec_huffman_lookup(16#92, 16#7) -> {ok, 16#8a, 16#16};
+dec_huffman_lookup(16#92, 16#8) -> {more, 16#8b, 16#01};
+dec_huffman_lookup(16#92, 16#9) -> {ok, 16#8b, 16#16};
+dec_huffman_lookup(16#92, 16#a) -> {more, 16#8c, 16#01};
+dec_huffman_lookup(16#92, 16#b) -> {ok, 16#8c, 16#16};
+dec_huffman_lookup(16#92, 16#c) -> {more, 16#8d, 16#01};
+dec_huffman_lookup(16#92, 16#d) -> {ok, 16#8d, 16#16};
+dec_huffman_lookup(16#92, 16#e) -> {more, 16#8f, 16#01};
+dec_huffman_lookup(16#92, 16#f) -> {ok, 16#8f, 16#16};
+dec_huffman_lookup(16#93, 16#0) -> {more, 16#01, 16#02};
+dec_huffman_lookup(16#93, 16#1) -> {more, 16#01, 16#09};
+dec_huffman_lookup(16#93, 16#2) -> {more, 16#01, 16#17};
+dec_huffman_lookup(16#93, 16#3) -> {ok, 16#01, 16#28};
+dec_huffman_lookup(16#93, 16#4) -> {more, 16#87, 16#02};
+dec_huffman_lookup(16#93, 16#5) -> {more, 16#87, 16#09};
+dec_huffman_lookup(16#93, 16#6) -> {more, 16#87, 16#17};
+dec_huffman_lookup(16#93, 16#7) -> {ok, 16#87, 16#28};
+dec_huffman_lookup(16#93, 16#8) -> {more, 16#89, 16#02};
+dec_huffman_lookup(16#93, 16#9) -> {more, 16#89, 16#09};
+dec_huffman_lookup(16#93, 16#a) -> {more, 16#89, 16#17};
+dec_huffman_lookup(16#93, 16#b) -> {ok, 16#89, 16#28};
+dec_huffman_lookup(16#93, 16#c) -> {more, 16#8a, 16#02};
+dec_huffman_lookup(16#93, 16#d) -> {more, 16#8a, 16#09};
+dec_huffman_lookup(16#93, 16#e) -> {more, 16#8a, 16#17};
+dec_huffman_lookup(16#93, 16#f) -> {ok, 16#8a, 16#28};
+dec_huffman_lookup(16#94, 16#0) -> {more, 16#01, 16#03};
+dec_huffman_lookup(16#94, 16#1) -> {more, 16#01, 16#06};
+dec_huffman_lookup(16#94, 16#2) -> {more, 16#01, 16#0a};
+dec_huffman_lookup(16#94, 16#3) -> {more, 16#01, 16#0f};
+dec_huffman_lookup(16#94, 16#4) -> {more, 16#01, 16#18};
+dec_huffman_lookup(16#94, 16#5) -> {more, 16#01, 16#1f};
+dec_huffman_lookup(16#94, 16#6) -> {more, 16#01, 16#29};
+dec_huffman_lookup(16#94, 16#7) -> {ok, 16#01, 16#38};
+dec_huffman_lookup(16#94, 16#8) -> {more, 16#87, 16#03};
+dec_huffman_lookup(16#94, 16#9) -> {more, 16#87, 16#06};
+dec_huffman_lookup(16#94, 16#a) -> {more, 16#87, 16#0a};
+dec_huffman_lookup(16#94, 16#b) -> {more, 16#87, 16#0f};
+dec_huffman_lookup(16#94, 16#c) -> {more, 16#87, 16#18};
+dec_huffman_lookup(16#94, 16#d) -> {more, 16#87, 16#1f};
+dec_huffman_lookup(16#94, 16#e) -> {more, 16#87, 16#29};
+dec_huffman_lookup(16#94, 16#f) -> {ok, 16#87, 16#38};
+dec_huffman_lookup(16#95, 16#0) -> {more, 16#89, 16#03};
+dec_huffman_lookup(16#95, 16#1) -> {more, 16#89, 16#06};
+dec_huffman_lookup(16#95, 16#2) -> {more, 16#89, 16#0a};
+dec_huffman_lookup(16#95, 16#3) -> {more, 16#89, 16#0f};
+dec_huffman_lookup(16#95, 16#4) -> {more, 16#89, 16#18};
+dec_huffman_lookup(16#95, 16#5) -> {more, 16#89, 16#1f};
+dec_huffman_lookup(16#95, 16#6) -> {more, 16#89, 16#29};
+dec_huffman_lookup(16#95, 16#7) -> {ok, 16#89, 16#38};
+dec_huffman_lookup(16#95, 16#8) -> {more, 16#8a, 16#03};
+dec_huffman_lookup(16#95, 16#9) -> {more, 16#8a, 16#06};
+dec_huffman_lookup(16#95, 16#a) -> {more, 16#8a, 16#0a};
+dec_huffman_lookup(16#95, 16#b) -> {more, 16#8a, 16#0f};
+dec_huffman_lookup(16#95, 16#c) -> {more, 16#8a, 16#18};
+dec_huffman_lookup(16#95, 16#d) -> {more, 16#8a, 16#1f};
+dec_huffman_lookup(16#95, 16#e) -> {more, 16#8a, 16#29};
+dec_huffman_lookup(16#95, 16#f) -> {ok, 16#8a, 16#38};
+dec_huffman_lookup(16#96, 16#0) -> {more, 16#8b, 16#02};
+dec_huffman_lookup(16#96, 16#1) -> {more, 16#8b, 16#09};
+dec_huffman_lookup(16#96, 16#2) -> {more, 16#8b, 16#17};
+dec_huffman_lookup(16#96, 16#3) -> {ok, 16#8b, 16#28};
+dec_huffman_lookup(16#96, 16#4) -> {more, 16#8c, 16#02};
+dec_huffman_lookup(16#96, 16#5) -> {more, 16#8c, 16#09};
+dec_huffman_lookup(16#96, 16#6) -> {more, 16#8c, 16#17};
+dec_huffman_lookup(16#96, 16#7) -> {ok, 16#8c, 16#28};
+dec_huffman_lookup(16#96, 16#8) -> {more, 16#8d, 16#02};
+dec_huffman_lookup(16#96, 16#9) -> {more, 16#8d, 16#09};
+dec_huffman_lookup(16#96, 16#a) -> {more, 16#8d, 16#17};
+dec_huffman_lookup(16#96, 16#b) -> {ok, 16#8d, 16#28};
+dec_huffman_lookup(16#96, 16#c) -> {more, 16#8f, 16#02};
+dec_huffman_lookup(16#96, 16#d) -> {more, 16#8f, 16#09};
+dec_huffman_lookup(16#96, 16#e) -> {more, 16#8f, 16#17};
+dec_huffman_lookup(16#96, 16#f) -> {ok, 16#8f, 16#28};
+dec_huffman_lookup(16#97, 16#0) -> {more, 16#8b, 16#03};
+dec_huffman_lookup(16#97, 16#1) -> {more, 16#8b, 16#06};
+dec_huffman_lookup(16#97, 16#2) -> {more, 16#8b, 16#0a};
+dec_huffman_lookup(16#97, 16#3) -> {more, 16#8b, 16#0f};
+dec_huffman_lookup(16#97, 16#4) -> {more, 16#8b, 16#18};
+dec_huffman_lookup(16#97, 16#5) -> {more, 16#8b, 16#1f};
+dec_huffman_lookup(16#97, 16#6) -> {more, 16#8b, 16#29};
+dec_huffman_lookup(16#97, 16#7) -> {ok, 16#8b, 16#38};
+dec_huffman_lookup(16#97, 16#8) -> {more, 16#8c, 16#03};
+dec_huffman_lookup(16#97, 16#9) -> {more, 16#8c, 16#06};
+dec_huffman_lookup(16#97, 16#a) -> {more, 16#8c, 16#0a};
+dec_huffman_lookup(16#97, 16#b) -> {more, 16#8c, 16#0f};
+dec_huffman_lookup(16#97, 16#c) -> {more, 16#8c, 16#18};
+dec_huffman_lookup(16#97, 16#d) -> {more, 16#8c, 16#1f};
+dec_huffman_lookup(16#97, 16#e) -> {more, 16#8c, 16#29};
+dec_huffman_lookup(16#97, 16#f) -> {ok, 16#8c, 16#38};
+dec_huffman_lookup(16#98, 16#0) -> {more, 16#8d, 16#03};
+dec_huffman_lookup(16#98, 16#1) -> {more, 16#8d, 16#06};
+dec_huffman_lookup(16#98, 16#2) -> {more, 16#8d, 16#0a};
+dec_huffman_lookup(16#98, 16#3) -> {more, 16#8d, 16#0f};
+dec_huffman_lookup(16#98, 16#4) -> {more, 16#8d, 16#18};
+dec_huffman_lookup(16#98, 16#5) -> {more, 16#8d, 16#1f};
+dec_huffman_lookup(16#98, 16#6) -> {more, 16#8d, 16#29};
+dec_huffman_lookup(16#98, 16#7) -> {ok, 16#8d, 16#38};
+dec_huffman_lookup(16#98, 16#8) -> {more, 16#8f, 16#03};
+dec_huffman_lookup(16#98, 16#9) -> {more, 16#8f, 16#06};
+dec_huffman_lookup(16#98, 16#a) -> {more, 16#8f, 16#0a};
+dec_huffman_lookup(16#98, 16#b) -> {more, 16#8f, 16#0f};
+dec_huffman_lookup(16#98, 16#c) -> {more, 16#8f, 16#18};
+dec_huffman_lookup(16#98, 16#d) -> {more, 16#8f, 16#1f};
+dec_huffman_lookup(16#98, 16#e) -> {more, 16#8f, 16#29};
+dec_huffman_lookup(16#98, 16#f) -> {ok, 16#8f, 16#38};
+dec_huffman_lookup(16#99, 16#0) -> {more, undefined, 16#9d};
+dec_huffman_lookup(16#99, 16#1) -> {more, undefined, 16#9e};
+dec_huffman_lookup(16#99, 16#2) -> {more, undefined, 16#a0};
+dec_huffman_lookup(16#99, 16#3) -> {more, undefined, 16#a1};
+dec_huffman_lookup(16#99, 16#4) -> {more, undefined, 16#a4};
+dec_huffman_lookup(16#99, 16#5) -> {more, undefined, 16#a5};
+dec_huffman_lookup(16#99, 16#6) -> {more, undefined, 16#a7};
+dec_huffman_lookup(16#99, 16#7) -> {more, undefined, 16#a8};
+dec_huffman_lookup(16#99, 16#8) -> {more, undefined, 16#ac};
+dec_huffman_lookup(16#99, 16#9) -> {more, undefined, 16#ad};
+dec_huffman_lookup(16#99, 16#a) -> {more, undefined, 16#af};
+dec_huffman_lookup(16#99, 16#b) -> {more, undefined, 16#b1};
+dec_huffman_lookup(16#99, 16#c) -> {more, undefined, 16#b6};
+dec_huffman_lookup(16#99, 16#d) -> {more, undefined, 16#b9};
+dec_huffman_lookup(16#99, 16#e) -> {more, undefined, 16#bf};
+dec_huffman_lookup(16#99, 16#f) -> {ok, undefined, 16#cf};
+dec_huffman_lookup(16#9a, 16#0) -> {ok, 16#93, 16#00};
+dec_huffman_lookup(16#9a, 16#1) -> {ok, 16#95, 16#00};
+dec_huffman_lookup(16#9a, 16#2) -> {ok, 16#96, 16#00};
+dec_huffman_lookup(16#9a, 16#3) -> {ok, 16#97, 16#00};
+dec_huffman_lookup(16#9a, 16#4) -> {ok, 16#98, 16#00};
+dec_huffman_lookup(16#9a, 16#5) -> {ok, 16#9b, 16#00};
+dec_huffman_lookup(16#9a, 16#6) -> {ok, 16#9d, 16#00};
+dec_huffman_lookup(16#9a, 16#7) -> {ok, 16#9e, 16#00};
+dec_huffman_lookup(16#9a, 16#8) -> {ok, 16#a5, 16#00};
+dec_huffman_lookup(16#9a, 16#9) -> {ok, 16#a6, 16#00};
+dec_huffman_lookup(16#9a, 16#a) -> {ok, 16#a8, 16#00};
+dec_huffman_lookup(16#9a, 16#b) -> {ok, 16#ae, 16#00};
+dec_huffman_lookup(16#9a, 16#c) -> {ok, 16#af, 16#00};
+dec_huffman_lookup(16#9a, 16#d) -> {ok, 16#b4, 16#00};
+dec_huffman_lookup(16#9a, 16#e) -> {ok, 16#b6, 16#00};
+dec_huffman_lookup(16#9a, 16#f) -> {ok, 16#b7, 16#00};
+dec_huffman_lookup(16#9b, 16#0) -> {more, 16#93, 16#01};
+dec_huffman_lookup(16#9b, 16#1) -> {ok, 16#93, 16#16};
+dec_huffman_lookup(16#9b, 16#2) -> {more, 16#95, 16#01};
+dec_huffman_lookup(16#9b, 16#3) -> {ok, 16#95, 16#16};
+dec_huffman_lookup(16#9b, 16#4) -> {more, 16#96, 16#01};
+dec_huffman_lookup(16#9b, 16#5) -> {ok, 16#96, 16#16};
+dec_huffman_lookup(16#9b, 16#6) -> {more, 16#97, 16#01};
+dec_huffman_lookup(16#9b, 16#7) -> {ok, 16#97, 16#16};
+dec_huffman_lookup(16#9b, 16#8) -> {more, 16#98, 16#01};
+dec_huffman_lookup(16#9b, 16#9) -> {ok, 16#98, 16#16};
+dec_huffman_lookup(16#9b, 16#a) -> {more, 16#9b, 16#01};
+dec_huffman_lookup(16#9b, 16#b) -> {ok, 16#9b, 16#16};
+dec_huffman_lookup(16#9b, 16#c) -> {more, 16#9d, 16#01};
+dec_huffman_lookup(16#9b, 16#d) -> {ok, 16#9d, 16#16};
+dec_huffman_lookup(16#9b, 16#e) -> {more, 16#9e, 16#01};
+dec_huffman_lookup(16#9b, 16#f) -> {ok, 16#9e, 16#16};
+dec_huffman_lookup(16#9c, 16#0) -> {more, 16#93, 16#02};
+dec_huffman_lookup(16#9c, 16#1) -> {more, 16#93, 16#09};
+dec_huffman_lookup(16#9c, 16#2) -> {more, 16#93, 16#17};
+dec_huffman_lookup(16#9c, 16#3) -> {ok, 16#93, 16#28};
+dec_huffman_lookup(16#9c, 16#4) -> {more, 16#95, 16#02};
+dec_huffman_lookup(16#9c, 16#5) -> {more, 16#95, 16#09};
+dec_huffman_lookup(16#9c, 16#6) -> {more, 16#95, 16#17};
+dec_huffman_lookup(16#9c, 16#7) -> {ok, 16#95, 16#28};
+dec_huffman_lookup(16#9c, 16#8) -> {more, 16#96, 16#02};
+dec_huffman_lookup(16#9c, 16#9) -> {more, 16#96, 16#09};
+dec_huffman_lookup(16#9c, 16#a) -> {more, 16#96, 16#17};
+dec_huffman_lookup(16#9c, 16#b) -> {ok, 16#96, 16#28};
+dec_huffman_lookup(16#9c, 16#c) -> {more, 16#97, 16#02};
+dec_huffman_lookup(16#9c, 16#d) -> {more, 16#97, 16#09};
+dec_huffman_lookup(16#9c, 16#e) -> {more, 16#97, 16#17};
+dec_huffman_lookup(16#9c, 16#f) -> {ok, 16#97, 16#28};
+dec_huffman_lookup(16#9d, 16#0) -> {more, 16#93, 16#03};
+dec_huffman_lookup(16#9d, 16#1) -> {more, 16#93, 16#06};
+dec_huffman_lookup(16#9d, 16#2) -> {more, 16#93, 16#0a};
+dec_huffman_lookup(16#9d, 16#3) -> {more, 16#93, 16#0f};
+dec_huffman_lookup(16#9d, 16#4) -> {more, 16#93, 16#18};
+dec_huffman_lookup(16#9d, 16#5) -> {more, 16#93, 16#1f};
+dec_huffman_lookup(16#9d, 16#6) -> {more, 16#93, 16#29};
+dec_huffman_lookup(16#9d, 16#7) -> {ok, 16#93, 16#38};
+dec_huffman_lookup(16#9d, 16#8) -> {more, 16#95, 16#03};
+dec_huffman_lookup(16#9d, 16#9) -> {more, 16#95, 16#06};
+dec_huffman_lookup(16#9d, 16#a) -> {more, 16#95, 16#0a};
+dec_huffman_lookup(16#9d, 16#b) -> {more, 16#95, 16#0f};
+dec_huffman_lookup(16#9d, 16#c) -> {more, 16#95, 16#18};
+dec_huffman_lookup(16#9d, 16#d) -> {more, 16#95, 16#1f};
+dec_huffman_lookup(16#9d, 16#e) -> {more, 16#95, 16#29};
+dec_huffman_lookup(16#9d, 16#f) -> {ok, 16#95, 16#38};
+dec_huffman_lookup(16#9e, 16#0) -> {more, 16#96, 16#03};
+dec_huffman_lookup(16#9e, 16#1) -> {more, 16#96, 16#06};
+dec_huffman_lookup(16#9e, 16#2) -> {more, 16#96, 16#0a};
+dec_huffman_lookup(16#9e, 16#3) -> {more, 16#96, 16#0f};
+dec_huffman_lookup(16#9e, 16#4) -> {more, 16#96, 16#18};
+dec_huffman_lookup(16#9e, 16#5) -> {more, 16#96, 16#1f};
+dec_huffman_lookup(16#9e, 16#6) -> {more, 16#96, 16#29};
+dec_huffman_lookup(16#9e, 16#7) -> {ok, 16#96, 16#38};
+dec_huffman_lookup(16#9e, 16#8) -> {more, 16#97, 16#03};
+dec_huffman_lookup(16#9e, 16#9) -> {more, 16#97, 16#06};
+dec_huffman_lookup(16#9e, 16#a) -> {more, 16#97, 16#0a};
+dec_huffman_lookup(16#9e, 16#b) -> {more, 16#97, 16#0f};
+dec_huffman_lookup(16#9e, 16#c) -> {more, 16#97, 16#18};
+dec_huffman_lookup(16#9e, 16#d) -> {more, 16#97, 16#1f};
+dec_huffman_lookup(16#9e, 16#e) -> {more, 16#97, 16#29};
+dec_huffman_lookup(16#9e, 16#f) -> {ok, 16#97, 16#38};
+dec_huffman_lookup(16#9f, 16#0) -> {more, 16#98, 16#02};
+dec_huffman_lookup(16#9f, 16#1) -> {more, 16#98, 16#09};
+dec_huffman_lookup(16#9f, 16#2) -> {more, 16#98, 16#17};
+dec_huffman_lookup(16#9f, 16#3) -> {ok, 16#98, 16#28};
+dec_huffman_lookup(16#9f, 16#4) -> {more, 16#9b, 16#02};
+dec_huffman_lookup(16#9f, 16#5) -> {more, 16#9b, 16#09};
+dec_huffman_lookup(16#9f, 16#6) -> {more, 16#9b, 16#17};
+dec_huffman_lookup(16#9f, 16#7) -> {ok, 16#9b, 16#28};
+dec_huffman_lookup(16#9f, 16#8) -> {more, 16#9d, 16#02};
+dec_huffman_lookup(16#9f, 16#9) -> {more, 16#9d, 16#09};
+dec_huffman_lookup(16#9f, 16#a) -> {more, 16#9d, 16#17};
+dec_huffman_lookup(16#9f, 16#b) -> {ok, 16#9d, 16#28};
+dec_huffman_lookup(16#9f, 16#c) -> {more, 16#9e, 16#02};
+dec_huffman_lookup(16#9f, 16#d) -> {more, 16#9e, 16#09};
+dec_huffman_lookup(16#9f, 16#e) -> {more, 16#9e, 16#17};
+dec_huffman_lookup(16#9f, 16#f) -> {ok, 16#9e, 16#28};
+dec_huffman_lookup(16#a0, 16#0) -> {more, 16#98, 16#03};
+dec_huffman_lookup(16#a0, 16#1) -> {more, 16#98, 16#06};
+dec_huffman_lookup(16#a0, 16#2) -> {more, 16#98, 16#0a};
+dec_huffman_lookup(16#a0, 16#3) -> {more, 16#98, 16#0f};
+dec_huffman_lookup(16#a0, 16#4) -> {more, 16#98, 16#18};
+dec_huffman_lookup(16#a0, 16#5) -> {more, 16#98, 16#1f};
+dec_huffman_lookup(16#a0, 16#6) -> {more, 16#98, 16#29};
+dec_huffman_lookup(16#a0, 16#7) -> {ok, 16#98, 16#38};
+dec_huffman_lookup(16#a0, 16#8) -> {more, 16#9b, 16#03};
+dec_huffman_lookup(16#a0, 16#9) -> {more, 16#9b, 16#06};
+dec_huffman_lookup(16#a0, 16#a) -> {more, 16#9b, 16#0a};
+dec_huffman_lookup(16#a0, 16#b) -> {more, 16#9b, 16#0f};
+dec_huffman_lookup(16#a0, 16#c) -> {more, 16#9b, 16#18};
+dec_huffman_lookup(16#a0, 16#d) -> {more, 16#9b, 16#1f};
+dec_huffman_lookup(16#a0, 16#e) -> {more, 16#9b, 16#29};
+dec_huffman_lookup(16#a0, 16#f) -> {ok, 16#9b, 16#38};
+dec_huffman_lookup(16#a1, 16#0) -> {more, 16#9d, 16#03};
+dec_huffman_lookup(16#a1, 16#1) -> {more, 16#9d, 16#06};
+dec_huffman_lookup(16#a1, 16#2) -> {more, 16#9d, 16#0a};
+dec_huffman_lookup(16#a1, 16#3) -> {more, 16#9d, 16#0f};
+dec_huffman_lookup(16#a1, 16#4) -> {more, 16#9d, 16#18};
+dec_huffman_lookup(16#a1, 16#5) -> {more, 16#9d, 16#1f};
+dec_huffman_lookup(16#a1, 16#6) -> {more, 16#9d, 16#29};
+dec_huffman_lookup(16#a1, 16#7) -> {ok, 16#9d, 16#38};
+dec_huffman_lookup(16#a1, 16#8) -> {more, 16#9e, 16#03};
+dec_huffman_lookup(16#a1, 16#9) -> {more, 16#9e, 16#06};
+dec_huffman_lookup(16#a1, 16#a) -> {more, 16#9e, 16#0a};
+dec_huffman_lookup(16#a1, 16#b) -> {more, 16#9e, 16#0f};
+dec_huffman_lookup(16#a1, 16#c) -> {more, 16#9e, 16#18};
+dec_huffman_lookup(16#a1, 16#d) -> {more, 16#9e, 16#1f};
+dec_huffman_lookup(16#a1, 16#e) -> {more, 16#9e, 16#29};
+dec_huffman_lookup(16#a1, 16#f) -> {ok, 16#9e, 16#38};
+dec_huffman_lookup(16#a2, 16#0) -> {more, 16#a5, 16#01};
+dec_huffman_lookup(16#a2, 16#1) -> {ok, 16#a5, 16#16};
+dec_huffman_lookup(16#a2, 16#2) -> {more, 16#a6, 16#01};
+dec_huffman_lookup(16#a2, 16#3) -> {ok, 16#a6, 16#16};
+dec_huffman_lookup(16#a2, 16#4) -> {more, 16#a8, 16#01};
+dec_huffman_lookup(16#a2, 16#5) -> {ok, 16#a8, 16#16};
+dec_huffman_lookup(16#a2, 16#6) -> {more, 16#ae, 16#01};
+dec_huffman_lookup(16#a2, 16#7) -> {ok, 16#ae, 16#16};
+dec_huffman_lookup(16#a2, 16#8) -> {more, 16#af, 16#01};
+dec_huffman_lookup(16#a2, 16#9) -> {ok, 16#af, 16#16};
+dec_huffman_lookup(16#a2, 16#a) -> {more, 16#b4, 16#01};
+dec_huffman_lookup(16#a2, 16#b) -> {ok, 16#b4, 16#16};
+dec_huffman_lookup(16#a2, 16#c) -> {more, 16#b6, 16#01};
+dec_huffman_lookup(16#a2, 16#d) -> {ok, 16#b6, 16#16};
+dec_huffman_lookup(16#a2, 16#e) -> {more, 16#b7, 16#01};
+dec_huffman_lookup(16#a2, 16#f) -> {ok, 16#b7, 16#16};
+dec_huffman_lookup(16#a3, 16#0) -> {more, 16#a5, 16#02};
+dec_huffman_lookup(16#a3, 16#1) -> {more, 16#a5, 16#09};
+dec_huffman_lookup(16#a3, 16#2) -> {more, 16#a5, 16#17};
+dec_huffman_lookup(16#a3, 16#3) -> {ok, 16#a5, 16#28};
+dec_huffman_lookup(16#a3, 16#4) -> {more, 16#a6, 16#02};
+dec_huffman_lookup(16#a3, 16#5) -> {more, 16#a6, 16#09};
+dec_huffman_lookup(16#a3, 16#6) -> {more, 16#a6, 16#17};
+dec_huffman_lookup(16#a3, 16#7) -> {ok, 16#a6, 16#28};
+dec_huffman_lookup(16#a3, 16#8) -> {more, 16#a8, 16#02};
+dec_huffman_lookup(16#a3, 16#9) -> {more, 16#a8, 16#09};
+dec_huffman_lookup(16#a3, 16#a) -> {more, 16#a8, 16#17};
+dec_huffman_lookup(16#a3, 16#b) -> {ok, 16#a8, 16#28};
+dec_huffman_lookup(16#a3, 16#c) -> {more, 16#ae, 16#02};
+dec_huffman_lookup(16#a3, 16#d) -> {more, 16#ae, 16#09};
+dec_huffman_lookup(16#a3, 16#e) -> {more, 16#ae, 16#17};
+dec_huffman_lookup(16#a3, 16#f) -> {ok, 16#ae, 16#28};
+dec_huffman_lookup(16#a4, 16#0) -> {more, 16#a5, 16#03};
+dec_huffman_lookup(16#a4, 16#1) -> {more, 16#a5, 16#06};
+dec_huffman_lookup(16#a4, 16#2) -> {more, 16#a5, 16#0a};
+dec_huffman_lookup(16#a4, 16#3) -> {more, 16#a5, 16#0f};
+dec_huffman_lookup(16#a4, 16#4) -> {more, 16#a5, 16#18};
+dec_huffman_lookup(16#a4, 16#5) -> {more, 16#a5, 16#1f};
+dec_huffman_lookup(16#a4, 16#6) -> {more, 16#a5, 16#29};
+dec_huffman_lookup(16#a4, 16#7) -> {ok, 16#a5, 16#38};
+dec_huffman_lookup(16#a4, 16#8) -> {more, 16#a6, 16#03};
+dec_huffman_lookup(16#a4, 16#9) -> {more, 16#a6, 16#06};
+dec_huffman_lookup(16#a4, 16#a) -> {more, 16#a6, 16#0a};
+dec_huffman_lookup(16#a4, 16#b) -> {more, 16#a6, 16#0f};
+dec_huffman_lookup(16#a4, 16#c) -> {more, 16#a6, 16#18};
+dec_huffman_lookup(16#a4, 16#d) -> {more, 16#a6, 16#1f};
+dec_huffman_lookup(16#a4, 16#e) -> {more, 16#a6, 16#29};
+dec_huffman_lookup(16#a4, 16#f) -> {ok, 16#a6, 16#38};
+dec_huffman_lookup(16#a5, 16#0) -> {more, 16#a8, 16#03};
+dec_huffman_lookup(16#a5, 16#1) -> {more, 16#a8, 16#06};
+dec_huffman_lookup(16#a5, 16#2) -> {more, 16#a8, 16#0a};
+dec_huffman_lookup(16#a5, 16#3) -> {more, 16#a8, 16#0f};
+dec_huffman_lookup(16#a5, 16#4) -> {more, 16#a8, 16#18};
+dec_huffman_lookup(16#a5, 16#5) -> {more, 16#a8, 16#1f};
+dec_huffman_lookup(16#a5, 16#6) -> {more, 16#a8, 16#29};
+dec_huffman_lookup(16#a5, 16#7) -> {ok, 16#a8, 16#38};
+dec_huffman_lookup(16#a5, 16#8) -> {more, 16#ae, 16#03};
+dec_huffman_lookup(16#a5, 16#9) -> {more, 16#ae, 16#06};
+dec_huffman_lookup(16#a5, 16#a) -> {more, 16#ae, 16#0a};
+dec_huffman_lookup(16#a5, 16#b) -> {more, 16#ae, 16#0f};
+dec_huffman_lookup(16#a5, 16#c) -> {more, 16#ae, 16#18};
+dec_huffman_lookup(16#a5, 16#d) -> {more, 16#ae, 16#1f};
+dec_huffman_lookup(16#a5, 16#e) -> {more, 16#ae, 16#29};
+dec_huffman_lookup(16#a5, 16#f) -> {ok, 16#ae, 16#38};
+dec_huffman_lookup(16#a6, 16#0) -> {more, 16#af, 16#02};
+dec_huffman_lookup(16#a6, 16#1) -> {more, 16#af, 16#09};
+dec_huffman_lookup(16#a6, 16#2) -> {more, 16#af, 16#17};
+dec_huffman_lookup(16#a6, 16#3) -> {ok, 16#af, 16#28};
+dec_huffman_lookup(16#a6, 16#4) -> {more, 16#b4, 16#02};
+dec_huffman_lookup(16#a6, 16#5) -> {more, 16#b4, 16#09};
+dec_huffman_lookup(16#a6, 16#6) -> {more, 16#b4, 16#17};
+dec_huffman_lookup(16#a6, 16#7) -> {ok, 16#b4, 16#28};
+dec_huffman_lookup(16#a6, 16#8) -> {more, 16#b6, 16#02};
+dec_huffman_lookup(16#a6, 16#9) -> {more, 16#b6, 16#09};
+dec_huffman_lookup(16#a6, 16#a) -> {more, 16#b6, 16#17};
+dec_huffman_lookup(16#a6, 16#b) -> {ok, 16#b6, 16#28};
+dec_huffman_lookup(16#a6, 16#c) -> {more, 16#b7, 16#02};
+dec_huffman_lookup(16#a6, 16#d) -> {more, 16#b7, 16#09};
+dec_huffman_lookup(16#a6, 16#e) -> {more, 16#b7, 16#17};
+dec_huffman_lookup(16#a6, 16#f) -> {ok, 16#b7, 16#28};
+dec_huffman_lookup(16#a7, 16#0) -> {more, 16#af, 16#03};
+dec_huffman_lookup(16#a7, 16#1) -> {more, 16#af, 16#06};
+dec_huffman_lookup(16#a7, 16#2) -> {more, 16#af, 16#0a};
+dec_huffman_lookup(16#a7, 16#3) -> {more, 16#af, 16#0f};
+dec_huffman_lookup(16#a7, 16#4) -> {more, 16#af, 16#18};
+dec_huffman_lookup(16#a7, 16#5) -> {more, 16#af, 16#1f};
+dec_huffman_lookup(16#a7, 16#6) -> {more, 16#af, 16#29};
+dec_huffman_lookup(16#a7, 16#7) -> {ok, 16#af, 16#38};
+dec_huffman_lookup(16#a7, 16#8) -> {more, 16#b4, 16#03};
+dec_huffman_lookup(16#a7, 16#9) -> {more, 16#b4, 16#06};
+dec_huffman_lookup(16#a7, 16#a) -> {more, 16#b4, 16#0a};
+dec_huffman_lookup(16#a7, 16#b) -> {more, 16#b4, 16#0f};
+dec_huffman_lookup(16#a7, 16#c) -> {more, 16#b4, 16#18};
+dec_huffman_lookup(16#a7, 16#d) -> {more, 16#b4, 16#1f};
+dec_huffman_lookup(16#a7, 16#e) -> {more, 16#b4, 16#29};
+dec_huffman_lookup(16#a7, 16#f) -> {ok, 16#b4, 16#38};
+dec_huffman_lookup(16#a8, 16#0) -> {more, 16#b6, 16#03};
+dec_huffman_lookup(16#a8, 16#1) -> {more, 16#b6, 16#06};
+dec_huffman_lookup(16#a8, 16#2) -> {more, 16#b6, 16#0a};
+dec_huffman_lookup(16#a8, 16#3) -> {more, 16#b6, 16#0f};
+dec_huffman_lookup(16#a8, 16#4) -> {more, 16#b6, 16#18};
+dec_huffman_lookup(16#a8, 16#5) -> {more, 16#b6, 16#1f};
+dec_huffman_lookup(16#a8, 16#6) -> {more, 16#b6, 16#29};
+dec_huffman_lookup(16#a8, 16#7) -> {ok, 16#b6, 16#38};
+dec_huffman_lookup(16#a8, 16#8) -> {more, 16#b7, 16#03};
+dec_huffman_lookup(16#a8, 16#9) -> {more, 16#b7, 16#06};
+dec_huffman_lookup(16#a8, 16#a) -> {more, 16#b7, 16#0a};
+dec_huffman_lookup(16#a8, 16#b) -> {more, 16#b7, 16#0f};
+dec_huffman_lookup(16#a8, 16#c) -> {more, 16#b7, 16#18};
+dec_huffman_lookup(16#a8, 16#d) -> {more, 16#b7, 16#1f};
+dec_huffman_lookup(16#a8, 16#e) -> {more, 16#b7, 16#29};
+dec_huffman_lookup(16#a8, 16#f) -> {ok, 16#b7, 16#38};
+dec_huffman_lookup(16#a9, 16#0) -> {ok, 16#bc, 16#00};
+dec_huffman_lookup(16#a9, 16#1) -> {ok, 16#bf, 16#00};
+dec_huffman_lookup(16#a9, 16#2) -> {ok, 16#c5, 16#00};
+dec_huffman_lookup(16#a9, 16#3) -> {ok, 16#e7, 16#00};
+dec_huffman_lookup(16#a9, 16#4) -> {ok, 16#ef, 16#00};
+dec_huffman_lookup(16#a9, 16#5) -> {more, undefined, 16#b0};
+dec_huffman_lookup(16#a9, 16#6) -> {more, undefined, 16#b2};
+dec_huffman_lookup(16#a9, 16#7) -> {more, undefined, 16#b3};
+dec_huffman_lookup(16#a9, 16#8) -> {more, undefined, 16#b7};
+dec_huffman_lookup(16#a9, 16#9) -> {more, undefined, 16#b8};
+dec_huffman_lookup(16#a9, 16#a) -> {more, undefined, 16#ba};
+dec_huffman_lookup(16#a9, 16#b) -> {more, undefined, 16#bb};
+dec_huffman_lookup(16#a9, 16#c) -> {more, undefined, 16#c0};
+dec_huffman_lookup(16#a9, 16#d) -> {more, undefined, 16#c7};
+dec_huffman_lookup(16#a9, 16#e) -> {more, undefined, 16#d0};
+dec_huffman_lookup(16#a9, 16#f) -> {ok, undefined, 16#df};
+dec_huffman_lookup(16#aa, 16#0) -> {more, 16#bc, 16#01};
+dec_huffman_lookup(16#aa, 16#1) -> {ok, 16#bc, 16#16};
+dec_huffman_lookup(16#aa, 16#2) -> {more, 16#bf, 16#01};
+dec_huffman_lookup(16#aa, 16#3) -> {ok, 16#bf, 16#16};
+dec_huffman_lookup(16#aa, 16#4) -> {more, 16#c5, 16#01};
+dec_huffman_lookup(16#aa, 16#5) -> {ok, 16#c5, 16#16};
+dec_huffman_lookup(16#aa, 16#6) -> {more, 16#e7, 16#01};
+dec_huffman_lookup(16#aa, 16#7) -> {ok, 16#e7, 16#16};
+dec_huffman_lookup(16#aa, 16#8) -> {more, 16#ef, 16#01};
+dec_huffman_lookup(16#aa, 16#9) -> {ok, 16#ef, 16#16};
+dec_huffman_lookup(16#aa, 16#a) -> {ok, 16#09, 16#00};
+dec_huffman_lookup(16#aa, 16#b) -> {ok, 16#8e, 16#00};
+dec_huffman_lookup(16#aa, 16#c) -> {ok, 16#90, 16#00};
+dec_huffman_lookup(16#aa, 16#d) -> {ok, 16#91, 16#00};
+dec_huffman_lookup(16#aa, 16#e) -> {ok, 16#94, 16#00};
+dec_huffman_lookup(16#aa, 16#f) -> {ok, 16#9f, 16#00};
+dec_huffman_lookup(16#ab, 16#0) -> {more, 16#bc, 16#02};
+dec_huffman_lookup(16#ab, 16#1) -> {more, 16#bc, 16#09};
+dec_huffman_lookup(16#ab, 16#2) -> {more, 16#bc, 16#17};
+dec_huffman_lookup(16#ab, 16#3) -> {ok, 16#bc, 16#28};
+dec_huffman_lookup(16#ab, 16#4) -> {more, 16#bf, 16#02};
+dec_huffman_lookup(16#ab, 16#5) -> {more, 16#bf, 16#09};
+dec_huffman_lookup(16#ab, 16#6) -> {more, 16#bf, 16#17};
+dec_huffman_lookup(16#ab, 16#7) -> {ok, 16#bf, 16#28};
+dec_huffman_lookup(16#ab, 16#8) -> {more, 16#c5, 16#02};
+dec_huffman_lookup(16#ab, 16#9) -> {more, 16#c5, 16#09};
+dec_huffman_lookup(16#ab, 16#a) -> {more, 16#c5, 16#17};
+dec_huffman_lookup(16#ab, 16#b) -> {ok, 16#c5, 16#28};
+dec_huffman_lookup(16#ab, 16#c) -> {more, 16#e7, 16#02};
+dec_huffman_lookup(16#ab, 16#d) -> {more, 16#e7, 16#09};
+dec_huffman_lookup(16#ab, 16#e) -> {more, 16#e7, 16#17};
+dec_huffman_lookup(16#ab, 16#f) -> {ok, 16#e7, 16#28};
+dec_huffman_lookup(16#ac, 16#0) -> {more, 16#bc, 16#03};
+dec_huffman_lookup(16#ac, 16#1) -> {more, 16#bc, 16#06};
+dec_huffman_lookup(16#ac, 16#2) -> {more, 16#bc, 16#0a};
+dec_huffman_lookup(16#ac, 16#3) -> {more, 16#bc, 16#0f};
+dec_huffman_lookup(16#ac, 16#4) -> {more, 16#bc, 16#18};
+dec_huffman_lookup(16#ac, 16#5) -> {more, 16#bc, 16#1f};
+dec_huffman_lookup(16#ac, 16#6) -> {more, 16#bc, 16#29};
+dec_huffman_lookup(16#ac, 16#7) -> {ok, 16#bc, 16#38};
+dec_huffman_lookup(16#ac, 16#8) -> {more, 16#bf, 16#03};
+dec_huffman_lookup(16#ac, 16#9) -> {more, 16#bf, 16#06};
+dec_huffman_lookup(16#ac, 16#a) -> {more, 16#bf, 16#0a};
+dec_huffman_lookup(16#ac, 16#b) -> {more, 16#bf, 16#0f};
+dec_huffman_lookup(16#ac, 16#c) -> {more, 16#bf, 16#18};
+dec_huffman_lookup(16#ac, 16#d) -> {more, 16#bf, 16#1f};
+dec_huffman_lookup(16#ac, 16#e) -> {more, 16#bf, 16#29};
+dec_huffman_lookup(16#ac, 16#f) -> {ok, 16#bf, 16#38};
+dec_huffman_lookup(16#ad, 16#0) -> {more, 16#c5, 16#03};
+dec_huffman_lookup(16#ad, 16#1) -> {more, 16#c5, 16#06};
+dec_huffman_lookup(16#ad, 16#2) -> {more, 16#c5, 16#0a};
+dec_huffman_lookup(16#ad, 16#3) -> {more, 16#c5, 16#0f};
+dec_huffman_lookup(16#ad, 16#4) -> {more, 16#c5, 16#18};
+dec_huffman_lookup(16#ad, 16#5) -> {more, 16#c5, 16#1f};
+dec_huffman_lookup(16#ad, 16#6) -> {more, 16#c5, 16#29};
+dec_huffman_lookup(16#ad, 16#7) -> {ok, 16#c5, 16#38};
+dec_huffman_lookup(16#ad, 16#8) -> {more, 16#e7, 16#03};
+dec_huffman_lookup(16#ad, 16#9) -> {more, 16#e7, 16#06};
+dec_huffman_lookup(16#ad, 16#a) -> {more, 16#e7, 16#0a};
+dec_huffman_lookup(16#ad, 16#b) -> {more, 16#e7, 16#0f};
+dec_huffman_lookup(16#ad, 16#c) -> {more, 16#e7, 16#18};
+dec_huffman_lookup(16#ad, 16#d) -> {more, 16#e7, 16#1f};
+dec_huffman_lookup(16#ad, 16#e) -> {more, 16#e7, 16#29};
+dec_huffman_lookup(16#ad, 16#f) -> {ok, 16#e7, 16#38};
+dec_huffman_lookup(16#ae, 16#0) -> {more, 16#ef, 16#02};
+dec_huffman_lookup(16#ae, 16#1) -> {more, 16#ef, 16#09};
+dec_huffman_lookup(16#ae, 16#2) -> {more, 16#ef, 16#17};
+dec_huffman_lookup(16#ae, 16#3) -> {ok, 16#ef, 16#28};
+dec_huffman_lookup(16#ae, 16#4) -> {more, 16#09, 16#01};
+dec_huffman_lookup(16#ae, 16#5) -> {ok, 16#09, 16#16};
+dec_huffman_lookup(16#ae, 16#6) -> {more, 16#8e, 16#01};
+dec_huffman_lookup(16#ae, 16#7) -> {ok, 16#8e, 16#16};
+dec_huffman_lookup(16#ae, 16#8) -> {more, 16#90, 16#01};
+dec_huffman_lookup(16#ae, 16#9) -> {ok, 16#90, 16#16};
+dec_huffman_lookup(16#ae, 16#a) -> {more, 16#91, 16#01};
+dec_huffman_lookup(16#ae, 16#b) -> {ok, 16#91, 16#16};
+dec_huffman_lookup(16#ae, 16#c) -> {more, 16#94, 16#01};
+dec_huffman_lookup(16#ae, 16#d) -> {ok, 16#94, 16#16};
+dec_huffman_lookup(16#ae, 16#e) -> {more, 16#9f, 16#01};
+dec_huffman_lookup(16#ae, 16#f) -> {ok, 16#9f, 16#16};
+dec_huffman_lookup(16#af, 16#0) -> {more, 16#ef, 16#03};
+dec_huffman_lookup(16#af, 16#1) -> {more, 16#ef, 16#06};
+dec_huffman_lookup(16#af, 16#2) -> {more, 16#ef, 16#0a};
+dec_huffman_lookup(16#af, 16#3) -> {more, 16#ef, 16#0f};
+dec_huffman_lookup(16#af, 16#4) -> {more, 16#ef, 16#18};
+dec_huffman_lookup(16#af, 16#5) -> {more, 16#ef, 16#1f};
+dec_huffman_lookup(16#af, 16#6) -> {more, 16#ef, 16#29};
+dec_huffman_lookup(16#af, 16#7) -> {ok, 16#ef, 16#38};
+dec_huffman_lookup(16#af, 16#8) -> {more, 16#09, 16#02};
+dec_huffman_lookup(16#af, 16#9) -> {more, 16#09, 16#09};
+dec_huffman_lookup(16#af, 16#a) -> {more, 16#09, 16#17};
+dec_huffman_lookup(16#af, 16#b) -> {ok, 16#09, 16#28};
+dec_huffman_lookup(16#af, 16#c) -> {more, 16#8e, 16#02};
+dec_huffman_lookup(16#af, 16#d) -> {more, 16#8e, 16#09};
+dec_huffman_lookup(16#af, 16#e) -> {more, 16#8e, 16#17};
+dec_huffman_lookup(16#af, 16#f) -> {ok, 16#8e, 16#28};
+dec_huffman_lookup(16#b0, 16#0) -> {more, 16#09, 16#03};
+dec_huffman_lookup(16#b0, 16#1) -> {more, 16#09, 16#06};
+dec_huffman_lookup(16#b0, 16#2) -> {more, 16#09, 16#0a};
+dec_huffman_lookup(16#b0, 16#3) -> {more, 16#09, 16#0f};
+dec_huffman_lookup(16#b0, 16#4) -> {more, 16#09, 16#18};
+dec_huffman_lookup(16#b0, 16#5) -> {more, 16#09, 16#1f};
+dec_huffman_lookup(16#b0, 16#6) -> {more, 16#09, 16#29};
+dec_huffman_lookup(16#b0, 16#7) -> {ok, 16#09, 16#38};
+dec_huffman_lookup(16#b0, 16#8) -> {more, 16#8e, 16#03};
+dec_huffman_lookup(16#b0, 16#9) -> {more, 16#8e, 16#06};
+dec_huffman_lookup(16#b0, 16#a) -> {more, 16#8e, 16#0a};
+dec_huffman_lookup(16#b0, 16#b) -> {more, 16#8e, 16#0f};
+dec_huffman_lookup(16#b0, 16#c) -> {more, 16#8e, 16#18};
+dec_huffman_lookup(16#b0, 16#d) -> {more, 16#8e, 16#1f};
+dec_huffman_lookup(16#b0, 16#e) -> {more, 16#8e, 16#29};
+dec_huffman_lookup(16#b0, 16#f) -> {ok, 16#8e, 16#38};
+dec_huffman_lookup(16#b1, 16#0) -> {more, 16#90, 16#02};
+dec_huffman_lookup(16#b1, 16#1) -> {more, 16#90, 16#09};
+dec_huffman_lookup(16#b1, 16#2) -> {more, 16#90, 16#17};
+dec_huffman_lookup(16#b1, 16#3) -> {ok, 16#90, 16#28};
+dec_huffman_lookup(16#b1, 16#4) -> {more, 16#91, 16#02};
+dec_huffman_lookup(16#b1, 16#5) -> {more, 16#91, 16#09};
+dec_huffman_lookup(16#b1, 16#6) -> {more, 16#91, 16#17};
+dec_huffman_lookup(16#b1, 16#7) -> {ok, 16#91, 16#28};
+dec_huffman_lookup(16#b1, 16#8) -> {more, 16#94, 16#02};
+dec_huffman_lookup(16#b1, 16#9) -> {more, 16#94, 16#09};
+dec_huffman_lookup(16#b1, 16#a) -> {more, 16#94, 16#17};
+dec_huffman_lookup(16#b1, 16#b) -> {ok, 16#94, 16#28};
+dec_huffman_lookup(16#b1, 16#c) -> {more, 16#9f, 16#02};
+dec_huffman_lookup(16#b1, 16#d) -> {more, 16#9f, 16#09};
+dec_huffman_lookup(16#b1, 16#e) -> {more, 16#9f, 16#17};
+dec_huffman_lookup(16#b1, 16#f) -> {ok, 16#9f, 16#28};
+dec_huffman_lookup(16#b2, 16#0) -> {more, 16#90, 16#03};
+dec_huffman_lookup(16#b2, 16#1) -> {more, 16#90, 16#06};
+dec_huffman_lookup(16#b2, 16#2) -> {more, 16#90, 16#0a};
+dec_huffman_lookup(16#b2, 16#3) -> {more, 16#90, 16#0f};
+dec_huffman_lookup(16#b2, 16#4) -> {more, 16#90, 16#18};
+dec_huffman_lookup(16#b2, 16#5) -> {more, 16#90, 16#1f};
+dec_huffman_lookup(16#b2, 16#6) -> {more, 16#90, 16#29};
+dec_huffman_lookup(16#b2, 16#7) -> {ok, 16#90, 16#38};
+dec_huffman_lookup(16#b2, 16#8) -> {more, 16#91, 16#03};
+dec_huffman_lookup(16#b2, 16#9) -> {more, 16#91, 16#06};
+dec_huffman_lookup(16#b2, 16#a) -> {more, 16#91, 16#0a};
+dec_huffman_lookup(16#b2, 16#b) -> {more, 16#91, 16#0f};
+dec_huffman_lookup(16#b2, 16#c) -> {more, 16#91, 16#18};
+dec_huffman_lookup(16#b2, 16#d) -> {more, 16#91, 16#1f};
+dec_huffman_lookup(16#b2, 16#e) -> {more, 16#91, 16#29};
+dec_huffman_lookup(16#b2, 16#f) -> {ok, 16#91, 16#38};
+dec_huffman_lookup(16#b3, 16#0) -> {more, 16#94, 16#03};
+dec_huffman_lookup(16#b3, 16#1) -> {more, 16#94, 16#06};
+dec_huffman_lookup(16#b3, 16#2) -> {more, 16#94, 16#0a};
+dec_huffman_lookup(16#b3, 16#3) -> {more, 16#94, 16#0f};
+dec_huffman_lookup(16#b3, 16#4) -> {more, 16#94, 16#18};
+dec_huffman_lookup(16#b3, 16#5) -> {more, 16#94, 16#1f};
+dec_huffman_lookup(16#b3, 16#6) -> {more, 16#94, 16#29};
+dec_huffman_lookup(16#b3, 16#7) -> {ok, 16#94, 16#38};
+dec_huffman_lookup(16#b3, 16#8) -> {more, 16#9f, 16#03};
+dec_huffman_lookup(16#b3, 16#9) -> {more, 16#9f, 16#06};
+dec_huffman_lookup(16#b3, 16#a) -> {more, 16#9f, 16#0a};
+dec_huffman_lookup(16#b3, 16#b) -> {more, 16#9f, 16#0f};
+dec_huffman_lookup(16#b3, 16#c) -> {more, 16#9f, 16#18};
+dec_huffman_lookup(16#b3, 16#d) -> {more, 16#9f, 16#1f};
+dec_huffman_lookup(16#b3, 16#e) -> {more, 16#9f, 16#29};
+dec_huffman_lookup(16#b3, 16#f) -> {ok, 16#9f, 16#38};
+dec_huffman_lookup(16#b4, 16#0) -> {ok, 16#ab, 16#00};
+dec_huffman_lookup(16#b4, 16#1) -> {ok, 16#ce, 16#00};
+dec_huffman_lookup(16#b4, 16#2) -> {ok, 16#d7, 16#00};
+dec_huffman_lookup(16#b4, 16#3) -> {ok, 16#e1, 16#00};
+dec_huffman_lookup(16#b4, 16#4) -> {ok, 16#ec, 16#00};
+dec_huffman_lookup(16#b4, 16#5) -> {ok, 16#ed, 16#00};
+dec_huffman_lookup(16#b4, 16#6) -> {more, undefined, 16#bc};
+dec_huffman_lookup(16#b4, 16#7) -> {more, undefined, 16#bd};
+dec_huffman_lookup(16#b4, 16#8) -> {more, undefined, 16#c1};
+dec_huffman_lookup(16#b4, 16#9) -> {more, undefined, 16#c4};
+dec_huffman_lookup(16#b4, 16#a) -> {more, undefined, 16#c8};
+dec_huffman_lookup(16#b4, 16#b) -> {more, undefined, 16#cb};
+dec_huffman_lookup(16#b4, 16#c) -> {more, undefined, 16#d1};
+dec_huffman_lookup(16#b4, 16#d) -> {more, undefined, 16#d8};
+dec_huffman_lookup(16#b4, 16#e) -> {more, undefined, 16#e0};
+dec_huffman_lookup(16#b4, 16#f) -> {ok, undefined, 16#ee};
+dec_huffman_lookup(16#b5, 16#0) -> {more, 16#ab, 16#01};
+dec_huffman_lookup(16#b5, 16#1) -> {ok, 16#ab, 16#16};
+dec_huffman_lookup(16#b5, 16#2) -> {more, 16#ce, 16#01};
+dec_huffman_lookup(16#b5, 16#3) -> {ok, 16#ce, 16#16};
+dec_huffman_lookup(16#b5, 16#4) -> {more, 16#d7, 16#01};
+dec_huffman_lookup(16#b5, 16#5) -> {ok, 16#d7, 16#16};
+dec_huffman_lookup(16#b5, 16#6) -> {more, 16#e1, 16#01};
+dec_huffman_lookup(16#b5, 16#7) -> {ok, 16#e1, 16#16};
+dec_huffman_lookup(16#b5, 16#8) -> {more, 16#ec, 16#01};
+dec_huffman_lookup(16#b5, 16#9) -> {ok, 16#ec, 16#16};
+dec_huffman_lookup(16#b5, 16#a) -> {more, 16#ed, 16#01};
+dec_huffman_lookup(16#b5, 16#b) -> {ok, 16#ed, 16#16};
+dec_huffman_lookup(16#b5, 16#c) -> {ok, 16#c7, 16#00};
+dec_huffman_lookup(16#b5, 16#d) -> {ok, 16#cf, 16#00};
+dec_huffman_lookup(16#b5, 16#e) -> {ok, 16#ea, 16#00};
+dec_huffman_lookup(16#b5, 16#f) -> {ok, 16#eb, 16#00};
+dec_huffman_lookup(16#b6, 16#0) -> {more, 16#ab, 16#02};
+dec_huffman_lookup(16#b6, 16#1) -> {more, 16#ab, 16#09};
+dec_huffman_lookup(16#b6, 16#2) -> {more, 16#ab, 16#17};
+dec_huffman_lookup(16#b6, 16#3) -> {ok, 16#ab, 16#28};
+dec_huffman_lookup(16#b6, 16#4) -> {more, 16#ce, 16#02};
+dec_huffman_lookup(16#b6, 16#5) -> {more, 16#ce, 16#09};
+dec_huffman_lookup(16#b6, 16#6) -> {more, 16#ce, 16#17};
+dec_huffman_lookup(16#b6, 16#7) -> {ok, 16#ce, 16#28};
+dec_huffman_lookup(16#b6, 16#8) -> {more, 16#d7, 16#02};
+dec_huffman_lookup(16#b6, 16#9) -> {more, 16#d7, 16#09};
+dec_huffman_lookup(16#b6, 16#a) -> {more, 16#d7, 16#17};
+dec_huffman_lookup(16#b6, 16#b) -> {ok, 16#d7, 16#28};
+dec_huffman_lookup(16#b6, 16#c) -> {more, 16#e1, 16#02};
+dec_huffman_lookup(16#b6, 16#d) -> {more, 16#e1, 16#09};
+dec_huffman_lookup(16#b6, 16#e) -> {more, 16#e1, 16#17};
+dec_huffman_lookup(16#b6, 16#f) -> {ok, 16#e1, 16#28};
+dec_huffman_lookup(16#b7, 16#0) -> {more, 16#ab, 16#03};
+dec_huffman_lookup(16#b7, 16#1) -> {more, 16#ab, 16#06};
+dec_huffman_lookup(16#b7, 16#2) -> {more, 16#ab, 16#0a};
+dec_huffman_lookup(16#b7, 16#3) -> {more, 16#ab, 16#0f};
+dec_huffman_lookup(16#b7, 16#4) -> {more, 16#ab, 16#18};
+dec_huffman_lookup(16#b7, 16#5) -> {more, 16#ab, 16#1f};
+dec_huffman_lookup(16#b7, 16#6) -> {more, 16#ab, 16#29};
+dec_huffman_lookup(16#b7, 16#7) -> {ok, 16#ab, 16#38};
+dec_huffman_lookup(16#b7, 16#8) -> {more, 16#ce, 16#03};
+dec_huffman_lookup(16#b7, 16#9) -> {more, 16#ce, 16#06};
+dec_huffman_lookup(16#b7, 16#a) -> {more, 16#ce, 16#0a};
+dec_huffman_lookup(16#b7, 16#b) -> {more, 16#ce, 16#0f};
+dec_huffman_lookup(16#b7, 16#c) -> {more, 16#ce, 16#18};
+dec_huffman_lookup(16#b7, 16#d) -> {more, 16#ce, 16#1f};
+dec_huffman_lookup(16#b7, 16#e) -> {more, 16#ce, 16#29};
+dec_huffman_lookup(16#b7, 16#f) -> {ok, 16#ce, 16#38};
+dec_huffman_lookup(16#b8, 16#0) -> {more, 16#d7, 16#03};
+dec_huffman_lookup(16#b8, 16#1) -> {more, 16#d7, 16#06};
+dec_huffman_lookup(16#b8, 16#2) -> {more, 16#d7, 16#0a};
+dec_huffman_lookup(16#b8, 16#3) -> {more, 16#d7, 16#0f};
+dec_huffman_lookup(16#b8, 16#4) -> {more, 16#d7, 16#18};
+dec_huffman_lookup(16#b8, 16#5) -> {more, 16#d7, 16#1f};
+dec_huffman_lookup(16#b8, 16#6) -> {more, 16#d7, 16#29};
+dec_huffman_lookup(16#b8, 16#7) -> {ok, 16#d7, 16#38};
+dec_huffman_lookup(16#b8, 16#8) -> {more, 16#e1, 16#03};
+dec_huffman_lookup(16#b8, 16#9) -> {more, 16#e1, 16#06};
+dec_huffman_lookup(16#b8, 16#a) -> {more, 16#e1, 16#0a};
+dec_huffman_lookup(16#b8, 16#b) -> {more, 16#e1, 16#0f};
+dec_huffman_lookup(16#b8, 16#c) -> {more, 16#e1, 16#18};
+dec_huffman_lookup(16#b8, 16#d) -> {more, 16#e1, 16#1f};
+dec_huffman_lookup(16#b8, 16#e) -> {more, 16#e1, 16#29};
+dec_huffman_lookup(16#b8, 16#f) -> {ok, 16#e1, 16#38};
+dec_huffman_lookup(16#b9, 16#0) -> {more, 16#ec, 16#02};
+dec_huffman_lookup(16#b9, 16#1) -> {more, 16#ec, 16#09};
+dec_huffman_lookup(16#b9, 16#2) -> {more, 16#ec, 16#17};
+dec_huffman_lookup(16#b9, 16#3) -> {ok, 16#ec, 16#28};
+dec_huffman_lookup(16#b9, 16#4) -> {more, 16#ed, 16#02};
+dec_huffman_lookup(16#b9, 16#5) -> {more, 16#ed, 16#09};
+dec_huffman_lookup(16#b9, 16#6) -> {more, 16#ed, 16#17};
+dec_huffman_lookup(16#b9, 16#7) -> {ok, 16#ed, 16#28};
+dec_huffman_lookup(16#b9, 16#8) -> {more, 16#c7, 16#01};
+dec_huffman_lookup(16#b9, 16#9) -> {ok, 16#c7, 16#16};
+dec_huffman_lookup(16#b9, 16#a) -> {more, 16#cf, 16#01};
+dec_huffman_lookup(16#b9, 16#b) -> {ok, 16#cf, 16#16};
+dec_huffman_lookup(16#b9, 16#c) -> {more, 16#ea, 16#01};
+dec_huffman_lookup(16#b9, 16#d) -> {ok, 16#ea, 16#16};
+dec_huffman_lookup(16#b9, 16#e) -> {more, 16#eb, 16#01};
+dec_huffman_lookup(16#b9, 16#f) -> {ok, 16#eb, 16#16};
+dec_huffman_lookup(16#ba, 16#0) -> {more, 16#ec, 16#03};
+dec_huffman_lookup(16#ba, 16#1) -> {more, 16#ec, 16#06};
+dec_huffman_lookup(16#ba, 16#2) -> {more, 16#ec, 16#0a};
+dec_huffman_lookup(16#ba, 16#3) -> {more, 16#ec, 16#0f};
+dec_huffman_lookup(16#ba, 16#4) -> {more, 16#ec, 16#18};
+dec_huffman_lookup(16#ba, 16#5) -> {more, 16#ec, 16#1f};
+dec_huffman_lookup(16#ba, 16#6) -> {more, 16#ec, 16#29};
+dec_huffman_lookup(16#ba, 16#7) -> {ok, 16#ec, 16#38};
+dec_huffman_lookup(16#ba, 16#8) -> {more, 16#ed, 16#03};
+dec_huffman_lookup(16#ba, 16#9) -> {more, 16#ed, 16#06};
+dec_huffman_lookup(16#ba, 16#a) -> {more, 16#ed, 16#0a};
+dec_huffman_lookup(16#ba, 16#b) -> {more, 16#ed, 16#0f};
+dec_huffman_lookup(16#ba, 16#c) -> {more, 16#ed, 16#18};
+dec_huffman_lookup(16#ba, 16#d) -> {more, 16#ed, 16#1f};
+dec_huffman_lookup(16#ba, 16#e) -> {more, 16#ed, 16#29};
+dec_huffman_lookup(16#ba, 16#f) -> {ok, 16#ed, 16#38};
+dec_huffman_lookup(16#bb, 16#0) -> {more, 16#c7, 16#02};
+dec_huffman_lookup(16#bb, 16#1) -> {more, 16#c7, 16#09};
+dec_huffman_lookup(16#bb, 16#2) -> {more, 16#c7, 16#17};
+dec_huffman_lookup(16#bb, 16#3) -> {ok, 16#c7, 16#28};
+dec_huffman_lookup(16#bb, 16#4) -> {more, 16#cf, 16#02};
+dec_huffman_lookup(16#bb, 16#5) -> {more, 16#cf, 16#09};
+dec_huffman_lookup(16#bb, 16#6) -> {more, 16#cf, 16#17};
+dec_huffman_lookup(16#bb, 16#7) -> {ok, 16#cf, 16#28};
+dec_huffman_lookup(16#bb, 16#8) -> {more, 16#ea, 16#02};
+dec_huffman_lookup(16#bb, 16#9) -> {more, 16#ea, 16#09};
+dec_huffman_lookup(16#bb, 16#a) -> {more, 16#ea, 16#17};
+dec_huffman_lookup(16#bb, 16#b) -> {ok, 16#ea, 16#28};
+dec_huffman_lookup(16#bb, 16#c) -> {more, 16#eb, 16#02};
+dec_huffman_lookup(16#bb, 16#d) -> {more, 16#eb, 16#09};
+dec_huffman_lookup(16#bb, 16#e) -> {more, 16#eb, 16#17};
+dec_huffman_lookup(16#bb, 16#f) -> {ok, 16#eb, 16#28};
+dec_huffman_lookup(16#bc, 16#0) -> {more, 16#c7, 16#03};
+dec_huffman_lookup(16#bc, 16#1) -> {more, 16#c7, 16#06};
+dec_huffman_lookup(16#bc, 16#2) -> {more, 16#c7, 16#0a};
+dec_huffman_lookup(16#bc, 16#3) -> {more, 16#c7, 16#0f};
+dec_huffman_lookup(16#bc, 16#4) -> {more, 16#c7, 16#18};
+dec_huffman_lookup(16#bc, 16#5) -> {more, 16#c7, 16#1f};
+dec_huffman_lookup(16#bc, 16#6) -> {more, 16#c7, 16#29};
+dec_huffman_lookup(16#bc, 16#7) -> {ok, 16#c7, 16#38};
+dec_huffman_lookup(16#bc, 16#8) -> {more, 16#cf, 16#03};
+dec_huffman_lookup(16#bc, 16#9) -> {more, 16#cf, 16#06};
+dec_huffman_lookup(16#bc, 16#a) -> {more, 16#cf, 16#0a};
+dec_huffman_lookup(16#bc, 16#b) -> {more, 16#cf, 16#0f};
+dec_huffman_lookup(16#bc, 16#c) -> {more, 16#cf, 16#18};
+dec_huffman_lookup(16#bc, 16#d) -> {more, 16#cf, 16#1f};
+dec_huffman_lookup(16#bc, 16#e) -> {more, 16#cf, 16#29};
+dec_huffman_lookup(16#bc, 16#f) -> {ok, 16#cf, 16#38};
+dec_huffman_lookup(16#bd, 16#0) -> {more, 16#ea, 16#03};
+dec_huffman_lookup(16#bd, 16#1) -> {more, 16#ea, 16#06};
+dec_huffman_lookup(16#bd, 16#2) -> {more, 16#ea, 16#0a};
+dec_huffman_lookup(16#bd, 16#3) -> {more, 16#ea, 16#0f};
+dec_huffman_lookup(16#bd, 16#4) -> {more, 16#ea, 16#18};
+dec_huffman_lookup(16#bd, 16#5) -> {more, 16#ea, 16#1f};
+dec_huffman_lookup(16#bd, 16#6) -> {more, 16#ea, 16#29};
+dec_huffman_lookup(16#bd, 16#7) -> {ok, 16#ea, 16#38};
+dec_huffman_lookup(16#bd, 16#8) -> {more, 16#eb, 16#03};
+dec_huffman_lookup(16#bd, 16#9) -> {more, 16#eb, 16#06};
+dec_huffman_lookup(16#bd, 16#a) -> {more, 16#eb, 16#0a};
+dec_huffman_lookup(16#bd, 16#b) -> {more, 16#eb, 16#0f};
+dec_huffman_lookup(16#bd, 16#c) -> {more, 16#eb, 16#18};
+dec_huffman_lookup(16#bd, 16#d) -> {more, 16#eb, 16#1f};
+dec_huffman_lookup(16#bd, 16#e) -> {more, 16#eb, 16#29};
+dec_huffman_lookup(16#bd, 16#f) -> {ok, 16#eb, 16#38};
+dec_huffman_lookup(16#be, 16#0) -> {more, undefined, 16#c2};
+dec_huffman_lookup(16#be, 16#1) -> {more, undefined, 16#c3};
+dec_huffman_lookup(16#be, 16#2) -> {more, undefined, 16#c5};
+dec_huffman_lookup(16#be, 16#3) -> {more, undefined, 16#c6};
+dec_huffman_lookup(16#be, 16#4) -> {more, undefined, 16#c9};
+dec_huffman_lookup(16#be, 16#5) -> {more, undefined, 16#ca};
+dec_huffman_lookup(16#be, 16#6) -> {more, undefined, 16#cc};
+dec_huffman_lookup(16#be, 16#7) -> {more, undefined, 16#cd};
+dec_huffman_lookup(16#be, 16#8) -> {more, undefined, 16#d2};
+dec_huffman_lookup(16#be, 16#9) -> {more, undefined, 16#d5};
+dec_huffman_lookup(16#be, 16#a) -> {more, undefined, 16#d9};
+dec_huffman_lookup(16#be, 16#b) -> {more, undefined, 16#dc};
+dec_huffman_lookup(16#be, 16#c) -> {more, undefined, 16#e1};
+dec_huffman_lookup(16#be, 16#d) -> {more, undefined, 16#e7};
+dec_huffman_lookup(16#be, 16#e) -> {more, undefined, 16#ef};
+dec_huffman_lookup(16#be, 16#f) -> {ok, undefined, 16#f6};
+dec_huffman_lookup(16#bf, 16#0) -> {ok, 16#c0, 16#00};
+dec_huffman_lookup(16#bf, 16#1) -> {ok, 16#c1, 16#00};
+dec_huffman_lookup(16#bf, 16#2) -> {ok, 16#c8, 16#00};
+dec_huffman_lookup(16#bf, 16#3) -> {ok, 16#c9, 16#00};
+dec_huffman_lookup(16#bf, 16#4) -> {ok, 16#ca, 16#00};
+dec_huffman_lookup(16#bf, 16#5) -> {ok, 16#cd, 16#00};
+dec_huffman_lookup(16#bf, 16#6) -> {ok, 16#d2, 16#00};
+dec_huffman_lookup(16#bf, 16#7) -> {ok, 16#d5, 16#00};
+dec_huffman_lookup(16#bf, 16#8) -> {ok, 16#da, 16#00};
+dec_huffman_lookup(16#bf, 16#9) -> {ok, 16#db, 16#00};
+dec_huffman_lookup(16#bf, 16#a) -> {ok, 16#ee, 16#00};
+dec_huffman_lookup(16#bf, 16#b) -> {ok, 16#f0, 16#00};
+dec_huffman_lookup(16#bf, 16#c) -> {ok, 16#f2, 16#00};
+dec_huffman_lookup(16#bf, 16#d) -> {ok, 16#f3, 16#00};
+dec_huffman_lookup(16#bf, 16#e) -> {ok, 16#ff, 16#00};
+dec_huffman_lookup(16#bf, 16#f) -> {more, undefined, 16#ce};
+dec_huffman_lookup(16#c0, 16#0) -> {more, 16#c0, 16#01};
+dec_huffman_lookup(16#c0, 16#1) -> {ok, 16#c0, 16#16};
+dec_huffman_lookup(16#c0, 16#2) -> {more, 16#c1, 16#01};
+dec_huffman_lookup(16#c0, 16#3) -> {ok, 16#c1, 16#16};
+dec_huffman_lookup(16#c0, 16#4) -> {more, 16#c8, 16#01};
+dec_huffman_lookup(16#c0, 16#5) -> {ok, 16#c8, 16#16};
+dec_huffman_lookup(16#c0, 16#6) -> {more, 16#c9, 16#01};
+dec_huffman_lookup(16#c0, 16#7) -> {ok, 16#c9, 16#16};
+dec_huffman_lookup(16#c0, 16#8) -> {more, 16#ca, 16#01};
+dec_huffman_lookup(16#c0, 16#9) -> {ok, 16#ca, 16#16};
+dec_huffman_lookup(16#c0, 16#a) -> {more, 16#cd, 16#01};
+dec_huffman_lookup(16#c0, 16#b) -> {ok, 16#cd, 16#16};
+dec_huffman_lookup(16#c0, 16#c) -> {more, 16#d2, 16#01};
+dec_huffman_lookup(16#c0, 16#d) -> {ok, 16#d2, 16#16};
+dec_huffman_lookup(16#c0, 16#e) -> {more, 16#d5, 16#01};
+dec_huffman_lookup(16#c0, 16#f) -> {ok, 16#d5, 16#16};
+dec_huffman_lookup(16#c1, 16#0) -> {more, 16#c0, 16#02};
+dec_huffman_lookup(16#c1, 16#1) -> {more, 16#c0, 16#09};
+dec_huffman_lookup(16#c1, 16#2) -> {more, 16#c0, 16#17};
+dec_huffman_lookup(16#c1, 16#3) -> {ok, 16#c0, 16#28};
+dec_huffman_lookup(16#c1, 16#4) -> {more, 16#c1, 16#02};
+dec_huffman_lookup(16#c1, 16#5) -> {more, 16#c1, 16#09};
+dec_huffman_lookup(16#c1, 16#6) -> {more, 16#c1, 16#17};
+dec_huffman_lookup(16#c1, 16#7) -> {ok, 16#c1, 16#28};
+dec_huffman_lookup(16#c1, 16#8) -> {more, 16#c8, 16#02};
+dec_huffman_lookup(16#c1, 16#9) -> {more, 16#c8, 16#09};
+dec_huffman_lookup(16#c1, 16#a) -> {more, 16#c8, 16#17};
+dec_huffman_lookup(16#c1, 16#b) -> {ok, 16#c8, 16#28};
+dec_huffman_lookup(16#c1, 16#c) -> {more, 16#c9, 16#02};
+dec_huffman_lookup(16#c1, 16#d) -> {more, 16#c9, 16#09};
+dec_huffman_lookup(16#c1, 16#e) -> {more, 16#c9, 16#17};
+dec_huffman_lookup(16#c1, 16#f) -> {ok, 16#c9, 16#28};
+dec_huffman_lookup(16#c2, 16#0) -> {more, 16#c0, 16#03};
+dec_huffman_lookup(16#c2, 16#1) -> {more, 16#c0, 16#06};
+dec_huffman_lookup(16#c2, 16#2) -> {more, 16#c0, 16#0a};
+dec_huffman_lookup(16#c2, 16#3) -> {more, 16#c0, 16#0f};
+dec_huffman_lookup(16#c2, 16#4) -> {more, 16#c0, 16#18};
+dec_huffman_lookup(16#c2, 16#5) -> {more, 16#c0, 16#1f};
+dec_huffman_lookup(16#c2, 16#6) -> {more, 16#c0, 16#29};
+dec_huffman_lookup(16#c2, 16#7) -> {ok, 16#c0, 16#38};
+dec_huffman_lookup(16#c2, 16#8) -> {more, 16#c1, 16#03};
+dec_huffman_lookup(16#c2, 16#9) -> {more, 16#c1, 16#06};
+dec_huffman_lookup(16#c2, 16#a) -> {more, 16#c1, 16#0a};
+dec_huffman_lookup(16#c2, 16#b) -> {more, 16#c1, 16#0f};
+dec_huffman_lookup(16#c2, 16#c) -> {more, 16#c1, 16#18};
+dec_huffman_lookup(16#c2, 16#d) -> {more, 16#c1, 16#1f};
+dec_huffman_lookup(16#c2, 16#e) -> {more, 16#c1, 16#29};
+dec_huffman_lookup(16#c2, 16#f) -> {ok, 16#c1, 16#38};
+dec_huffman_lookup(16#c3, 16#0) -> {more, 16#c8, 16#03};
+dec_huffman_lookup(16#c3, 16#1) -> {more, 16#c8, 16#06};
+dec_huffman_lookup(16#c3, 16#2) -> {more, 16#c8, 16#0a};
+dec_huffman_lookup(16#c3, 16#3) -> {more, 16#c8, 16#0f};
+dec_huffman_lookup(16#c3, 16#4) -> {more, 16#c8, 16#18};
+dec_huffman_lookup(16#c3, 16#5) -> {more, 16#c8, 16#1f};
+dec_huffman_lookup(16#c3, 16#6) -> {more, 16#c8, 16#29};
+dec_huffman_lookup(16#c3, 16#7) -> {ok, 16#c8, 16#38};
+dec_huffman_lookup(16#c3, 16#8) -> {more, 16#c9, 16#03};
+dec_huffman_lookup(16#c3, 16#9) -> {more, 16#c9, 16#06};
+dec_huffman_lookup(16#c3, 16#a) -> {more, 16#c9, 16#0a};
+dec_huffman_lookup(16#c3, 16#b) -> {more, 16#c9, 16#0f};
+dec_huffman_lookup(16#c3, 16#c) -> {more, 16#c9, 16#18};
+dec_huffman_lookup(16#c3, 16#d) -> {more, 16#c9, 16#1f};
+dec_huffman_lookup(16#c3, 16#e) -> {more, 16#c9, 16#29};
+dec_huffman_lookup(16#c3, 16#f) -> {ok, 16#c9, 16#38};
+dec_huffman_lookup(16#c4, 16#0) -> {more, 16#ca, 16#02};
+dec_huffman_lookup(16#c4, 16#1) -> {more, 16#ca, 16#09};
+dec_huffman_lookup(16#c4, 16#2) -> {more, 16#ca, 16#17};
+dec_huffman_lookup(16#c4, 16#3) -> {ok, 16#ca, 16#28};
+dec_huffman_lookup(16#c4, 16#4) -> {more, 16#cd, 16#02};
+dec_huffman_lookup(16#c4, 16#5) -> {more, 16#cd, 16#09};
+dec_huffman_lookup(16#c4, 16#6) -> {more, 16#cd, 16#17};
+dec_huffman_lookup(16#c4, 16#7) -> {ok, 16#cd, 16#28};
+dec_huffman_lookup(16#c4, 16#8) -> {more, 16#d2, 16#02};
+dec_huffman_lookup(16#c4, 16#9) -> {more, 16#d2, 16#09};
+dec_huffman_lookup(16#c4, 16#a) -> {more, 16#d2, 16#17};
+dec_huffman_lookup(16#c4, 16#b) -> {ok, 16#d2, 16#28};
+dec_huffman_lookup(16#c4, 16#c) -> {more, 16#d5, 16#02};
+dec_huffman_lookup(16#c4, 16#d) -> {more, 16#d5, 16#09};
+dec_huffman_lookup(16#c4, 16#e) -> {more, 16#d5, 16#17};
+dec_huffman_lookup(16#c4, 16#f) -> {ok, 16#d5, 16#28};
+dec_huffman_lookup(16#c5, 16#0) -> {more, 16#ca, 16#03};
+dec_huffman_lookup(16#c5, 16#1) -> {more, 16#ca, 16#06};
+dec_huffman_lookup(16#c5, 16#2) -> {more, 16#ca, 16#0a};
+dec_huffman_lookup(16#c5, 16#3) -> {more, 16#ca, 16#0f};
+dec_huffman_lookup(16#c5, 16#4) -> {more, 16#ca, 16#18};
+dec_huffman_lookup(16#c5, 16#5) -> {more, 16#ca, 16#1f};
+dec_huffman_lookup(16#c5, 16#6) -> {more, 16#ca, 16#29};
+dec_huffman_lookup(16#c5, 16#7) -> {ok, 16#ca, 16#38};
+dec_huffman_lookup(16#c5, 16#8) -> {more, 16#cd, 16#03};
+dec_huffman_lookup(16#c5, 16#9) -> {more, 16#cd, 16#06};
+dec_huffman_lookup(16#c5, 16#a) -> {more, 16#cd, 16#0a};
+dec_huffman_lookup(16#c5, 16#b) -> {more, 16#cd, 16#0f};
+dec_huffman_lookup(16#c5, 16#c) -> {more, 16#cd, 16#18};
+dec_huffman_lookup(16#c5, 16#d) -> {more, 16#cd, 16#1f};
+dec_huffman_lookup(16#c5, 16#e) -> {more, 16#cd, 16#29};
+dec_huffman_lookup(16#c5, 16#f) -> {ok, 16#cd, 16#38};
+dec_huffman_lookup(16#c6, 16#0) -> {more, 16#d2, 16#03};
+dec_huffman_lookup(16#c6, 16#1) -> {more, 16#d2, 16#06};
+dec_huffman_lookup(16#c6, 16#2) -> {more, 16#d2, 16#0a};
+dec_huffman_lookup(16#c6, 16#3) -> {more, 16#d2, 16#0f};
+dec_huffman_lookup(16#c6, 16#4) -> {more, 16#d2, 16#18};
+dec_huffman_lookup(16#c6, 16#5) -> {more, 16#d2, 16#1f};
+dec_huffman_lookup(16#c6, 16#6) -> {more, 16#d2, 16#29};
+dec_huffman_lookup(16#c6, 16#7) -> {ok, 16#d2, 16#38};
+dec_huffman_lookup(16#c6, 16#8) -> {more, 16#d5, 16#03};
+dec_huffman_lookup(16#c6, 16#9) -> {more, 16#d5, 16#06};
+dec_huffman_lookup(16#c6, 16#a) -> {more, 16#d5, 16#0a};
+dec_huffman_lookup(16#c6, 16#b) -> {more, 16#d5, 16#0f};
+dec_huffman_lookup(16#c6, 16#c) -> {more, 16#d5, 16#18};
+dec_huffman_lookup(16#c6, 16#d) -> {more, 16#d5, 16#1f};
+dec_huffman_lookup(16#c6, 16#e) -> {more, 16#d5, 16#29};
+dec_huffman_lookup(16#c6, 16#f) -> {ok, 16#d5, 16#38};
+dec_huffman_lookup(16#c7, 16#0) -> {more, 16#da, 16#01};
+dec_huffman_lookup(16#c7, 16#1) -> {ok, 16#da, 16#16};
+dec_huffman_lookup(16#c7, 16#2) -> {more, 16#db, 16#01};
+dec_huffman_lookup(16#c7, 16#3) -> {ok, 16#db, 16#16};
+dec_huffman_lookup(16#c7, 16#4) -> {more, 16#ee, 16#01};
+dec_huffman_lookup(16#c7, 16#5) -> {ok, 16#ee, 16#16};
+dec_huffman_lookup(16#c7, 16#6) -> {more, 16#f0, 16#01};
+dec_huffman_lookup(16#c7, 16#7) -> {ok, 16#f0, 16#16};
+dec_huffman_lookup(16#c7, 16#8) -> {more, 16#f2, 16#01};
+dec_huffman_lookup(16#c7, 16#9) -> {ok, 16#f2, 16#16};
+dec_huffman_lookup(16#c7, 16#a) -> {more, 16#f3, 16#01};
+dec_huffman_lookup(16#c7, 16#b) -> {ok, 16#f3, 16#16};
+dec_huffman_lookup(16#c7, 16#c) -> {more, 16#ff, 16#01};
+dec_huffman_lookup(16#c7, 16#d) -> {ok, 16#ff, 16#16};
+dec_huffman_lookup(16#c7, 16#e) -> {ok, 16#cb, 16#00};
+dec_huffman_lookup(16#c7, 16#f) -> {ok, 16#cc, 16#00};
+dec_huffman_lookup(16#c8, 16#0) -> {more, 16#da, 16#02};
+dec_huffman_lookup(16#c8, 16#1) -> {more, 16#da, 16#09};
+dec_huffman_lookup(16#c8, 16#2) -> {more, 16#da, 16#17};
+dec_huffman_lookup(16#c8, 16#3) -> {ok, 16#da, 16#28};
+dec_huffman_lookup(16#c8, 16#4) -> {more, 16#db, 16#02};
+dec_huffman_lookup(16#c8, 16#5) -> {more, 16#db, 16#09};
+dec_huffman_lookup(16#c8, 16#6) -> {more, 16#db, 16#17};
+dec_huffman_lookup(16#c8, 16#7) -> {ok, 16#db, 16#28};
+dec_huffman_lookup(16#c8, 16#8) -> {more, 16#ee, 16#02};
+dec_huffman_lookup(16#c8, 16#9) -> {more, 16#ee, 16#09};
+dec_huffman_lookup(16#c8, 16#a) -> {more, 16#ee, 16#17};
+dec_huffman_lookup(16#c8, 16#b) -> {ok, 16#ee, 16#28};
+dec_huffman_lookup(16#c8, 16#c) -> {more, 16#f0, 16#02};
+dec_huffman_lookup(16#c8, 16#d) -> {more, 16#f0, 16#09};
+dec_huffman_lookup(16#c8, 16#e) -> {more, 16#f0, 16#17};
+dec_huffman_lookup(16#c8, 16#f) -> {ok, 16#f0, 16#28};
+dec_huffman_lookup(16#c9, 16#0) -> {more, 16#da, 16#03};
+dec_huffman_lookup(16#c9, 16#1) -> {more, 16#da, 16#06};
+dec_huffman_lookup(16#c9, 16#2) -> {more, 16#da, 16#0a};
+dec_huffman_lookup(16#c9, 16#3) -> {more, 16#da, 16#0f};
+dec_huffman_lookup(16#c9, 16#4) -> {more, 16#da, 16#18};
+dec_huffman_lookup(16#c9, 16#5) -> {more, 16#da, 16#1f};
+dec_huffman_lookup(16#c9, 16#6) -> {more, 16#da, 16#29};
+dec_huffman_lookup(16#c9, 16#7) -> {ok, 16#da, 16#38};
+dec_huffman_lookup(16#c9, 16#8) -> {more, 16#db, 16#03};
+dec_huffman_lookup(16#c9, 16#9) -> {more, 16#db, 16#06};
+dec_huffman_lookup(16#c9, 16#a) -> {more, 16#db, 16#0a};
+dec_huffman_lookup(16#c9, 16#b) -> {more, 16#db, 16#0f};
+dec_huffman_lookup(16#c9, 16#c) -> {more, 16#db, 16#18};
+dec_huffman_lookup(16#c9, 16#d) -> {more, 16#db, 16#1f};
+dec_huffman_lookup(16#c9, 16#e) -> {more, 16#db, 16#29};
+dec_huffman_lookup(16#c9, 16#f) -> {ok, 16#db, 16#38};
+dec_huffman_lookup(16#ca, 16#0) -> {more, 16#ee, 16#03};
+dec_huffman_lookup(16#ca, 16#1) -> {more, 16#ee, 16#06};
+dec_huffman_lookup(16#ca, 16#2) -> {more, 16#ee, 16#0a};
+dec_huffman_lookup(16#ca, 16#3) -> {more, 16#ee, 16#0f};
+dec_huffman_lookup(16#ca, 16#4) -> {more, 16#ee, 16#18};
+dec_huffman_lookup(16#ca, 16#5) -> {more, 16#ee, 16#1f};
+dec_huffman_lookup(16#ca, 16#6) -> {more, 16#ee, 16#29};
+dec_huffman_lookup(16#ca, 16#7) -> {ok, 16#ee, 16#38};
+dec_huffman_lookup(16#ca, 16#8) -> {more, 16#f0, 16#03};
+dec_huffman_lookup(16#ca, 16#9) -> {more, 16#f0, 16#06};
+dec_huffman_lookup(16#ca, 16#a) -> {more, 16#f0, 16#0a};
+dec_huffman_lookup(16#ca, 16#b) -> {more, 16#f0, 16#0f};
+dec_huffman_lookup(16#ca, 16#c) -> {more, 16#f0, 16#18};
+dec_huffman_lookup(16#ca, 16#d) -> {more, 16#f0, 16#1f};
+dec_huffman_lookup(16#ca, 16#e) -> {more, 16#f0, 16#29};
+dec_huffman_lookup(16#ca, 16#f) -> {ok, 16#f0, 16#38};
+dec_huffman_lookup(16#cb, 16#0) -> {more, 16#f2, 16#02};
+dec_huffman_lookup(16#cb, 16#1) -> {more, 16#f2, 16#09};
+dec_huffman_lookup(16#cb, 16#2) -> {more, 16#f2, 16#17};
+dec_huffman_lookup(16#cb, 16#3) -> {ok, 16#f2, 16#28};
+dec_huffman_lookup(16#cb, 16#4) -> {more, 16#f3, 16#02};
+dec_huffman_lookup(16#cb, 16#5) -> {more, 16#f3, 16#09};
+dec_huffman_lookup(16#cb, 16#6) -> {more, 16#f3, 16#17};
+dec_huffman_lookup(16#cb, 16#7) -> {ok, 16#f3, 16#28};
+dec_huffman_lookup(16#cb, 16#8) -> {more, 16#ff, 16#02};
+dec_huffman_lookup(16#cb, 16#9) -> {more, 16#ff, 16#09};
+dec_huffman_lookup(16#cb, 16#a) -> {more, 16#ff, 16#17};
+dec_huffman_lookup(16#cb, 16#b) -> {ok, 16#ff, 16#28};
+dec_huffman_lookup(16#cb, 16#c) -> {more, 16#cb, 16#01};
+dec_huffman_lookup(16#cb, 16#d) -> {ok, 16#cb, 16#16};
+dec_huffman_lookup(16#cb, 16#e) -> {more, 16#cc, 16#01};
+dec_huffman_lookup(16#cb, 16#f) -> {ok, 16#cc, 16#16};
+dec_huffman_lookup(16#cc, 16#0) -> {more, 16#f2, 16#03};
+dec_huffman_lookup(16#cc, 16#1) -> {more, 16#f2, 16#06};
+dec_huffman_lookup(16#cc, 16#2) -> {more, 16#f2, 16#0a};
+dec_huffman_lookup(16#cc, 16#3) -> {more, 16#f2, 16#0f};
+dec_huffman_lookup(16#cc, 16#4) -> {more, 16#f2, 16#18};
+dec_huffman_lookup(16#cc, 16#5) -> {more, 16#f2, 16#1f};
+dec_huffman_lookup(16#cc, 16#6) -> {more, 16#f2, 16#29};
+dec_huffman_lookup(16#cc, 16#7) -> {ok, 16#f2, 16#38};
+dec_huffman_lookup(16#cc, 16#8) -> {more, 16#f3, 16#03};
+dec_huffman_lookup(16#cc, 16#9) -> {more, 16#f3, 16#06};
+dec_huffman_lookup(16#cc, 16#a) -> {more, 16#f3, 16#0a};
+dec_huffman_lookup(16#cc, 16#b) -> {more, 16#f3, 16#0f};
+dec_huffman_lookup(16#cc, 16#c) -> {more, 16#f3, 16#18};
+dec_huffman_lookup(16#cc, 16#d) -> {more, 16#f3, 16#1f};
+dec_huffman_lookup(16#cc, 16#e) -> {more, 16#f3, 16#29};
+dec_huffman_lookup(16#cc, 16#f) -> {ok, 16#f3, 16#38};
+dec_huffman_lookup(16#cd, 16#0) -> {more, 16#ff, 16#03};
+dec_huffman_lookup(16#cd, 16#1) -> {more, 16#ff, 16#06};
+dec_huffman_lookup(16#cd, 16#2) -> {more, 16#ff, 16#0a};
+dec_huffman_lookup(16#cd, 16#3) -> {more, 16#ff, 16#0f};
+dec_huffman_lookup(16#cd, 16#4) -> {more, 16#ff, 16#18};
+dec_huffman_lookup(16#cd, 16#5) -> {more, 16#ff, 16#1f};
+dec_huffman_lookup(16#cd, 16#6) -> {more, 16#ff, 16#29};
+dec_huffman_lookup(16#cd, 16#7) -> {ok, 16#ff, 16#38};
+dec_huffman_lookup(16#cd, 16#8) -> {more, 16#cb, 16#02};
+dec_huffman_lookup(16#cd, 16#9) -> {more, 16#cb, 16#09};
+dec_huffman_lookup(16#cd, 16#a) -> {more, 16#cb, 16#17};
+dec_huffman_lookup(16#cd, 16#b) -> {ok, 16#cb, 16#28};
+dec_huffman_lookup(16#cd, 16#c) -> {more, 16#cc, 16#02};
+dec_huffman_lookup(16#cd, 16#d) -> {more, 16#cc, 16#09};
+dec_huffman_lookup(16#cd, 16#e) -> {more, 16#cc, 16#17};
+dec_huffman_lookup(16#cd, 16#f) -> {ok, 16#cc, 16#28};
+dec_huffman_lookup(16#ce, 16#0) -> {more, 16#cb, 16#03};
+dec_huffman_lookup(16#ce, 16#1) -> {more, 16#cb, 16#06};
+dec_huffman_lookup(16#ce, 16#2) -> {more, 16#cb, 16#0a};
+dec_huffman_lookup(16#ce, 16#3) -> {more, 16#cb, 16#0f};
+dec_huffman_lookup(16#ce, 16#4) -> {more, 16#cb, 16#18};
+dec_huffman_lookup(16#ce, 16#5) -> {more, 16#cb, 16#1f};
+dec_huffman_lookup(16#ce, 16#6) -> {more, 16#cb, 16#29};
+dec_huffman_lookup(16#ce, 16#7) -> {ok, 16#cb, 16#38};
+dec_huffman_lookup(16#ce, 16#8) -> {more, 16#cc, 16#03};
+dec_huffman_lookup(16#ce, 16#9) -> {more, 16#cc, 16#06};
+dec_huffman_lookup(16#ce, 16#a) -> {more, 16#cc, 16#0a};
+dec_huffman_lookup(16#ce, 16#b) -> {more, 16#cc, 16#0f};
+dec_huffman_lookup(16#ce, 16#c) -> {more, 16#cc, 16#18};
+dec_huffman_lookup(16#ce, 16#d) -> {more, 16#cc, 16#1f};
+dec_huffman_lookup(16#ce, 16#e) -> {more, 16#cc, 16#29};
+dec_huffman_lookup(16#ce, 16#f) -> {ok, 16#cc, 16#38};
+dec_huffman_lookup(16#cf, 16#0) -> {more, undefined, 16#d3};
+dec_huffman_lookup(16#cf, 16#1) -> {more, undefined, 16#d4};
+dec_huffman_lookup(16#cf, 16#2) -> {more, undefined, 16#d6};
+dec_huffman_lookup(16#cf, 16#3) -> {more, undefined, 16#d7};
+dec_huffman_lookup(16#cf, 16#4) -> {more, undefined, 16#da};
+dec_huffman_lookup(16#cf, 16#5) -> {more, undefined, 16#db};
+dec_huffman_lookup(16#cf, 16#6) -> {more, undefined, 16#dd};
+dec_huffman_lookup(16#cf, 16#7) -> {more, undefined, 16#de};
+dec_huffman_lookup(16#cf, 16#8) -> {more, undefined, 16#e2};
+dec_huffman_lookup(16#cf, 16#9) -> {more, undefined, 16#e4};
+dec_huffman_lookup(16#cf, 16#a) -> {more, undefined, 16#e8};
+dec_huffman_lookup(16#cf, 16#b) -> {more, undefined, 16#eb};
+dec_huffman_lookup(16#cf, 16#c) -> {more, undefined, 16#f0};
+dec_huffman_lookup(16#cf, 16#d) -> {more, undefined, 16#f3};
+dec_huffman_lookup(16#cf, 16#e) -> {more, undefined, 16#f7};
+dec_huffman_lookup(16#cf, 16#f) -> {ok, undefined, 16#fa};
+dec_huffman_lookup(16#d0, 16#0) -> {ok, 16#d3, 16#00};
+dec_huffman_lookup(16#d0, 16#1) -> {ok, 16#d4, 16#00};
+dec_huffman_lookup(16#d0, 16#2) -> {ok, 16#d6, 16#00};
+dec_huffman_lookup(16#d0, 16#3) -> {ok, 16#dd, 16#00};
+dec_huffman_lookup(16#d0, 16#4) -> {ok, 16#de, 16#00};
+dec_huffman_lookup(16#d0, 16#5) -> {ok, 16#df, 16#00};
+dec_huffman_lookup(16#d0, 16#6) -> {ok, 16#f1, 16#00};
+dec_huffman_lookup(16#d0, 16#7) -> {ok, 16#f4, 16#00};
+dec_huffman_lookup(16#d0, 16#8) -> {ok, 16#f5, 16#00};
+dec_huffman_lookup(16#d0, 16#9) -> {ok, 16#f6, 16#00};
+dec_huffman_lookup(16#d0, 16#a) -> {ok, 16#f7, 16#00};
+dec_huffman_lookup(16#d0, 16#b) -> {ok, 16#f8, 16#00};
+dec_huffman_lookup(16#d0, 16#c) -> {ok, 16#fa, 16#00};
+dec_huffman_lookup(16#d0, 16#d) -> {ok, 16#fb, 16#00};
+dec_huffman_lookup(16#d0, 16#e) -> {ok, 16#fc, 16#00};
+dec_huffman_lookup(16#d0, 16#f) -> {ok, 16#fd, 16#00};
+dec_huffman_lookup(16#d1, 16#0) -> {more, 16#d3, 16#01};
+dec_huffman_lookup(16#d1, 16#1) -> {ok, 16#d3, 16#16};
+dec_huffman_lookup(16#d1, 16#2) -> {more, 16#d4, 16#01};
+dec_huffman_lookup(16#d1, 16#3) -> {ok, 16#d4, 16#16};
+dec_huffman_lookup(16#d1, 16#4) -> {more, 16#d6, 16#01};
+dec_huffman_lookup(16#d1, 16#5) -> {ok, 16#d6, 16#16};
+dec_huffman_lookup(16#d1, 16#6) -> {more, 16#dd, 16#01};
+dec_huffman_lookup(16#d1, 16#7) -> {ok, 16#dd, 16#16};
+dec_huffman_lookup(16#d1, 16#8) -> {more, 16#de, 16#01};
+dec_huffman_lookup(16#d1, 16#9) -> {ok, 16#de, 16#16};
+dec_huffman_lookup(16#d1, 16#a) -> {more, 16#df, 16#01};
+dec_huffman_lookup(16#d1, 16#b) -> {ok, 16#df, 16#16};
+dec_huffman_lookup(16#d1, 16#c) -> {more, 16#f1, 16#01};
+dec_huffman_lookup(16#d1, 16#d) -> {ok, 16#f1, 16#16};
+dec_huffman_lookup(16#d1, 16#e) -> {more, 16#f4, 16#01};
+dec_huffman_lookup(16#d1, 16#f) -> {ok, 16#f4, 16#16};
+dec_huffman_lookup(16#d2, 16#0) -> {more, 16#d3, 16#02};
+dec_huffman_lookup(16#d2, 16#1) -> {more, 16#d3, 16#09};
+dec_huffman_lookup(16#d2, 16#2) -> {more, 16#d3, 16#17};
+dec_huffman_lookup(16#d2, 16#3) -> {ok, 16#d3, 16#28};
+dec_huffman_lookup(16#d2, 16#4) -> {more, 16#d4, 16#02};
+dec_huffman_lookup(16#d2, 16#5) -> {more, 16#d4, 16#09};
+dec_huffman_lookup(16#d2, 16#6) -> {more, 16#d4, 16#17};
+dec_huffman_lookup(16#d2, 16#7) -> {ok, 16#d4, 16#28};
+dec_huffman_lookup(16#d2, 16#8) -> {more, 16#d6, 16#02};
+dec_huffman_lookup(16#d2, 16#9) -> {more, 16#d6, 16#09};
+dec_huffman_lookup(16#d2, 16#a) -> {more, 16#d6, 16#17};
+dec_huffman_lookup(16#d2, 16#b) -> {ok, 16#d6, 16#28};
+dec_huffman_lookup(16#d2, 16#c) -> {more, 16#dd, 16#02};
+dec_huffman_lookup(16#d2, 16#d) -> {more, 16#dd, 16#09};
+dec_huffman_lookup(16#d2, 16#e) -> {more, 16#dd, 16#17};
+dec_huffman_lookup(16#d2, 16#f) -> {ok, 16#dd, 16#28};
+dec_huffman_lookup(16#d3, 16#0) -> {more, 16#d3, 16#03};
+dec_huffman_lookup(16#d3, 16#1) -> {more, 16#d3, 16#06};
+dec_huffman_lookup(16#d3, 16#2) -> {more, 16#d3, 16#0a};
+dec_huffman_lookup(16#d3, 16#3) -> {more, 16#d3, 16#0f};
+dec_huffman_lookup(16#d3, 16#4) -> {more, 16#d3, 16#18};
+dec_huffman_lookup(16#d3, 16#5) -> {more, 16#d3, 16#1f};
+dec_huffman_lookup(16#d3, 16#6) -> {more, 16#d3, 16#29};
+dec_huffman_lookup(16#d3, 16#7) -> {ok, 16#d3, 16#38};
+dec_huffman_lookup(16#d3, 16#8) -> {more, 16#d4, 16#03};
+dec_huffman_lookup(16#d3, 16#9) -> {more, 16#d4, 16#06};
+dec_huffman_lookup(16#d3, 16#a) -> {more, 16#d4, 16#0a};
+dec_huffman_lookup(16#d3, 16#b) -> {more, 16#d4, 16#0f};
+dec_huffman_lookup(16#d3, 16#c) -> {more, 16#d4, 16#18};
+dec_huffman_lookup(16#d3, 16#d) -> {more, 16#d4, 16#1f};
+dec_huffman_lookup(16#d3, 16#e) -> {more, 16#d4, 16#29};
+dec_huffman_lookup(16#d3, 16#f) -> {ok, 16#d4, 16#38};
+dec_huffman_lookup(16#d4, 16#0) -> {more, 16#d6, 16#03};
+dec_huffman_lookup(16#d4, 16#1) -> {more, 16#d6, 16#06};
+dec_huffman_lookup(16#d4, 16#2) -> {more, 16#d6, 16#0a};
+dec_huffman_lookup(16#d4, 16#3) -> {more, 16#d6, 16#0f};
+dec_huffman_lookup(16#d4, 16#4) -> {more, 16#d6, 16#18};
+dec_huffman_lookup(16#d4, 16#5) -> {more, 16#d6, 16#1f};
+dec_huffman_lookup(16#d4, 16#6) -> {more, 16#d6, 16#29};
+dec_huffman_lookup(16#d4, 16#7) -> {ok, 16#d6, 16#38};
+dec_huffman_lookup(16#d4, 16#8) -> {more, 16#dd, 16#03};
+dec_huffman_lookup(16#d4, 16#9) -> {more, 16#dd, 16#06};
+dec_huffman_lookup(16#d4, 16#a) -> {more, 16#dd, 16#0a};
+dec_huffman_lookup(16#d4, 16#b) -> {more, 16#dd, 16#0f};
+dec_huffman_lookup(16#d4, 16#c) -> {more, 16#dd, 16#18};
+dec_huffman_lookup(16#d4, 16#d) -> {more, 16#dd, 16#1f};
+dec_huffman_lookup(16#d4, 16#e) -> {more, 16#dd, 16#29};
+dec_huffman_lookup(16#d4, 16#f) -> {ok, 16#dd, 16#38};
+dec_huffman_lookup(16#d5, 16#0) -> {more, 16#de, 16#02};
+dec_huffman_lookup(16#d5, 16#1) -> {more, 16#de, 16#09};
+dec_huffman_lookup(16#d5, 16#2) -> {more, 16#de, 16#17};
+dec_huffman_lookup(16#d5, 16#3) -> {ok, 16#de, 16#28};
+dec_huffman_lookup(16#d5, 16#4) -> {more, 16#df, 16#02};
+dec_huffman_lookup(16#d5, 16#5) -> {more, 16#df, 16#09};
+dec_huffman_lookup(16#d5, 16#6) -> {more, 16#df, 16#17};
+dec_huffman_lookup(16#d5, 16#7) -> {ok, 16#df, 16#28};
+dec_huffman_lookup(16#d5, 16#8) -> {more, 16#f1, 16#02};
+dec_huffman_lookup(16#d5, 16#9) -> {more, 16#f1, 16#09};
+dec_huffman_lookup(16#d5, 16#a) -> {more, 16#f1, 16#17};
+dec_huffman_lookup(16#d5, 16#b) -> {ok, 16#f1, 16#28};
+dec_huffman_lookup(16#d5, 16#c) -> {more, 16#f4, 16#02};
+dec_huffman_lookup(16#d5, 16#d) -> {more, 16#f4, 16#09};
+dec_huffman_lookup(16#d5, 16#e) -> {more, 16#f4, 16#17};
+dec_huffman_lookup(16#d5, 16#f) -> {ok, 16#f4, 16#28};
+dec_huffman_lookup(16#d6, 16#0) -> {more, 16#de, 16#03};
+dec_huffman_lookup(16#d6, 16#1) -> {more, 16#de, 16#06};
+dec_huffman_lookup(16#d6, 16#2) -> {more, 16#de, 16#0a};
+dec_huffman_lookup(16#d6, 16#3) -> {more, 16#de, 16#0f};
+dec_huffman_lookup(16#d6, 16#4) -> {more, 16#de, 16#18};
+dec_huffman_lookup(16#d6, 16#5) -> {more, 16#de, 16#1f};
+dec_huffman_lookup(16#d6, 16#6) -> {more, 16#de, 16#29};
+dec_huffman_lookup(16#d6, 16#7) -> {ok, 16#de, 16#38};
+dec_huffman_lookup(16#d6, 16#8) -> {more, 16#df, 16#03};
+dec_huffman_lookup(16#d6, 16#9) -> {more, 16#df, 16#06};
+dec_huffman_lookup(16#d6, 16#a) -> {more, 16#df, 16#0a};
+dec_huffman_lookup(16#d6, 16#b) -> {more, 16#df, 16#0f};
+dec_huffman_lookup(16#d6, 16#c) -> {more, 16#df, 16#18};
+dec_huffman_lookup(16#d6, 16#d) -> {more, 16#df, 16#1f};
+dec_huffman_lookup(16#d6, 16#e) -> {more, 16#df, 16#29};
+dec_huffman_lookup(16#d6, 16#f) -> {ok, 16#df, 16#38};
+dec_huffman_lookup(16#d7, 16#0) -> {more, 16#f1, 16#03};
+dec_huffman_lookup(16#d7, 16#1) -> {more, 16#f1, 16#06};
+dec_huffman_lookup(16#d7, 16#2) -> {more, 16#f1, 16#0a};
+dec_huffman_lookup(16#d7, 16#3) -> {more, 16#f1, 16#0f};
+dec_huffman_lookup(16#d7, 16#4) -> {more, 16#f1, 16#18};
+dec_huffman_lookup(16#d7, 16#5) -> {more, 16#f1, 16#1f};
+dec_huffman_lookup(16#d7, 16#6) -> {more, 16#f1, 16#29};
+dec_huffman_lookup(16#d7, 16#7) -> {ok, 16#f1, 16#38};
+dec_huffman_lookup(16#d7, 16#8) -> {more, 16#f4, 16#03};
+dec_huffman_lookup(16#d7, 16#9) -> {more, 16#f4, 16#06};
+dec_huffman_lookup(16#d7, 16#a) -> {more, 16#f4, 16#0a};
+dec_huffman_lookup(16#d7, 16#b) -> {more, 16#f4, 16#0f};
+dec_huffman_lookup(16#d7, 16#c) -> {more, 16#f4, 16#18};
+dec_huffman_lookup(16#d7, 16#d) -> {more, 16#f4, 16#1f};
+dec_huffman_lookup(16#d7, 16#e) -> {more, 16#f4, 16#29};
+dec_huffman_lookup(16#d7, 16#f) -> {ok, 16#f4, 16#38};
+dec_huffman_lookup(16#d8, 16#0) -> {more, 16#f5, 16#01};
+dec_huffman_lookup(16#d8, 16#1) -> {ok, 16#f5, 16#16};
+dec_huffman_lookup(16#d8, 16#2) -> {more, 16#f6, 16#01};
+dec_huffman_lookup(16#d8, 16#3) -> {ok, 16#f6, 16#16};
+dec_huffman_lookup(16#d8, 16#4) -> {more, 16#f7, 16#01};
+dec_huffman_lookup(16#d8, 16#5) -> {ok, 16#f7, 16#16};
+dec_huffman_lookup(16#d8, 16#6) -> {more, 16#f8, 16#01};
+dec_huffman_lookup(16#d8, 16#7) -> {ok, 16#f8, 16#16};
+dec_huffman_lookup(16#d8, 16#8) -> {more, 16#fa, 16#01};
+dec_huffman_lookup(16#d8, 16#9) -> {ok, 16#fa, 16#16};
+dec_huffman_lookup(16#d8, 16#a) -> {more, 16#fb, 16#01};
+dec_huffman_lookup(16#d8, 16#b) -> {ok, 16#fb, 16#16};
+dec_huffman_lookup(16#d8, 16#c) -> {more, 16#fc, 16#01};
+dec_huffman_lookup(16#d8, 16#d) -> {ok, 16#fc, 16#16};
+dec_huffman_lookup(16#d8, 16#e) -> {more, 16#fd, 16#01};
+dec_huffman_lookup(16#d8, 16#f) -> {ok, 16#fd, 16#16};
+dec_huffman_lookup(16#d9, 16#0) -> {more, 16#f5, 16#02};
+dec_huffman_lookup(16#d9, 16#1) -> {more, 16#f5, 16#09};
+dec_huffman_lookup(16#d9, 16#2) -> {more, 16#f5, 16#17};
+dec_huffman_lookup(16#d9, 16#3) -> {ok, 16#f5, 16#28};
+dec_huffman_lookup(16#d9, 16#4) -> {more, 16#f6, 16#02};
+dec_huffman_lookup(16#d9, 16#5) -> {more, 16#f6, 16#09};
+dec_huffman_lookup(16#d9, 16#6) -> {more, 16#f6, 16#17};
+dec_huffman_lookup(16#d9, 16#7) -> {ok, 16#f6, 16#28};
+dec_huffman_lookup(16#d9, 16#8) -> {more, 16#f7, 16#02};
+dec_huffman_lookup(16#d9, 16#9) -> {more, 16#f7, 16#09};
+dec_huffman_lookup(16#d9, 16#a) -> {more, 16#f7, 16#17};
+dec_huffman_lookup(16#d9, 16#b) -> {ok, 16#f7, 16#28};
+dec_huffman_lookup(16#d9, 16#c) -> {more, 16#f8, 16#02};
+dec_huffman_lookup(16#d9, 16#d) -> {more, 16#f8, 16#09};
+dec_huffman_lookup(16#d9, 16#e) -> {more, 16#f8, 16#17};
+dec_huffman_lookup(16#d9, 16#f) -> {ok, 16#f8, 16#28};
+dec_huffman_lookup(16#da, 16#0) -> {more, 16#f5, 16#03};
+dec_huffman_lookup(16#da, 16#1) -> {more, 16#f5, 16#06};
+dec_huffman_lookup(16#da, 16#2) -> {more, 16#f5, 16#0a};
+dec_huffman_lookup(16#da, 16#3) -> {more, 16#f5, 16#0f};
+dec_huffman_lookup(16#da, 16#4) -> {more, 16#f5, 16#18};
+dec_huffman_lookup(16#da, 16#5) -> {more, 16#f5, 16#1f};
+dec_huffman_lookup(16#da, 16#6) -> {more, 16#f5, 16#29};
+dec_huffman_lookup(16#da, 16#7) -> {ok, 16#f5, 16#38};
+dec_huffman_lookup(16#da, 16#8) -> {more, 16#f6, 16#03};
+dec_huffman_lookup(16#da, 16#9) -> {more, 16#f6, 16#06};
+dec_huffman_lookup(16#da, 16#a) -> {more, 16#f6, 16#0a};
+dec_huffman_lookup(16#da, 16#b) -> {more, 16#f6, 16#0f};
+dec_huffman_lookup(16#da, 16#c) -> {more, 16#f6, 16#18};
+dec_huffman_lookup(16#da, 16#d) -> {more, 16#f6, 16#1f};
+dec_huffman_lookup(16#da, 16#e) -> {more, 16#f6, 16#29};
+dec_huffman_lookup(16#da, 16#f) -> {ok, 16#f6, 16#38};
+dec_huffman_lookup(16#db, 16#0) -> {more, 16#f7, 16#03};
+dec_huffman_lookup(16#db, 16#1) -> {more, 16#f7, 16#06};
+dec_huffman_lookup(16#db, 16#2) -> {more, 16#f7, 16#0a};
+dec_huffman_lookup(16#db, 16#3) -> {more, 16#f7, 16#0f};
+dec_huffman_lookup(16#db, 16#4) -> {more, 16#f7, 16#18};
+dec_huffman_lookup(16#db, 16#5) -> {more, 16#f7, 16#1f};
+dec_huffman_lookup(16#db, 16#6) -> {more, 16#f7, 16#29};
+dec_huffman_lookup(16#db, 16#7) -> {ok, 16#f7, 16#38};
+dec_huffman_lookup(16#db, 16#8) -> {more, 16#f8, 16#03};
+dec_huffman_lookup(16#db, 16#9) -> {more, 16#f8, 16#06};
+dec_huffman_lookup(16#db, 16#a) -> {more, 16#f8, 16#0a};
+dec_huffman_lookup(16#db, 16#b) -> {more, 16#f8, 16#0f};
+dec_huffman_lookup(16#db, 16#c) -> {more, 16#f8, 16#18};
+dec_huffman_lookup(16#db, 16#d) -> {more, 16#f8, 16#1f};
+dec_huffman_lookup(16#db, 16#e) -> {more, 16#f8, 16#29};
+dec_huffman_lookup(16#db, 16#f) -> {ok, 16#f8, 16#38};
+dec_huffman_lookup(16#dc, 16#0) -> {more, 16#fa, 16#02};
+dec_huffman_lookup(16#dc, 16#1) -> {more, 16#fa, 16#09};
+dec_huffman_lookup(16#dc, 16#2) -> {more, 16#fa, 16#17};
+dec_huffman_lookup(16#dc, 16#3) -> {ok, 16#fa, 16#28};
+dec_huffman_lookup(16#dc, 16#4) -> {more, 16#fb, 16#02};
+dec_huffman_lookup(16#dc, 16#5) -> {more, 16#fb, 16#09};
+dec_huffman_lookup(16#dc, 16#6) -> {more, 16#fb, 16#17};
+dec_huffman_lookup(16#dc, 16#7) -> {ok, 16#fb, 16#28};
+dec_huffman_lookup(16#dc, 16#8) -> {more, 16#fc, 16#02};
+dec_huffman_lookup(16#dc, 16#9) -> {more, 16#fc, 16#09};
+dec_huffman_lookup(16#dc, 16#a) -> {more, 16#fc, 16#17};
+dec_huffman_lookup(16#dc, 16#b) -> {ok, 16#fc, 16#28};
+dec_huffman_lookup(16#dc, 16#c) -> {more, 16#fd, 16#02};
+dec_huffman_lookup(16#dc, 16#d) -> {more, 16#fd, 16#09};
+dec_huffman_lookup(16#dc, 16#e) -> {more, 16#fd, 16#17};
+dec_huffman_lookup(16#dc, 16#f) -> {ok, 16#fd, 16#28};
+dec_huffman_lookup(16#dd, 16#0) -> {more, 16#fa, 16#03};
+dec_huffman_lookup(16#dd, 16#1) -> {more, 16#fa, 16#06};
+dec_huffman_lookup(16#dd, 16#2) -> {more, 16#fa, 16#0a};
+dec_huffman_lookup(16#dd, 16#3) -> {more, 16#fa, 16#0f};
+dec_huffman_lookup(16#dd, 16#4) -> {more, 16#fa, 16#18};
+dec_huffman_lookup(16#dd, 16#5) -> {more, 16#fa, 16#1f};
+dec_huffman_lookup(16#dd, 16#6) -> {more, 16#fa, 16#29};
+dec_huffman_lookup(16#dd, 16#7) -> {ok, 16#fa, 16#38};
+dec_huffman_lookup(16#dd, 16#8) -> {more, 16#fb, 16#03};
+dec_huffman_lookup(16#dd, 16#9) -> {more, 16#fb, 16#06};
+dec_huffman_lookup(16#dd, 16#a) -> {more, 16#fb, 16#0a};
+dec_huffman_lookup(16#dd, 16#b) -> {more, 16#fb, 16#0f};
+dec_huffman_lookup(16#dd, 16#c) -> {more, 16#fb, 16#18};
+dec_huffman_lookup(16#dd, 16#d) -> {more, 16#fb, 16#1f};
+dec_huffman_lookup(16#dd, 16#e) -> {more, 16#fb, 16#29};
+dec_huffman_lookup(16#dd, 16#f) -> {ok, 16#fb, 16#38};
+dec_huffman_lookup(16#de, 16#0) -> {more, 16#fc, 16#03};
+dec_huffman_lookup(16#de, 16#1) -> {more, 16#fc, 16#06};
+dec_huffman_lookup(16#de, 16#2) -> {more, 16#fc, 16#0a};
+dec_huffman_lookup(16#de, 16#3) -> {more, 16#fc, 16#0f};
+dec_huffman_lookup(16#de, 16#4) -> {more, 16#fc, 16#18};
+dec_huffman_lookup(16#de, 16#5) -> {more, 16#fc, 16#1f};
+dec_huffman_lookup(16#de, 16#6) -> {more, 16#fc, 16#29};
+dec_huffman_lookup(16#de, 16#7) -> {ok, 16#fc, 16#38};
+dec_huffman_lookup(16#de, 16#8) -> {more, 16#fd, 16#03};
+dec_huffman_lookup(16#de, 16#9) -> {more, 16#fd, 16#06};
+dec_huffman_lookup(16#de, 16#a) -> {more, 16#fd, 16#0a};
+dec_huffman_lookup(16#de, 16#b) -> {more, 16#fd, 16#0f};
+dec_huffman_lookup(16#de, 16#c) -> {more, 16#fd, 16#18};
+dec_huffman_lookup(16#de, 16#d) -> {more, 16#fd, 16#1f};
+dec_huffman_lookup(16#de, 16#e) -> {more, 16#fd, 16#29};
+dec_huffman_lookup(16#de, 16#f) -> {ok, 16#fd, 16#38};
+dec_huffman_lookup(16#df, 16#0) -> {ok, 16#fe, 16#00};
+dec_huffman_lookup(16#df, 16#1) -> {more, undefined, 16#e3};
+dec_huffman_lookup(16#df, 16#2) -> {more, undefined, 16#e5};
+dec_huffman_lookup(16#df, 16#3) -> {more, undefined, 16#e6};
+dec_huffman_lookup(16#df, 16#4) -> {more, undefined, 16#e9};
+dec_huffman_lookup(16#df, 16#5) -> {more, undefined, 16#ea};
+dec_huffman_lookup(16#df, 16#6) -> {more, undefined, 16#ec};
+dec_huffman_lookup(16#df, 16#7) -> {more, undefined, 16#ed};
+dec_huffman_lookup(16#df, 16#8) -> {more, undefined, 16#f1};
+dec_huffman_lookup(16#df, 16#9) -> {more, undefined, 16#f2};
+dec_huffman_lookup(16#df, 16#a) -> {more, undefined, 16#f4};
+dec_huffman_lookup(16#df, 16#b) -> {more, undefined, 16#f5};
+dec_huffman_lookup(16#df, 16#c) -> {more, undefined, 16#f8};
+dec_huffman_lookup(16#df, 16#d) -> {more, undefined, 16#f9};
+dec_huffman_lookup(16#df, 16#e) -> {more, undefined, 16#fb};
+dec_huffman_lookup(16#df, 16#f) -> {ok, undefined, 16#fc};
+dec_huffman_lookup(16#e0, 16#0) -> {more, 16#fe, 16#01};
+dec_huffman_lookup(16#e0, 16#1) -> {ok, 16#fe, 16#16};
+dec_huffman_lookup(16#e0, 16#2) -> {ok, 16#02, 16#00};
+dec_huffman_lookup(16#e0, 16#3) -> {ok, 16#03, 16#00};
+dec_huffman_lookup(16#e0, 16#4) -> {ok, 16#04, 16#00};
+dec_huffman_lookup(16#e0, 16#5) -> {ok, 16#05, 16#00};
+dec_huffman_lookup(16#e0, 16#6) -> {ok, 16#06, 16#00};
+dec_huffman_lookup(16#e0, 16#7) -> {ok, 16#07, 16#00};
+dec_huffman_lookup(16#e0, 16#8) -> {ok, 16#08, 16#00};
+dec_huffman_lookup(16#e0, 16#9) -> {ok, 16#0b, 16#00};
+dec_huffman_lookup(16#e0, 16#a) -> {ok, 16#0c, 16#00};
+dec_huffman_lookup(16#e0, 16#b) -> {ok, 16#0e, 16#00};
+dec_huffman_lookup(16#e0, 16#c) -> {ok, 16#0f, 16#00};
+dec_huffman_lookup(16#e0, 16#d) -> {ok, 16#10, 16#00};
+dec_huffman_lookup(16#e0, 16#e) -> {ok, 16#11, 16#00};
+dec_huffman_lookup(16#e0, 16#f) -> {ok, 16#12, 16#00};
+dec_huffman_lookup(16#e1, 16#0) -> {more, 16#fe, 16#02};
+dec_huffman_lookup(16#e1, 16#1) -> {more, 16#fe, 16#09};
+dec_huffman_lookup(16#e1, 16#2) -> {more, 16#fe, 16#17};
+dec_huffman_lookup(16#e1, 16#3) -> {ok, 16#fe, 16#28};
+dec_huffman_lookup(16#e1, 16#4) -> {more, 16#02, 16#01};
+dec_huffman_lookup(16#e1, 16#5) -> {ok, 16#02, 16#16};
+dec_huffman_lookup(16#e1, 16#6) -> {more, 16#03, 16#01};
+dec_huffman_lookup(16#e1, 16#7) -> {ok, 16#03, 16#16};
+dec_huffman_lookup(16#e1, 16#8) -> {more, 16#04, 16#01};
+dec_huffman_lookup(16#e1, 16#9) -> {ok, 16#04, 16#16};
+dec_huffman_lookup(16#e1, 16#a) -> {more, 16#05, 16#01};
+dec_huffman_lookup(16#e1, 16#b) -> {ok, 16#05, 16#16};
+dec_huffman_lookup(16#e1, 16#c) -> {more, 16#06, 16#01};
+dec_huffman_lookup(16#e1, 16#d) -> {ok, 16#06, 16#16};
+dec_huffman_lookup(16#e1, 16#e) -> {more, 16#07, 16#01};
+dec_huffman_lookup(16#e1, 16#f) -> {ok, 16#07, 16#16};
+dec_huffman_lookup(16#e2, 16#0) -> {more, 16#fe, 16#03};
+dec_huffman_lookup(16#e2, 16#1) -> {more, 16#fe, 16#06};
+dec_huffman_lookup(16#e2, 16#2) -> {more, 16#fe, 16#0a};
+dec_huffman_lookup(16#e2, 16#3) -> {more, 16#fe, 16#0f};
+dec_huffman_lookup(16#e2, 16#4) -> {more, 16#fe, 16#18};
+dec_huffman_lookup(16#e2, 16#5) -> {more, 16#fe, 16#1f};
+dec_huffman_lookup(16#e2, 16#6) -> {more, 16#fe, 16#29};
+dec_huffman_lookup(16#e2, 16#7) -> {ok, 16#fe, 16#38};
+dec_huffman_lookup(16#e2, 16#8) -> {more, 16#02, 16#02};
+dec_huffman_lookup(16#e2, 16#9) -> {more, 16#02, 16#09};
+dec_huffman_lookup(16#e2, 16#a) -> {more, 16#02, 16#17};
+dec_huffman_lookup(16#e2, 16#b) -> {ok, 16#02, 16#28};
+dec_huffman_lookup(16#e2, 16#c) -> {more, 16#03, 16#02};
+dec_huffman_lookup(16#e2, 16#d) -> {more, 16#03, 16#09};
+dec_huffman_lookup(16#e2, 16#e) -> {more, 16#03, 16#17};
+dec_huffman_lookup(16#e2, 16#f) -> {ok, 16#03, 16#28};
+dec_huffman_lookup(16#e3, 16#0) -> {more, 16#02, 16#03};
+dec_huffman_lookup(16#e3, 16#1) -> {more, 16#02, 16#06};
+dec_huffman_lookup(16#e3, 16#2) -> {more, 16#02, 16#0a};
+dec_huffman_lookup(16#e3, 16#3) -> {more, 16#02, 16#0f};
+dec_huffman_lookup(16#e3, 16#4) -> {more, 16#02, 16#18};
+dec_huffman_lookup(16#e3, 16#5) -> {more, 16#02, 16#1f};
+dec_huffman_lookup(16#e3, 16#6) -> {more, 16#02, 16#29};
+dec_huffman_lookup(16#e3, 16#7) -> {ok, 16#02, 16#38};
+dec_huffman_lookup(16#e3, 16#8) -> {more, 16#03, 16#03};
+dec_huffman_lookup(16#e3, 16#9) -> {more, 16#03, 16#06};
+dec_huffman_lookup(16#e3, 16#a) -> {more, 16#03, 16#0a};
+dec_huffman_lookup(16#e3, 16#b) -> {more, 16#03, 16#0f};
+dec_huffman_lookup(16#e3, 16#c) -> {more, 16#03, 16#18};
+dec_huffman_lookup(16#e3, 16#d) -> {more, 16#03, 16#1f};
+dec_huffman_lookup(16#e3, 16#e) -> {more, 16#03, 16#29};
+dec_huffman_lookup(16#e3, 16#f) -> {ok, 16#03, 16#38};
+dec_huffman_lookup(16#e4, 16#0) -> {more, 16#04, 16#02};
+dec_huffman_lookup(16#e4, 16#1) -> {more, 16#04, 16#09};
+dec_huffman_lookup(16#e4, 16#2) -> {more, 16#04, 16#17};
+dec_huffman_lookup(16#e4, 16#3) -> {ok, 16#04, 16#28};
+dec_huffman_lookup(16#e4, 16#4) -> {more, 16#05, 16#02};
+dec_huffman_lookup(16#e4, 16#5) -> {more, 16#05, 16#09};
+dec_huffman_lookup(16#e4, 16#6) -> {more, 16#05, 16#17};
+dec_huffman_lookup(16#e4, 16#7) -> {ok, 16#05, 16#28};
+dec_huffman_lookup(16#e4, 16#8) -> {more, 16#06, 16#02};
+dec_huffman_lookup(16#e4, 16#9) -> {more, 16#06, 16#09};
+dec_huffman_lookup(16#e4, 16#a) -> {more, 16#06, 16#17};
+dec_huffman_lookup(16#e4, 16#b) -> {ok, 16#06, 16#28};
+dec_huffman_lookup(16#e4, 16#c) -> {more, 16#07, 16#02};
+dec_huffman_lookup(16#e4, 16#d) -> {more, 16#07, 16#09};
+dec_huffman_lookup(16#e4, 16#e) -> {more, 16#07, 16#17};
+dec_huffman_lookup(16#e4, 16#f) -> {ok, 16#07, 16#28};
+dec_huffman_lookup(16#e5, 16#0) -> {more, 16#04, 16#03};
+dec_huffman_lookup(16#e5, 16#1) -> {more, 16#04, 16#06};
+dec_huffman_lookup(16#e5, 16#2) -> {more, 16#04, 16#0a};
+dec_huffman_lookup(16#e5, 16#3) -> {more, 16#04, 16#0f};
+dec_huffman_lookup(16#e5, 16#4) -> {more, 16#04, 16#18};
+dec_huffman_lookup(16#e5, 16#5) -> {more, 16#04, 16#1f};
+dec_huffman_lookup(16#e5, 16#6) -> {more, 16#04, 16#29};
+dec_huffman_lookup(16#e5, 16#7) -> {ok, 16#04, 16#38};
+dec_huffman_lookup(16#e5, 16#8) -> {more, 16#05, 16#03};
+dec_huffman_lookup(16#e5, 16#9) -> {more, 16#05, 16#06};
+dec_huffman_lookup(16#e5, 16#a) -> {more, 16#05, 16#0a};
+dec_huffman_lookup(16#e5, 16#b) -> {more, 16#05, 16#0f};
+dec_huffman_lookup(16#e5, 16#c) -> {more, 16#05, 16#18};
+dec_huffman_lookup(16#e5, 16#d) -> {more, 16#05, 16#1f};
+dec_huffman_lookup(16#e5, 16#e) -> {more, 16#05, 16#29};
+dec_huffman_lookup(16#e5, 16#f) -> {ok, 16#05, 16#38};
+dec_huffman_lookup(16#e6, 16#0) -> {more, 16#06, 16#03};
+dec_huffman_lookup(16#e6, 16#1) -> {more, 16#06, 16#06};
+dec_huffman_lookup(16#e6, 16#2) -> {more, 16#06, 16#0a};
+dec_huffman_lookup(16#e6, 16#3) -> {more, 16#06, 16#0f};
+dec_huffman_lookup(16#e6, 16#4) -> {more, 16#06, 16#18};
+dec_huffman_lookup(16#e6, 16#5) -> {more, 16#06, 16#1f};
+dec_huffman_lookup(16#e6, 16#6) -> {more, 16#06, 16#29};
+dec_huffman_lookup(16#e6, 16#7) -> {ok, 16#06, 16#38};
+dec_huffman_lookup(16#e6, 16#8) -> {more, 16#07, 16#03};
+dec_huffman_lookup(16#e6, 16#9) -> {more, 16#07, 16#06};
+dec_huffman_lookup(16#e6, 16#a) -> {more, 16#07, 16#0a};
+dec_huffman_lookup(16#e6, 16#b) -> {more, 16#07, 16#0f};
+dec_huffman_lookup(16#e6, 16#c) -> {more, 16#07, 16#18};
+dec_huffman_lookup(16#e6, 16#d) -> {more, 16#07, 16#1f};
+dec_huffman_lookup(16#e6, 16#e) -> {more, 16#07, 16#29};
+dec_huffman_lookup(16#e6, 16#f) -> {ok, 16#07, 16#38};
+dec_huffman_lookup(16#e7, 16#0) -> {more, 16#08, 16#01};
+dec_huffman_lookup(16#e7, 16#1) -> {ok, 16#08, 16#16};
+dec_huffman_lookup(16#e7, 16#2) -> {more, 16#0b, 16#01};
+dec_huffman_lookup(16#e7, 16#3) -> {ok, 16#0b, 16#16};
+dec_huffman_lookup(16#e7, 16#4) -> {more, 16#0c, 16#01};
+dec_huffman_lookup(16#e7, 16#5) -> {ok, 16#0c, 16#16};
+dec_huffman_lookup(16#e7, 16#6) -> {more, 16#0e, 16#01};
+dec_huffman_lookup(16#e7, 16#7) -> {ok, 16#0e, 16#16};
+dec_huffman_lookup(16#e7, 16#8) -> {more, 16#0f, 16#01};
+dec_huffman_lookup(16#e7, 16#9) -> {ok, 16#0f, 16#16};
+dec_huffman_lookup(16#e7, 16#a) -> {more, 16#10, 16#01};
+dec_huffman_lookup(16#e7, 16#b) -> {ok, 16#10, 16#16};
+dec_huffman_lookup(16#e7, 16#c) -> {more, 16#11, 16#01};
+dec_huffman_lookup(16#e7, 16#d) -> {ok, 16#11, 16#16};
+dec_huffman_lookup(16#e7, 16#e) -> {more, 16#12, 16#01};
+dec_huffman_lookup(16#e7, 16#f) -> {ok, 16#12, 16#16};
+dec_huffman_lookup(16#e8, 16#0) -> {more, 16#08, 16#02};
+dec_huffman_lookup(16#e8, 16#1) -> {more, 16#08, 16#09};
+dec_huffman_lookup(16#e8, 16#2) -> {more, 16#08, 16#17};
+dec_huffman_lookup(16#e8, 16#3) -> {ok, 16#08, 16#28};
+dec_huffman_lookup(16#e8, 16#4) -> {more, 16#0b, 16#02};
+dec_huffman_lookup(16#e8, 16#5) -> {more, 16#0b, 16#09};
+dec_huffman_lookup(16#e8, 16#6) -> {more, 16#0b, 16#17};
+dec_huffman_lookup(16#e8, 16#7) -> {ok, 16#0b, 16#28};
+dec_huffman_lookup(16#e8, 16#8) -> {more, 16#0c, 16#02};
+dec_huffman_lookup(16#e8, 16#9) -> {more, 16#0c, 16#09};
+dec_huffman_lookup(16#e8, 16#a) -> {more, 16#0c, 16#17};
+dec_huffman_lookup(16#e8, 16#b) -> {ok, 16#0c, 16#28};
+dec_huffman_lookup(16#e8, 16#c) -> {more, 16#0e, 16#02};
+dec_huffman_lookup(16#e8, 16#d) -> {more, 16#0e, 16#09};
+dec_huffman_lookup(16#e8, 16#e) -> {more, 16#0e, 16#17};
+dec_huffman_lookup(16#e8, 16#f) -> {ok, 16#0e, 16#28};
+dec_huffman_lookup(16#e9, 16#0) -> {more, 16#08, 16#03};
+dec_huffman_lookup(16#e9, 16#1) -> {more, 16#08, 16#06};
+dec_huffman_lookup(16#e9, 16#2) -> {more, 16#08, 16#0a};
+dec_huffman_lookup(16#e9, 16#3) -> {more, 16#08, 16#0f};
+dec_huffman_lookup(16#e9, 16#4) -> {more, 16#08, 16#18};
+dec_huffman_lookup(16#e9, 16#5) -> {more, 16#08, 16#1f};
+dec_huffman_lookup(16#e9, 16#6) -> {more, 16#08, 16#29};
+dec_huffman_lookup(16#e9, 16#7) -> {ok, 16#08, 16#38};
+dec_huffman_lookup(16#e9, 16#8) -> {more, 16#0b, 16#03};
+dec_huffman_lookup(16#e9, 16#9) -> {more, 16#0b, 16#06};
+dec_huffman_lookup(16#e9, 16#a) -> {more, 16#0b, 16#0a};
+dec_huffman_lookup(16#e9, 16#b) -> {more, 16#0b, 16#0f};
+dec_huffman_lookup(16#e9, 16#c) -> {more, 16#0b, 16#18};
+dec_huffman_lookup(16#e9, 16#d) -> {more, 16#0b, 16#1f};
+dec_huffman_lookup(16#e9, 16#e) -> {more, 16#0b, 16#29};
+dec_huffman_lookup(16#e9, 16#f) -> {ok, 16#0b, 16#38};
+dec_huffman_lookup(16#ea, 16#0) -> {more, 16#0c, 16#03};
+dec_huffman_lookup(16#ea, 16#1) -> {more, 16#0c, 16#06};
+dec_huffman_lookup(16#ea, 16#2) -> {more, 16#0c, 16#0a};
+dec_huffman_lookup(16#ea, 16#3) -> {more, 16#0c, 16#0f};
+dec_huffman_lookup(16#ea, 16#4) -> {more, 16#0c, 16#18};
+dec_huffman_lookup(16#ea, 16#5) -> {more, 16#0c, 16#1f};
+dec_huffman_lookup(16#ea, 16#6) -> {more, 16#0c, 16#29};
+dec_huffman_lookup(16#ea, 16#7) -> {ok, 16#0c, 16#38};
+dec_huffman_lookup(16#ea, 16#8) -> {more, 16#0e, 16#03};
+dec_huffman_lookup(16#ea, 16#9) -> {more, 16#0e, 16#06};
+dec_huffman_lookup(16#ea, 16#a) -> {more, 16#0e, 16#0a};
+dec_huffman_lookup(16#ea, 16#b) -> {more, 16#0e, 16#0f};
+dec_huffman_lookup(16#ea, 16#c) -> {more, 16#0e, 16#18};
+dec_huffman_lookup(16#ea, 16#d) -> {more, 16#0e, 16#1f};
+dec_huffman_lookup(16#ea, 16#e) -> {more, 16#0e, 16#29};
+dec_huffman_lookup(16#ea, 16#f) -> {ok, 16#0e, 16#38};
+dec_huffman_lookup(16#eb, 16#0) -> {more, 16#0f, 16#02};
+dec_huffman_lookup(16#eb, 16#1) -> {more, 16#0f, 16#09};
+dec_huffman_lookup(16#eb, 16#2) -> {more, 16#0f, 16#17};
+dec_huffman_lookup(16#eb, 16#3) -> {ok, 16#0f, 16#28};
+dec_huffman_lookup(16#eb, 16#4) -> {more, 16#10, 16#02};
+dec_huffman_lookup(16#eb, 16#5) -> {more, 16#10, 16#09};
+dec_huffman_lookup(16#eb, 16#6) -> {more, 16#10, 16#17};
+dec_huffman_lookup(16#eb, 16#7) -> {ok, 16#10, 16#28};
+dec_huffman_lookup(16#eb, 16#8) -> {more, 16#11, 16#02};
+dec_huffman_lookup(16#eb, 16#9) -> {more, 16#11, 16#09};
+dec_huffman_lookup(16#eb, 16#a) -> {more, 16#11, 16#17};
+dec_huffman_lookup(16#eb, 16#b) -> {ok, 16#11, 16#28};
+dec_huffman_lookup(16#eb, 16#c) -> {more, 16#12, 16#02};
+dec_huffman_lookup(16#eb, 16#d) -> {more, 16#12, 16#09};
+dec_huffman_lookup(16#eb, 16#e) -> {more, 16#12, 16#17};
+dec_huffman_lookup(16#eb, 16#f) -> {ok, 16#12, 16#28};
+dec_huffman_lookup(16#ec, 16#0) -> {more, 16#0f, 16#03};
+dec_huffman_lookup(16#ec, 16#1) -> {more, 16#0f, 16#06};
+dec_huffman_lookup(16#ec, 16#2) -> {more, 16#0f, 16#0a};
+dec_huffman_lookup(16#ec, 16#3) -> {more, 16#0f, 16#0f};
+dec_huffman_lookup(16#ec, 16#4) -> {more, 16#0f, 16#18};
+dec_huffman_lookup(16#ec, 16#5) -> {more, 16#0f, 16#1f};
+dec_huffman_lookup(16#ec, 16#6) -> {more, 16#0f, 16#29};
+dec_huffman_lookup(16#ec, 16#7) -> {ok, 16#0f, 16#38};
+dec_huffman_lookup(16#ec, 16#8) -> {more, 16#10, 16#03};
+dec_huffman_lookup(16#ec, 16#9) -> {more, 16#10, 16#06};
+dec_huffman_lookup(16#ec, 16#a) -> {more, 16#10, 16#0a};
+dec_huffman_lookup(16#ec, 16#b) -> {more, 16#10, 16#0f};
+dec_huffman_lookup(16#ec, 16#c) -> {more, 16#10, 16#18};
+dec_huffman_lookup(16#ec, 16#d) -> {more, 16#10, 16#1f};
+dec_huffman_lookup(16#ec, 16#e) -> {more, 16#10, 16#29};
+dec_huffman_lookup(16#ec, 16#f) -> {ok, 16#10, 16#38};
+dec_huffman_lookup(16#ed, 16#0) -> {more, 16#11, 16#03};
+dec_huffman_lookup(16#ed, 16#1) -> {more, 16#11, 16#06};
+dec_huffman_lookup(16#ed, 16#2) -> {more, 16#11, 16#0a};
+dec_huffman_lookup(16#ed, 16#3) -> {more, 16#11, 16#0f};
+dec_huffman_lookup(16#ed, 16#4) -> {more, 16#11, 16#18};
+dec_huffman_lookup(16#ed, 16#5) -> {more, 16#11, 16#1f};
+dec_huffman_lookup(16#ed, 16#6) -> {more, 16#11, 16#29};
+dec_huffman_lookup(16#ed, 16#7) -> {ok, 16#11, 16#38};
+dec_huffman_lookup(16#ed, 16#8) -> {more, 16#12, 16#03};
+dec_huffman_lookup(16#ed, 16#9) -> {more, 16#12, 16#06};
+dec_huffman_lookup(16#ed, 16#a) -> {more, 16#12, 16#0a};
+dec_huffman_lookup(16#ed, 16#b) -> {more, 16#12, 16#0f};
+dec_huffman_lookup(16#ed, 16#c) -> {more, 16#12, 16#18};
+dec_huffman_lookup(16#ed, 16#d) -> {more, 16#12, 16#1f};
+dec_huffman_lookup(16#ed, 16#e) -> {more, 16#12, 16#29};
+dec_huffman_lookup(16#ed, 16#f) -> {ok, 16#12, 16#38};
+dec_huffman_lookup(16#ee, 16#0) -> {ok, 16#13, 16#00};
+dec_huffman_lookup(16#ee, 16#1) -> {ok, 16#14, 16#00};
+dec_huffman_lookup(16#ee, 16#2) -> {ok, 16#15, 16#00};
+dec_huffman_lookup(16#ee, 16#3) -> {ok, 16#17, 16#00};
+dec_huffman_lookup(16#ee, 16#4) -> {ok, 16#18, 16#00};
+dec_huffman_lookup(16#ee, 16#5) -> {ok, 16#19, 16#00};
+dec_huffman_lookup(16#ee, 16#6) -> {ok, 16#1a, 16#00};
+dec_huffman_lookup(16#ee, 16#7) -> {ok, 16#1b, 16#00};
+dec_huffman_lookup(16#ee, 16#8) -> {ok, 16#1c, 16#00};
+dec_huffman_lookup(16#ee, 16#9) -> {ok, 16#1d, 16#00};
+dec_huffman_lookup(16#ee, 16#a) -> {ok, 16#1e, 16#00};
+dec_huffman_lookup(16#ee, 16#b) -> {ok, 16#1f, 16#00};
+dec_huffman_lookup(16#ee, 16#c) -> {ok, 16#7f, 16#00};
+dec_huffman_lookup(16#ee, 16#d) -> {ok, 16#dc, 16#00};
+dec_huffman_lookup(16#ee, 16#e) -> {ok, 16#f9, 16#00};
+dec_huffman_lookup(16#ee, 16#f) -> {ok, undefined, 16#fd};
+dec_huffman_lookup(16#ef, 16#0) -> {more, 16#13, 16#01};
+dec_huffman_lookup(16#ef, 16#1) -> {ok, 16#13, 16#16};
+dec_huffman_lookup(16#ef, 16#2) -> {more, 16#14, 16#01};
+dec_huffman_lookup(16#ef, 16#3) -> {ok, 16#14, 16#16};
+dec_huffman_lookup(16#ef, 16#4) -> {more, 16#15, 16#01};
+dec_huffman_lookup(16#ef, 16#5) -> {ok, 16#15, 16#16};
+dec_huffman_lookup(16#ef, 16#6) -> {more, 16#17, 16#01};
+dec_huffman_lookup(16#ef, 16#7) -> {ok, 16#17, 16#16};
+dec_huffman_lookup(16#ef, 16#8) -> {more, 16#18, 16#01};
+dec_huffman_lookup(16#ef, 16#9) -> {ok, 16#18, 16#16};
+dec_huffman_lookup(16#ef, 16#a) -> {more, 16#19, 16#01};
+dec_huffman_lookup(16#ef, 16#b) -> {ok, 16#19, 16#16};
+dec_huffman_lookup(16#ef, 16#c) -> {more, 16#1a, 16#01};
+dec_huffman_lookup(16#ef, 16#d) -> {ok, 16#1a, 16#16};
+dec_huffman_lookup(16#ef, 16#e) -> {more, 16#1b, 16#01};
+dec_huffman_lookup(16#ef, 16#f) -> {ok, 16#1b, 16#16};
+dec_huffman_lookup(16#f0, 16#0) -> {more, 16#13, 16#02};
+dec_huffman_lookup(16#f0, 16#1) -> {more, 16#13, 16#09};
+dec_huffman_lookup(16#f0, 16#2) -> {more, 16#13, 16#17};
+dec_huffman_lookup(16#f0, 16#3) -> {ok, 16#13, 16#28};
+dec_huffman_lookup(16#f0, 16#4) -> {more, 16#14, 16#02};
+dec_huffman_lookup(16#f0, 16#5) -> {more, 16#14, 16#09};
+dec_huffman_lookup(16#f0, 16#6) -> {more, 16#14, 16#17};
+dec_huffman_lookup(16#f0, 16#7) -> {ok, 16#14, 16#28};
+dec_huffman_lookup(16#f0, 16#8) -> {more, 16#15, 16#02};
+dec_huffman_lookup(16#f0, 16#9) -> {more, 16#15, 16#09};
+dec_huffman_lookup(16#f0, 16#a) -> {more, 16#15, 16#17};
+dec_huffman_lookup(16#f0, 16#b) -> {ok, 16#15, 16#28};
+dec_huffman_lookup(16#f0, 16#c) -> {more, 16#17, 16#02};
+dec_huffman_lookup(16#f0, 16#d) -> {more, 16#17, 16#09};
+dec_huffman_lookup(16#f0, 16#e) -> {more, 16#17, 16#17};
+dec_huffman_lookup(16#f0, 16#f) -> {ok, 16#17, 16#28};
+dec_huffman_lookup(16#f1, 16#0) -> {more, 16#13, 16#03};
+dec_huffman_lookup(16#f1, 16#1) -> {more, 16#13, 16#06};
+dec_huffman_lookup(16#f1, 16#2) -> {more, 16#13, 16#0a};
+dec_huffman_lookup(16#f1, 16#3) -> {more, 16#13, 16#0f};
+dec_huffman_lookup(16#f1, 16#4) -> {more, 16#13, 16#18};
+dec_huffman_lookup(16#f1, 16#5) -> {more, 16#13, 16#1f};
+dec_huffman_lookup(16#f1, 16#6) -> {more, 16#13, 16#29};
+dec_huffman_lookup(16#f1, 16#7) -> {ok, 16#13, 16#38};
+dec_huffman_lookup(16#f1, 16#8) -> {more, 16#14, 16#03};
+dec_huffman_lookup(16#f1, 16#9) -> {more, 16#14, 16#06};
+dec_huffman_lookup(16#f1, 16#a) -> {more, 16#14, 16#0a};
+dec_huffman_lookup(16#f1, 16#b) -> {more, 16#14, 16#0f};
+dec_huffman_lookup(16#f1, 16#c) -> {more, 16#14, 16#18};
+dec_huffman_lookup(16#f1, 16#d) -> {more, 16#14, 16#1f};
+dec_huffman_lookup(16#f1, 16#e) -> {more, 16#14, 16#29};
+dec_huffman_lookup(16#f1, 16#f) -> {ok, 16#14, 16#38};
+dec_huffman_lookup(16#f2, 16#0) -> {more, 16#15, 16#03};
+dec_huffman_lookup(16#f2, 16#1) -> {more, 16#15, 16#06};
+dec_huffman_lookup(16#f2, 16#2) -> {more, 16#15, 16#0a};
+dec_huffman_lookup(16#f2, 16#3) -> {more, 16#15, 16#0f};
+dec_huffman_lookup(16#f2, 16#4) -> {more, 16#15, 16#18};
+dec_huffman_lookup(16#f2, 16#5) -> {more, 16#15, 16#1f};
+dec_huffman_lookup(16#f2, 16#6) -> {more, 16#15, 16#29};
+dec_huffman_lookup(16#f2, 16#7) -> {ok, 16#15, 16#38};
+dec_huffman_lookup(16#f2, 16#8) -> {more, 16#17, 16#03};
+dec_huffman_lookup(16#f2, 16#9) -> {more, 16#17, 16#06};
+dec_huffman_lookup(16#f2, 16#a) -> {more, 16#17, 16#0a};
+dec_huffman_lookup(16#f2, 16#b) -> {more, 16#17, 16#0f};
+dec_huffman_lookup(16#f2, 16#c) -> {more, 16#17, 16#18};
+dec_huffman_lookup(16#f2, 16#d) -> {more, 16#17, 16#1f};
+dec_huffman_lookup(16#f2, 16#e) -> {more, 16#17, 16#29};
+dec_huffman_lookup(16#f2, 16#f) -> {ok, 16#17, 16#38};
+dec_huffman_lookup(16#f3, 16#0) -> {more, 16#18, 16#02};
+dec_huffman_lookup(16#f3, 16#1) -> {more, 16#18, 16#09};
+dec_huffman_lookup(16#f3, 16#2) -> {more, 16#18, 16#17};
+dec_huffman_lookup(16#f3, 16#3) -> {ok, 16#18, 16#28};
+dec_huffman_lookup(16#f3, 16#4) -> {more, 16#19, 16#02};
+dec_huffman_lookup(16#f3, 16#5) -> {more, 16#19, 16#09};
+dec_huffman_lookup(16#f3, 16#6) -> {more, 16#19, 16#17};
+dec_huffman_lookup(16#f3, 16#7) -> {ok, 16#19, 16#28};
+dec_huffman_lookup(16#f3, 16#8) -> {more, 16#1a, 16#02};
+dec_huffman_lookup(16#f3, 16#9) -> {more, 16#1a, 16#09};
+dec_huffman_lookup(16#f3, 16#a) -> {more, 16#1a, 16#17};
+dec_huffman_lookup(16#f3, 16#b) -> {ok, 16#1a, 16#28};
+dec_huffman_lookup(16#f3, 16#c) -> {more, 16#1b, 16#02};
+dec_huffman_lookup(16#f3, 16#d) -> {more, 16#1b, 16#09};
+dec_huffman_lookup(16#f3, 16#e) -> {more, 16#1b, 16#17};
+dec_huffman_lookup(16#f3, 16#f) -> {ok, 16#1b, 16#28};
+dec_huffman_lookup(16#f4, 16#0) -> {more, 16#18, 16#03};
+dec_huffman_lookup(16#f4, 16#1) -> {more, 16#18, 16#06};
+dec_huffman_lookup(16#f4, 16#2) -> {more, 16#18, 16#0a};
+dec_huffman_lookup(16#f4, 16#3) -> {more, 16#18, 16#0f};
+dec_huffman_lookup(16#f4, 16#4) -> {more, 16#18, 16#18};
+dec_huffman_lookup(16#f4, 16#5) -> {more, 16#18, 16#1f};
+dec_huffman_lookup(16#f4, 16#6) -> {more, 16#18, 16#29};
+dec_huffman_lookup(16#f4, 16#7) -> {ok, 16#18, 16#38};
+dec_huffman_lookup(16#f4, 16#8) -> {more, 16#19, 16#03};
+dec_huffman_lookup(16#f4, 16#9) -> {more, 16#19, 16#06};
+dec_huffman_lookup(16#f4, 16#a) -> {more, 16#19, 16#0a};
+dec_huffman_lookup(16#f4, 16#b) -> {more, 16#19, 16#0f};
+dec_huffman_lookup(16#f4, 16#c) -> {more, 16#19, 16#18};
+dec_huffman_lookup(16#f4, 16#d) -> {more, 16#19, 16#1f};
+dec_huffman_lookup(16#f4, 16#e) -> {more, 16#19, 16#29};
+dec_huffman_lookup(16#f4, 16#f) -> {ok, 16#19, 16#38};
+dec_huffman_lookup(16#f5, 16#0) -> {more, 16#1a, 16#03};
+dec_huffman_lookup(16#f5, 16#1) -> {more, 16#1a, 16#06};
+dec_huffman_lookup(16#f5, 16#2) -> {more, 16#1a, 16#0a};
+dec_huffman_lookup(16#f5, 16#3) -> {more, 16#1a, 16#0f};
+dec_huffman_lookup(16#f5, 16#4) -> {more, 16#1a, 16#18};
+dec_huffman_lookup(16#f5, 16#5) -> {more, 16#1a, 16#1f};
+dec_huffman_lookup(16#f5, 16#6) -> {more, 16#1a, 16#29};
+dec_huffman_lookup(16#f5, 16#7) -> {ok, 16#1a, 16#38};
+dec_huffman_lookup(16#f5, 16#8) -> {more, 16#1b, 16#03};
+dec_huffman_lookup(16#f5, 16#9) -> {more, 16#1b, 16#06};
+dec_huffman_lookup(16#f5, 16#a) -> {more, 16#1b, 16#0a};
+dec_huffman_lookup(16#f5, 16#b) -> {more, 16#1b, 16#0f};
+dec_huffman_lookup(16#f5, 16#c) -> {more, 16#1b, 16#18};
+dec_huffman_lookup(16#f5, 16#d) -> {more, 16#1b, 16#1f};
+dec_huffman_lookup(16#f5, 16#e) -> {more, 16#1b, 16#29};
+dec_huffman_lookup(16#f5, 16#f) -> {ok, 16#1b, 16#38};
+dec_huffman_lookup(16#f6, 16#0) -> {more, 16#1c, 16#01};
+dec_huffman_lookup(16#f6, 16#1) -> {ok, 16#1c, 16#16};
+dec_huffman_lookup(16#f6, 16#2) -> {more, 16#1d, 16#01};
+dec_huffman_lookup(16#f6, 16#3) -> {ok, 16#1d, 16#16};
+dec_huffman_lookup(16#f6, 16#4) -> {more, 16#1e, 16#01};
+dec_huffman_lookup(16#f6, 16#5) -> {ok, 16#1e, 16#16};
+dec_huffman_lookup(16#f6, 16#6) -> {more, 16#1f, 16#01};
+dec_huffman_lookup(16#f6, 16#7) -> {ok, 16#1f, 16#16};
+dec_huffman_lookup(16#f6, 16#8) -> {more, 16#7f, 16#01};
+dec_huffman_lookup(16#f6, 16#9) -> {ok, 16#7f, 16#16};
+dec_huffman_lookup(16#f6, 16#a) -> {more, 16#dc, 16#01};
+dec_huffman_lookup(16#f6, 16#b) -> {ok, 16#dc, 16#16};
+dec_huffman_lookup(16#f6, 16#c) -> {more, 16#f9, 16#01};
+dec_huffman_lookup(16#f6, 16#d) -> {ok, 16#f9, 16#16};
+dec_huffman_lookup(16#f6, 16#e) -> {more, undefined, 16#fe};
+dec_huffman_lookup(16#f6, 16#f) -> {ok, undefined, 16#ff};
+dec_huffman_lookup(16#f7, 16#0) -> {more, 16#1c, 16#02};
+dec_huffman_lookup(16#f7, 16#1) -> {more, 16#1c, 16#09};
+dec_huffman_lookup(16#f7, 16#2) -> {more, 16#1c, 16#17};
+dec_huffman_lookup(16#f7, 16#3) -> {ok, 16#1c, 16#28};
+dec_huffman_lookup(16#f7, 16#4) -> {more, 16#1d, 16#02};
+dec_huffman_lookup(16#f7, 16#5) -> {more, 16#1d, 16#09};
+dec_huffman_lookup(16#f7, 16#6) -> {more, 16#1d, 16#17};
+dec_huffman_lookup(16#f7, 16#7) -> {ok, 16#1d, 16#28};
+dec_huffman_lookup(16#f7, 16#8) -> {more, 16#1e, 16#02};
+dec_huffman_lookup(16#f7, 16#9) -> {more, 16#1e, 16#09};
+dec_huffman_lookup(16#f7, 16#a) -> {more, 16#1e, 16#17};
+dec_huffman_lookup(16#f7, 16#b) -> {ok, 16#1e, 16#28};
+dec_huffman_lookup(16#f7, 16#c) -> {more, 16#1f, 16#02};
+dec_huffman_lookup(16#f7, 16#d) -> {more, 16#1f, 16#09};
+dec_huffman_lookup(16#f7, 16#e) -> {more, 16#1f, 16#17};
+dec_huffman_lookup(16#f7, 16#f) -> {ok, 16#1f, 16#28};
+dec_huffman_lookup(16#f8, 16#0) -> {more, 16#1c, 16#03};
+dec_huffman_lookup(16#f8, 16#1) -> {more, 16#1c, 16#06};
+dec_huffman_lookup(16#f8, 16#2) -> {more, 16#1c, 16#0a};
+dec_huffman_lookup(16#f8, 16#3) -> {more, 16#1c, 16#0f};
+dec_huffman_lookup(16#f8, 16#4) -> {more, 16#1c, 16#18};
+dec_huffman_lookup(16#f8, 16#5) -> {more, 16#1c, 16#1f};
+dec_huffman_lookup(16#f8, 16#6) -> {more, 16#1c, 16#29};
+dec_huffman_lookup(16#f8, 16#7) -> {ok, 16#1c, 16#38};
+dec_huffman_lookup(16#f8, 16#8) -> {more, 16#1d, 16#03};
+dec_huffman_lookup(16#f8, 16#9) -> {more, 16#1d, 16#06};
+dec_huffman_lookup(16#f8, 16#a) -> {more, 16#1d, 16#0a};
+dec_huffman_lookup(16#f8, 16#b) -> {more, 16#1d, 16#0f};
+dec_huffman_lookup(16#f8, 16#c) -> {more, 16#1d, 16#18};
+dec_huffman_lookup(16#f8, 16#d) -> {more, 16#1d, 16#1f};
+dec_huffman_lookup(16#f8, 16#e) -> {more, 16#1d, 16#29};
+dec_huffman_lookup(16#f8, 16#f) -> {ok, 16#1d, 16#38};
+dec_huffman_lookup(16#f9, 16#0) -> {more, 16#1e, 16#03};
+dec_huffman_lookup(16#f9, 16#1) -> {more, 16#1e, 16#06};
+dec_huffman_lookup(16#f9, 16#2) -> {more, 16#1e, 16#0a};
+dec_huffman_lookup(16#f9, 16#3) -> {more, 16#1e, 16#0f};
+dec_huffman_lookup(16#f9, 16#4) -> {more, 16#1e, 16#18};
+dec_huffman_lookup(16#f9, 16#5) -> {more, 16#1e, 16#1f};
+dec_huffman_lookup(16#f9, 16#6) -> {more, 16#1e, 16#29};
+dec_huffman_lookup(16#f9, 16#7) -> {ok, 16#1e, 16#38};
+dec_huffman_lookup(16#f9, 16#8) -> {more, 16#1f, 16#03};
+dec_huffman_lookup(16#f9, 16#9) -> {more, 16#1f, 16#06};
+dec_huffman_lookup(16#f9, 16#a) -> {more, 16#1f, 16#0a};
+dec_huffman_lookup(16#f9, 16#b) -> {more, 16#1f, 16#0f};
+dec_huffman_lookup(16#f9, 16#c) -> {more, 16#1f, 16#18};
+dec_huffman_lookup(16#f9, 16#d) -> {more, 16#1f, 16#1f};
+dec_huffman_lookup(16#f9, 16#e) -> {more, 16#1f, 16#29};
+dec_huffman_lookup(16#f9, 16#f) -> {ok, 16#1f, 16#38};
+dec_huffman_lookup(16#fa, 16#0) -> {more, 16#7f, 16#02};
+dec_huffman_lookup(16#fa, 16#1) -> {more, 16#7f, 16#09};
+dec_huffman_lookup(16#fa, 16#2) -> {more, 16#7f, 16#17};
+dec_huffman_lookup(16#fa, 16#3) -> {ok, 16#7f, 16#28};
+dec_huffman_lookup(16#fa, 16#4) -> {more, 16#dc, 16#02};
+dec_huffman_lookup(16#fa, 16#5) -> {more, 16#dc, 16#09};
+dec_huffman_lookup(16#fa, 16#6) -> {more, 16#dc, 16#17};
+dec_huffman_lookup(16#fa, 16#7) -> {ok, 16#dc, 16#28};
+dec_huffman_lookup(16#fa, 16#8) -> {more, 16#f9, 16#02};
+dec_huffman_lookup(16#fa, 16#9) -> {more, 16#f9, 16#09};
+dec_huffman_lookup(16#fa, 16#a) -> {more, 16#f9, 16#17};
+dec_huffman_lookup(16#fa, 16#b) -> {ok, 16#f9, 16#28};
+dec_huffman_lookup(16#fa, 16#c) -> {ok, 16#0a, 16#00};
+dec_huffman_lookup(16#fa, 16#d) -> {ok, 16#0d, 16#00};
+dec_huffman_lookup(16#fa, 16#e) -> {ok, 16#16, 16#00};
+dec_huffman_lookup(16#fa, 16#f) -> error;
+dec_huffman_lookup(16#fb, 16#0) -> {more, 16#7f, 16#03};
+dec_huffman_lookup(16#fb, 16#1) -> {more, 16#7f, 16#06};
+dec_huffman_lookup(16#fb, 16#2) -> {more, 16#7f, 16#0a};
+dec_huffman_lookup(16#fb, 16#3) -> {more, 16#7f, 16#0f};
+dec_huffman_lookup(16#fb, 16#4) -> {more, 16#7f, 16#18};
+dec_huffman_lookup(16#fb, 16#5) -> {more, 16#7f, 16#1f};
+dec_huffman_lookup(16#fb, 16#6) -> {more, 16#7f, 16#29};
+dec_huffman_lookup(16#fb, 16#7) -> {ok, 16#7f, 16#38};
+dec_huffman_lookup(16#fb, 16#8) -> {more, 16#dc, 16#03};
+dec_huffman_lookup(16#fb, 16#9) -> {more, 16#dc, 16#06};
+dec_huffman_lookup(16#fb, 16#a) -> {more, 16#dc, 16#0a};
+dec_huffman_lookup(16#fb, 16#b) -> {more, 16#dc, 16#0f};
+dec_huffman_lookup(16#fb, 16#c) -> {more, 16#dc, 16#18};
+dec_huffman_lookup(16#fb, 16#d) -> {more, 16#dc, 16#1f};
+dec_huffman_lookup(16#fb, 16#e) -> {more, 16#dc, 16#29};
+dec_huffman_lookup(16#fb, 16#f) -> {ok, 16#dc, 16#38};
+dec_huffman_lookup(16#fc, 16#0) -> {more, 16#f9, 16#03};
+dec_huffman_lookup(16#fc, 16#1) -> {more, 16#f9, 16#06};
+dec_huffman_lookup(16#fc, 16#2) -> {more, 16#f9, 16#0a};
+dec_huffman_lookup(16#fc, 16#3) -> {more, 16#f9, 16#0f};
+dec_huffman_lookup(16#fc, 16#4) -> {more, 16#f9, 16#18};
+dec_huffman_lookup(16#fc, 16#5) -> {more, 16#f9, 16#1f};
+dec_huffman_lookup(16#fc, 16#6) -> {more, 16#f9, 16#29};
+dec_huffman_lookup(16#fc, 16#7) -> {ok, 16#f9, 16#38};
+dec_huffman_lookup(16#fc, 16#8) -> {more, 16#0a, 16#01};
+dec_huffman_lookup(16#fc, 16#9) -> {ok, 16#0a, 16#16};
+dec_huffman_lookup(16#fc, 16#a) -> {more, 16#0d, 16#01};
+dec_huffman_lookup(16#fc, 16#b) -> {ok, 16#0d, 16#16};
+dec_huffman_lookup(16#fc, 16#c) -> {more, 16#16, 16#01};
+dec_huffman_lookup(16#fc, 16#d) -> {ok, 16#16, 16#16};
+dec_huffman_lookup(16#fc, 16#e) -> error;
+dec_huffman_lookup(16#fc, 16#f) -> error;
+dec_huffman_lookup(16#fd, 16#0) -> {more, 16#0a, 16#02};
+dec_huffman_lookup(16#fd, 16#1) -> {more, 16#0a, 16#09};
+dec_huffman_lookup(16#fd, 16#2) -> {more, 16#0a, 16#17};
+dec_huffman_lookup(16#fd, 16#3) -> {ok, 16#0a, 16#28};
+dec_huffman_lookup(16#fd, 16#4) -> {more, 16#0d, 16#02};
+dec_huffman_lookup(16#fd, 16#5) -> {more, 16#0d, 16#09};
+dec_huffman_lookup(16#fd, 16#6) -> {more, 16#0d, 16#17};
+dec_huffman_lookup(16#fd, 16#7) -> {ok, 16#0d, 16#28};
+dec_huffman_lookup(16#fd, 16#8) -> {more, 16#16, 16#02};
+dec_huffman_lookup(16#fd, 16#9) -> {more, 16#16, 16#09};
+dec_huffman_lookup(16#fd, 16#a) -> {more, 16#16, 16#17};
+dec_huffman_lookup(16#fd, 16#b) -> {ok, 16#16, 16#28};
+dec_huffman_lookup(16#fd, 16#c) -> error;
+dec_huffman_lookup(16#fd, 16#d) -> error;
+dec_huffman_lookup(16#fd, 16#e) -> error;
+dec_huffman_lookup(16#fd, 16#f) -> error;
+dec_huffman_lookup(16#fe, 16#0) -> {more, 16#0a, 16#03};
+dec_huffman_lookup(16#fe, 16#1) -> {more, 16#0a, 16#06};
+dec_huffman_lookup(16#fe, 16#2) -> {more, 16#0a, 16#0a};
+dec_huffman_lookup(16#fe, 16#3) -> {more, 16#0a, 16#0f};
+dec_huffman_lookup(16#fe, 16#4) -> {more, 16#0a, 16#18};
+dec_huffman_lookup(16#fe, 16#5) -> {more, 16#0a, 16#1f};
+dec_huffman_lookup(16#fe, 16#6) -> {more, 16#0a, 16#29};
+dec_huffman_lookup(16#fe, 16#7) -> {ok, 16#0a, 16#38};
+dec_huffman_lookup(16#fe, 16#8) -> {more, 16#0d, 16#03};
+dec_huffman_lookup(16#fe, 16#9) -> {more, 16#0d, 16#06};
+dec_huffman_lookup(16#fe, 16#a) -> {more, 16#0d, 16#0a};
+dec_huffman_lookup(16#fe, 16#b) -> {more, 16#0d, 16#0f};
+dec_huffman_lookup(16#fe, 16#c) -> {more, 16#0d, 16#18};
+dec_huffman_lookup(16#fe, 16#d) -> {more, 16#0d, 16#1f};
+dec_huffman_lookup(16#fe, 16#e) -> {more, 16#0d, 16#29};
+dec_huffman_lookup(16#fe, 16#f) -> {ok, 16#0d, 16#38};
+dec_huffman_lookup(16#ff, 16#0) -> {more, 16#16, 16#03};
+dec_huffman_lookup(16#ff, 16#1) -> {more, 16#16, 16#06};
+dec_huffman_lookup(16#ff, 16#2) -> {more, 16#16, 16#0a};
+dec_huffman_lookup(16#ff, 16#3) -> {more, 16#16, 16#0f};
+dec_huffman_lookup(16#ff, 16#4) -> {more, 16#16, 16#18};
+dec_huffman_lookup(16#ff, 16#5) -> {more, 16#16, 16#1f};
+dec_huffman_lookup(16#ff, 16#6) -> {more, 16#16, 16#29};
+dec_huffman_lookup(16#ff, 16#7) -> {ok, 16#16, 16#38};
+dec_huffman_lookup(16#ff, 16#8) -> error;
+dec_huffman_lookup(16#ff, 16#9) -> error;
+dec_huffman_lookup(16#ff, 16#a) -> error;
+dec_huffman_lookup(16#ff, 16#b) -> error;
+dec_huffman_lookup(16#ff, 16#c) -> error;
+dec_huffman_lookup(16#ff, 16#d) -> error;
+dec_huffman_lookup(16#ff, 16#e) -> error;
+dec_huffman_lookup(16#ff, 16#f) -> error.
diff --git a/server/_build/default/lib/cowlib/src/cow_http.erl b/server/_build/default/lib/cowlib/src/cow_http.erl
new file mode 100644
index 0000000..93e9193
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http.erl
@@ -0,0 +1,426 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http).
+
+-export([parse_request_line/1]).
+-export([parse_status_line/1]).
+-export([status_to_integer/1]).
+-export([parse_headers/1]).
+
+-export([parse_fullpath/1]).
+-export([parse_version/1]).
+
+-export([request/4]).
+-export([response/3]).
+-export([headers/1]).
+-export([version/1]).
+
+-type version() :: 'HTTP/1.0' | 'HTTP/1.1'.
+-export_type([version/0]).
+
+-type status() :: 100..999.
+-export_type([status/0]).
+
+-type headers() :: [{binary(), iodata()}].
+-export_type([headers/0]).
+
+-include("cow_inline.hrl").
+
+%% @doc Parse the request line.
+
+-spec parse_request_line(binary()) -> {binary(), binary(), version(), binary()}.
+parse_request_line(Data) ->
+ {Pos, _} = binary:match(Data, <<"\r">>),
+ <<RequestLine:Pos/binary, "\r\n", Rest/bits>> = Data,
+ [Method, Target, Version0] = binary:split(RequestLine, <<$\s>>, [trim_all, global]),
+ Version = case Version0 of
+ <<"HTTP/1.1">> -> 'HTTP/1.1';
+ <<"HTTP/1.0">> -> 'HTTP/1.0'
+ end,
+ {Method, Target, Version, Rest}.
+
+-ifdef(TEST).
+parse_request_line_test_() ->
+ Tests = [
+ {<<"GET /path HTTP/1.0\r\nRest">>,
+ {<<"GET">>, <<"/path">>, 'HTTP/1.0', <<"Rest">>}},
+ {<<"GET /path HTTP/1.1\r\nRest">>,
+ {<<"GET">>, <<"/path">>, 'HTTP/1.1', <<"Rest">>}},
+ {<<"CONNECT proxy.example.org:1080 HTTP/1.1\r\nRest">>,
+ {<<"CONNECT">>, <<"proxy.example.org:1080">>, 'HTTP/1.1', <<"Rest">>}}
+ ],
+ [{V, fun() -> R = parse_request_line(V) end}
+ || {V, R} <- Tests].
+
+parse_request_line_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"GET">>,
+ <<"GET /path\r\n">>,
+ <<"GET /path HTTP/1.1">>,
+ <<"GET /path HTTP/1.1\r">>,
+ <<"GET /path HTTP/1.1\n">>,
+ <<"GET /path HTTP/0.9\r\n">>,
+ <<"content-type: text/plain\r\n">>,
+ <<0:80, "\r\n">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_request_line(V)) end}
+ || V <- Tests].
+
+horse_parse_request_line_get_path() ->
+ horse:repeat(200000,
+ parse_request_line(<<"GET /path HTTP/1.1\r\n">>)
+ ).
+-endif.
+
+%% @doc Parse the status line.
+
+-spec parse_status_line(binary()) -> {version(), status(), binary(), binary()}.
+parse_status_line(<< "HTTP/1.1 200 OK\r\n", Rest/bits >>) ->
+ {'HTTP/1.1', 200, <<"OK">>, Rest};
+parse_status_line(<< "HTTP/1.1 404 Not Found\r\n", Rest/bits >>) ->
+ {'HTTP/1.1', 404, <<"Not Found">>, Rest};
+parse_status_line(<< "HTTP/1.1 500 Internal Server Error\r\n", Rest/bits >>) ->
+ {'HTTP/1.1', 500, <<"Internal Server Error">>, Rest};
+parse_status_line(<< "HTTP/1.1 ", Status/bits >>) ->
+ parse_status_line(Status, 'HTTP/1.1');
+parse_status_line(<< "HTTP/1.0 ", Status/bits >>) ->
+ parse_status_line(Status, 'HTTP/1.0').
+
+parse_status_line(<<H, T, U, " ", Rest/bits>>, Version) ->
+ Status = status_to_integer(H, T, U),
+ {Pos, _} = binary:match(Rest, <<"\r">>),
+ << StatusStr:Pos/binary, "\r\n", Rest2/bits >> = Rest,
+ {Version, Status, StatusStr, Rest2}.
+
+-spec status_to_integer(status() | binary()) -> status().
+status_to_integer(Status) when is_integer(Status) ->
+ Status;
+status_to_integer(Status) ->
+ case Status of
+ <<H, T, U>> ->
+ status_to_integer(H, T, U);
+ <<H, T, U, " ", _/bits>> ->
+ status_to_integer(H, T, U)
+ end.
+
+status_to_integer(H, T, U)
+ when $0 =< H, H =< $9, $0 =< T, T =< $9, $0 =< U, U =< $9 ->
+ (H - $0) * 100 + (T - $0) * 10 + (U - $0).
+
+-ifdef(TEST).
+parse_status_line_test_() ->
+ Tests = [
+ {<<"HTTP/1.1 200 OK\r\nRest">>,
+ {'HTTP/1.1', 200, <<"OK">>, <<"Rest">>}},
+ {<<"HTTP/1.0 404 Not Found\r\nRest">>,
+ {'HTTP/1.0', 404, <<"Not Found">>, <<"Rest">>}},
+ {<<"HTTP/1.1 500 Something very funny here\r\nRest">>,
+ {'HTTP/1.1', 500, <<"Something very funny here">>, <<"Rest">>}},
+ {<<"HTTP/1.1 200 \r\nRest">>,
+ {'HTTP/1.1', 200, <<>>, <<"Rest">>}}
+ ],
+ [{V, fun() -> R = parse_status_line(V) end}
+ || {V, R} <- Tests].
+
+parse_status_line_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"HTTP/1.1">>,
+ <<"HTTP/1.1 200\r\n">>,
+ <<"HTTP/1.1 200 OK">>,
+ <<"HTTP/1.1 200 OK\r">>,
+ <<"HTTP/1.1 200 OK\n">>,
+ <<"HTTP/0.9 200 OK\r\n">>,
+ <<"HTTP/1.1 42 Answer\r\n">>,
+ <<"HTTP/1.1 999999999 More than OK\r\n">>,
+ <<"content-type: text/plain\r\n">>,
+ <<0:80, "\r\n">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_status_line(V)) end}
+ || V <- Tests].
+
+horse_parse_status_line_200() ->
+ horse:repeat(200000,
+ parse_status_line(<<"HTTP/1.1 200 OK\r\n">>)
+ ).
+
+horse_parse_status_line_404() ->
+ horse:repeat(200000,
+ parse_status_line(<<"HTTP/1.1 404 Not Found\r\n">>)
+ ).
+
+horse_parse_status_line_500() ->
+ horse:repeat(200000,
+ parse_status_line(<<"HTTP/1.1 500 Internal Server Error\r\n">>)
+ ).
+
+horse_parse_status_line_other() ->
+ horse:repeat(200000,
+ parse_status_line(<<"HTTP/1.1 416 Requested range not satisfiable\r\n">>)
+ ).
+-endif.
+
+%% @doc Parse the list of headers.
+
+-spec parse_headers(binary()) -> {[{binary(), binary()}], binary()}.
+parse_headers(Data) ->
+ parse_header(Data, []).
+
+parse_header(<< $\r, $\n, Rest/bits >>, Acc) ->
+ {lists:reverse(Acc), Rest};
+parse_header(Data, Acc) ->
+ parse_hd_name(Data, Acc, <<>>).
+
+parse_hd_name(<< C, Rest/bits >>, Acc, SoFar) ->
+ case C of
+ $: -> parse_hd_before_value(Rest, Acc, SoFar);
+ $\s -> parse_hd_name_ws(Rest, Acc, SoFar);
+ $\t -> parse_hd_name_ws(Rest, Acc, SoFar);
+ _ -> ?LOWER(parse_hd_name, Rest, Acc, SoFar)
+ end.
+
+parse_hd_name_ws(<< C, Rest/bits >>, Acc, Name) ->
+ case C of
+ $: -> parse_hd_before_value(Rest, Acc, Name);
+ $\s -> parse_hd_name_ws(Rest, Acc, Name);
+ $\t -> parse_hd_name_ws(Rest, Acc, Name)
+ end.
+
+parse_hd_before_value(<< $\s, Rest/bits >>, Acc, Name) ->
+ parse_hd_before_value(Rest, Acc, Name);
+parse_hd_before_value(<< $\t, Rest/bits >>, Acc, Name) ->
+ parse_hd_before_value(Rest, Acc, Name);
+parse_hd_before_value(Data, Acc, Name) ->
+ parse_hd_value(Data, Acc, Name, <<>>).
+
+parse_hd_value(<< $\r, Rest/bits >>, Acc, Name, SoFar) ->
+ case Rest of
+ << $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t ->
+ parse_hd_value(Rest2, Acc, Name, << SoFar/binary, C >>);
+ << $\n, Rest2/bits >> ->
+ Value = clean_value_ws_end(SoFar, byte_size(SoFar) - 1),
+ parse_header(Rest2, [{Name, Value}|Acc])
+ end;
+parse_hd_value(<< C, Rest/bits >>, Acc, Name, SoFar) ->
+ parse_hd_value(Rest, Acc, Name, << SoFar/binary, C >>).
+
+%% This function has been copied from cowboy_http.
+clean_value_ws_end(_, -1) ->
+ <<>>;
+clean_value_ws_end(Value, N) ->
+ case binary:at(Value, N) of
+ $\s -> clean_value_ws_end(Value, N - 1);
+ $\t -> clean_value_ws_end(Value, N - 1);
+ _ ->
+ S = N + 1,
+ << Value2:S/binary, _/bits >> = Value,
+ Value2
+ end.
+
+-ifdef(TEST).
+parse_headers_test_() ->
+ Tests = [
+ {<<"\r\nRest">>,
+ {[], <<"Rest">>}},
+ {<<"Server: Erlang/R17 \r\n\r\n">>,
+ {[{<<"server">>, <<"Erlang/R17">>}], <<>>}},
+ {<<"Server: Erlang/R17\r\n"
+ "Date: Sun, 23 Feb 2014 09:30:39 GMT\r\n"
+ "Multiline-Header: why hello!\r\n"
+ " I didn't see you all the way over there!\r\n"
+ "Content-Length: 12\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\nRest">>,
+ {[{<<"server">>, <<"Erlang/R17">>},
+ {<<"date">>, <<"Sun, 23 Feb 2014 09:30:39 GMT">>},
+ {<<"multiline-header">>,
+ <<"why hello! I didn't see you all the way over there!">>},
+ {<<"content-length">>, <<"12">>},
+ {<<"content-type">>, <<"text/plain">>}],
+ <<"Rest">>}}
+ ],
+ [{V, fun() -> R = parse_headers(V) end}
+ || {V, R} <- Tests].
+
+parse_headers_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"\r">>,
+ <<"Malformed\r\n\r\n">>,
+ <<"content-type: text/plain\r\nMalformed\r\n\r\n">>,
+ <<"HTTP/1.1 200 OK\r\n\r\n">>,
+ <<0:80, "\r\n\r\n">>,
+ <<"content-type: text/plain\r\ncontent-length: 12\r\n">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_headers(V)) end}
+ || V <- Tests].
+
+horse_parse_headers() ->
+ horse:repeat(50000,
+ parse_headers(<<"Server: Erlang/R17\r\n"
+ "Date: Sun, 23 Feb 2014 09:30:39 GMT\r\n"
+ "Multiline-Header: why hello!\r\n"
+ " I didn't see you all the way over there!\r\n"
+ "Content-Length: 12\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\nRest">>)
+ ).
+-endif.
+
+%% @doc Extract path and query string from a binary,
+%% removing any fragment component.
+
+-spec parse_fullpath(binary()) -> {binary(), binary()}.
+parse_fullpath(Fullpath) ->
+ parse_fullpath(Fullpath, <<>>).
+
+parse_fullpath(<<>>, Path) -> {Path, <<>>};
+parse_fullpath(<< $#, _/bits >>, Path) -> {Path, <<>>};
+parse_fullpath(<< $?, Qs/bits >>, Path) -> parse_fullpath_query(Qs, Path, <<>>);
+parse_fullpath(<< C, Rest/bits >>, SoFar) -> parse_fullpath(Rest, << SoFar/binary, C >>).
+
+parse_fullpath_query(<<>>, Path, Query) -> {Path, Query};
+parse_fullpath_query(<< $#, _/bits >>, Path, Query) -> {Path, Query};
+parse_fullpath_query(<< C, Rest/bits >>, Path, SoFar) ->
+ parse_fullpath_query(Rest, Path, << SoFar/binary, C >>).
+
+-ifdef(TEST).
+parse_fullpath_test() ->
+ {<<"*">>, <<>>} = parse_fullpath(<<"*">>),
+ {<<"/">>, <<>>} = parse_fullpath(<<"/">>),
+ {<<"/path/to/resource">>, <<>>} = parse_fullpath(<<"/path/to/resource#fragment">>),
+ {<<"/path/to/resource">>, <<>>} = parse_fullpath(<<"/path/to/resource">>),
+ {<<"/">>, <<>>} = parse_fullpath(<<"/?">>),
+ {<<"/">>, <<"q=cowboy">>} = parse_fullpath(<<"/?q=cowboy#fragment">>),
+ {<<"/">>, <<"q=cowboy">>} = parse_fullpath(<<"/?q=cowboy">>),
+ {<<"/path/to/resource">>, <<"q=cowboy">>}
+ = parse_fullpath(<<"/path/to/resource?q=cowboy">>),
+ ok.
+-endif.
+
+%% @doc Convert an HTTP version to atom.
+
+-spec parse_version(binary()) -> version().
+parse_version(<<"HTTP/1.1">>) -> 'HTTP/1.1';
+parse_version(<<"HTTP/1.0">>) -> 'HTTP/1.0'.
+
+-ifdef(TEST).
+parse_version_test() ->
+ 'HTTP/1.1' = parse_version(<<"HTTP/1.1">>),
+ 'HTTP/1.0' = parse_version(<<"HTTP/1.0">>),
+ {'EXIT', _} = (catch parse_version(<<"HTTP/1.2">>)),
+ ok.
+-endif.
+
+%% @doc Return formatted request-line and headers.
+%% @todo Add tests when the corresponding reverse functions are added.
+
+-spec request(binary(), iodata(), version(), headers()) -> iodata().
+request(Method, Path, Version, Headers) ->
+ [Method, <<" ">>, Path, <<" ">>, version(Version), <<"\r\n">>,
+ [[N, <<": ">>, V, <<"\r\n">>] || {N, V} <- Headers],
+ <<"\r\n">>].
+
+-spec response(status() | binary(), version(), headers()) -> iodata().
+response(Status, Version, Headers) ->
+ [version(Version), <<" ">>, status(Status), <<"\r\n">>,
+ headers(Headers), <<"\r\n">>].
+
+-spec headers(headers()) -> iodata().
+headers(Headers) ->
+ [[N, <<": ">>, V, <<"\r\n">>] || {N, V} <- Headers].
+
+%% @doc Return the version as a binary.
+
+-spec version(version()) -> binary().
+version('HTTP/1.1') -> <<"HTTP/1.1">>;
+version('HTTP/1.0') -> <<"HTTP/1.0">>.
+
+-ifdef(TEST).
+version_test() ->
+ <<"HTTP/1.1">> = version('HTTP/1.1'),
+ <<"HTTP/1.0">> = version('HTTP/1.0'),
+ {'EXIT', _} = (catch version('HTTP/1.2')),
+ ok.
+-endif.
+
+%% @doc Return the status code and string as binary.
+
+-spec status(status() | binary()) -> binary().
+status(100) -> <<"100 Continue">>;
+status(101) -> <<"101 Switching Protocols">>;
+status(102) -> <<"102 Processing">>;
+status(103) -> <<"103 Early Hints">>;
+status(200) -> <<"200 OK">>;
+status(201) -> <<"201 Created">>;
+status(202) -> <<"202 Accepted">>;
+status(203) -> <<"203 Non-Authoritative Information">>;
+status(204) -> <<"204 No Content">>;
+status(205) -> <<"205 Reset Content">>;
+status(206) -> <<"206 Partial Content">>;
+status(207) -> <<"207 Multi-Status">>;
+status(208) -> <<"208 Already Reported">>;
+status(226) -> <<"226 IM Used">>;
+status(300) -> <<"300 Multiple Choices">>;
+status(301) -> <<"301 Moved Permanently">>;
+status(302) -> <<"302 Found">>;
+status(303) -> <<"303 See Other">>;
+status(304) -> <<"304 Not Modified">>;
+status(305) -> <<"305 Use Proxy">>;
+status(306) -> <<"306 Switch Proxy">>;
+status(307) -> <<"307 Temporary Redirect">>;
+status(308) -> <<"308 Permanent Redirect">>;
+status(400) -> <<"400 Bad Request">>;
+status(401) -> <<"401 Unauthorized">>;
+status(402) -> <<"402 Payment Required">>;
+status(403) -> <<"403 Forbidden">>;
+status(404) -> <<"404 Not Found">>;
+status(405) -> <<"405 Method Not Allowed">>;
+status(406) -> <<"406 Not Acceptable">>;
+status(407) -> <<"407 Proxy Authentication Required">>;
+status(408) -> <<"408 Request Timeout">>;
+status(409) -> <<"409 Conflict">>;
+status(410) -> <<"410 Gone">>;
+status(411) -> <<"411 Length Required">>;
+status(412) -> <<"412 Precondition Failed">>;
+status(413) -> <<"413 Request Entity Too Large">>;
+status(414) -> <<"414 Request-URI Too Long">>;
+status(415) -> <<"415 Unsupported Media Type">>;
+status(416) -> <<"416 Requested Range Not Satisfiable">>;
+status(417) -> <<"417 Expectation Failed">>;
+status(418) -> <<"418 I'm a teapot">>;
+status(421) -> <<"421 Misdirected Request">>;
+status(422) -> <<"422 Unprocessable Entity">>;
+status(423) -> <<"423 Locked">>;
+status(424) -> <<"424 Failed Dependency">>;
+status(425) -> <<"425 Unordered Collection">>;
+status(426) -> <<"426 Upgrade Required">>;
+status(428) -> <<"428 Precondition Required">>;
+status(429) -> <<"429 Too Many Requests">>;
+status(431) -> <<"431 Request Header Fields Too Large">>;
+status(451) -> <<"451 Unavailable For Legal Reasons">>;
+status(500) -> <<"500 Internal Server Error">>;
+status(501) -> <<"501 Not Implemented">>;
+status(502) -> <<"502 Bad Gateway">>;
+status(503) -> <<"503 Service Unavailable">>;
+status(504) -> <<"504 Gateway Timeout">>;
+status(505) -> <<"505 HTTP Version Not Supported">>;
+status(506) -> <<"506 Variant Also Negotiates">>;
+status(507) -> <<"507 Insufficient Storage">>;
+status(508) -> <<"508 Loop Detected">>;
+status(510) -> <<"510 Not Extended">>;
+status(511) -> <<"511 Network Authentication Required">>;
+status(B) when is_binary(B) -> B.
diff --git a/server/_build/default/lib/cowlib/src/cow_http2.erl b/server/_build/default/lib/cowlib/src/cow_http2.erl
new file mode 100644
index 0000000..2925e37
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http2.erl
@@ -0,0 +1,482 @@
+%% Copyright (c) 2015-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http2).
+
+%% Parsing.
+-export([parse_sequence/1]).
+-export([parse/1]).
+-export([parse/2]).
+-export([parse_settings_payload/1]).
+
+%% Building.
+-export([data/3]).
+-export([data_header/3]).
+-export([headers/3]).
+-export([priority/4]).
+-export([rst_stream/2]).
+-export([settings/1]).
+-export([settings_payload/1]).
+-export([settings_ack/0]).
+-export([push_promise/3]).
+-export([ping/1]).
+-export([ping_ack/1]).
+-export([goaway/3]).
+-export([window_update/1]).
+-export([window_update/2]).
+
+-type streamid() :: pos_integer().
+-export_type([streamid/0]).
+
+-type fin() :: fin | nofin.
+-export_type([fin/0]).
+
+-type head_fin() :: head_fin | head_nofin.
+-export_type([head_fin/0]).
+
+-type exclusive() :: exclusive | shared.
+-type weight() :: 1..256.
+-type settings() :: map().
+
+-type error() :: no_error
+ | protocol_error
+ | internal_error
+ | flow_control_error
+ | settings_timeout
+ | stream_closed
+ | frame_size_error
+ | refused_stream
+ | cancel
+ | compression_error
+ | connect_error
+ | enhance_your_calm
+ | inadequate_security
+ | http_1_1_required
+ | unknown_error.
+-export_type([error/0]).
+
+-type frame() :: {data, streamid(), fin(), binary()}
+ | {headers, streamid(), fin(), head_fin(), binary()}
+ | {headers, streamid(), fin(), head_fin(), exclusive(), streamid(), weight(), binary()}
+ | {priority, streamid(), exclusive(), streamid(), weight()}
+ | {rst_stream, streamid(), error()}
+ | {settings, settings()}
+ | settings_ack
+ | {push_promise, streamid(), head_fin(), streamid(), binary()}
+ | {ping, integer()}
+ | {ping_ack, integer()}
+ | {goaway, streamid(), error(), binary()}
+ | {window_update, non_neg_integer()}
+ | {window_update, streamid(), non_neg_integer()}
+ | {continuation, streamid(), head_fin(), binary()}.
+-export_type([frame/0]).
+
+%% Parsing.
+
+-spec parse_sequence(binary())
+ -> {ok, binary()} | more | {connection_error, error(), atom()}.
+parse_sequence(<<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", Rest/bits>>) ->
+ {ok, Rest};
+parse_sequence(Data) when byte_size(Data) >= 24 ->
+ {connection_error, protocol_error,
+ 'The connection preface was invalid. (RFC7540 3.5)'};
+parse_sequence(Data) ->
+ Len = byte_size(Data),
+ <<Preface:Len/binary, _/bits>> = <<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n">>,
+ case Data of
+ Preface ->
+ more;
+ _ ->
+ {connection_error, protocol_error,
+ 'The connection preface was invalid. (RFC7540 3.5)'}
+ end.
+
+parse(<< Len:24, _/bits >>, MaxFrameSize) when Len > MaxFrameSize ->
+ {connection_error, frame_size_error, 'The frame size exceeded SETTINGS_MAX_FRAME_SIZE. (RFC7540 4.2)'};
+parse(Data, _) ->
+ parse(Data).
+
+%%
+%% DATA frames.
+%%
+parse(<< _:24, 0:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'DATA frames MUST be associated with a stream. (RFC7540 6.1)'};
+parse(<< 0:24, 0:8, _:4, 1:1, _:35, _/bits >>) ->
+ {connection_error, frame_size_error, 'DATA frames with padding flag MUST have a length > 0. (RFC7540 6.1)'};
+parse(<< Len0:24, 0:8, _:4, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 ->
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.1)'};
+%% No padding.
+parse(<< Len:24, 0:8, _:4, 0:1, _:2, FlagEndStream:1, _:1, StreamID:31, Data:Len/binary, Rest/bits >>) ->
+ {ok, {data, StreamID, parse_fin(FlagEndStream), Data}, Rest};
+%% Padding.
+parse(<< Len0:24, 0:8, _:4, 1:1, _:2, FlagEndStream:1, _:1, StreamID:31, PadLen:8, Rest0/bits >>)
+ when byte_size(Rest0) >= Len0 - 1 ->
+ Len = Len0 - PadLen - 1,
+ case Rest0 of
+ << Data:Len/binary, 0:PadLen/unit:8, Rest/bits >> ->
+ {ok, {data, StreamID, parse_fin(FlagEndStream), Data}, Rest};
+ _ ->
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.1)'}
+ end;
+%%
+%% HEADERS frames.
+%%
+parse(<< _:24, 1:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'HEADERS frames MUST be associated with a stream. (RFC7540 6.2)'};
+parse(<< 0:24, 1:8, _:4, 1:1, _:35, _/bits >>) ->
+ {connection_error, frame_size_error, 'HEADERS frames with padding flag MUST have a length > 0. (RFC7540 6.1)'};
+parse(<< Len:24, 1:8, _:2, 1:1, _:37, _/bits >>) when Len < 5 ->
+ {connection_error, frame_size_error, 'HEADERS frames with priority flag MUST have a length >= 5. (RFC7540 6.1)'};
+parse(<< Len:24, 1:8, _:2, 1:1, _:1, 1:1, _:35, _/bits >>) when Len < 6 ->
+ {connection_error, frame_size_error, 'HEADERS frames with padding and priority flags MUST have a length >= 6. (RFC7540 6.1)'};
+parse(<< Len0:24, 1:8, _:4, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 ->
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.2)'};
+parse(<< Len0:24, 1:8, _:2, 1:1, _:1, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 - 5 ->
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.2)'};
+%% No padding, no priority.
+parse(<< Len:24, 1:8, _:2, 0:1, _:1, 0:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
+ HeaderBlockFragment:Len/binary, Rest/bits >>) ->
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders), HeaderBlockFragment}, Rest};
+%% Padding, no priority.
+parse(<< Len0:24, 1:8, _:2, 0:1, _:1, 1:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
+ PadLen:8, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 1 ->
+ Len = Len0 - PadLen - 1,
+ case Rest0 of
+ << HeaderBlockFragment:Len/binary, 0:PadLen/unit:8, Rest/bits >> ->
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders), HeaderBlockFragment}, Rest};
+ _ ->
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.2)'}
+ end;
+%% No padding, priority.
+parse(<< _:24, 1:8, _:2, 1:1, _:1, 0:1, _:4, StreamID:31, _:1, StreamID:31, _/bits >>) ->
+ {connection_error, protocol_error,
+ 'HEADERS frames cannot define a stream that depends on itself. (RFC7540 5.3.1)'};
+parse(<< Len0:24, 1:8, _:2, 1:1, _:1, 0:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
+ E:1, DepStreamID:31, Weight:8, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 5 ->
+ Len = Len0 - 5,
+ << HeaderBlockFragment:Len/binary, Rest/bits >> = Rest0,
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders),
+ parse_exclusive(E), DepStreamID, Weight + 1, HeaderBlockFragment}, Rest};
+%% Padding, priority.
+parse(<< _:24, 1:8, _:2, 1:1, _:1, 1:1, _:4, StreamID:31, _:9, StreamID:31, _/bits >>) ->
+ {connection_error, protocol_error,
+ 'HEADERS frames cannot define a stream that depends on itself. (RFC7540 5.3.1)'};
+parse(<< Len0:24, 1:8, _:2, 1:1, _:1, 1:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
+ PadLen:8, E:1, DepStreamID:31, Weight:8, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 6 ->
+ Len = Len0 - PadLen - 6,
+ case Rest0 of
+ << HeaderBlockFragment:Len/binary, 0:PadLen/unit:8, Rest/bits >> ->
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders),
+ parse_exclusive(E), DepStreamID, Weight + 1, HeaderBlockFragment}, Rest};
+ _ ->
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.2)'}
+ end;
+%%
+%% PRIORITY frames.
+%%
+parse(<< 5:24, 2:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'PRIORITY frames MUST be associated with a stream. (RFC7540 6.3)'};
+parse(<< 5:24, 2:8, _:9, StreamID:31, _:1, StreamID:31, _:8, Rest/bits >>) ->
+ {stream_error, StreamID, protocol_error,
+ 'PRIORITY frames cannot make a stream depend on itself. (RFC7540 5.3.1)', Rest};
+parse(<< 5:24, 2:8, _:9, StreamID:31, E:1, DepStreamID:31, Weight:8, Rest/bits >>) ->
+ {ok, {priority, StreamID, parse_exclusive(E), DepStreamID, Weight + 1}, Rest};
+%% @todo Figure out how to best deal with non-fatal frame size errors; if we have everything
+%% then OK if not we might want to inform the caller how much he should expect so that it can
+%% decide if it should just close the connection
+parse(<< BadLen:24, 2:8, _:9, StreamID:31, _:BadLen/binary, Rest/bits >>) ->
+ {stream_error, StreamID, frame_size_error, 'PRIORITY frames MUST be 5 bytes wide. (RFC7540 6.3)', Rest};
+%%
+%% RST_STREAM frames.
+%%
+parse(<< 4:24, 3:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'RST_STREAM frames MUST be associated with a stream. (RFC7540 6.4)'};
+parse(<< 4:24, 3:8, _:9, StreamID:31, ErrorCode:32, Rest/bits >>) ->
+ {ok, {rst_stream, StreamID, parse_error_code(ErrorCode)}, Rest};
+parse(<< BadLen:24, 3:8, _:9, _:31, _/bits >>) when BadLen =/= 4 ->
+ {connection_error, frame_size_error, 'RST_STREAM frames MUST be 4 bytes wide. (RFC7540 6.4)'};
+%%
+%% SETTINGS frames.
+%%
+parse(<< 0:24, 4:8, _:7, 1:1, _:1, 0:31, Rest/bits >>) ->
+ {ok, settings_ack, Rest};
+parse(<< _:24, 4:8, _:7, 1:1, _:1, 0:31, _/bits >>) ->
+ {connection_error, frame_size_error, 'SETTINGS frames with the ACK flag set MUST have a length of 0. (RFC7540 6.5)'};
+parse(<< Len:24, 4:8, _:7, 0:1, _:1, 0:31, _/bits >>) when Len rem 6 =/= 0 ->
+ {connection_error, frame_size_error, 'SETTINGS frames MUST have a length multiple of 6. (RFC7540 6.5)'};
+parse(<< Len:24, 4:8, _:7, 0:1, _:1, 0:31, Rest/bits >>) when byte_size(Rest) >= Len ->
+ parse_settings_payload(Rest, Len, #{});
+parse(<< _:24, 4:8, _:8, _:1, StreamID:31, _/bits >>) when StreamID =/= 0 ->
+ {connection_error, protocol_error, 'SETTINGS frames MUST NOT be associated with a stream. (RFC7540 6.5)'};
+%%
+%% PUSH_PROMISE frames.
+%%
+parse(<< Len:24, 5:8, _:40, _/bits >>) when Len < 4 ->
+ {connection_error, frame_size_error, 'PUSH_PROMISE frames MUST have a length >= 4. (RFC7540 4.2, RFC7540 6.6)'};
+parse(<< Len:24, 5:8, _:4, 1:1, _:35, _/bits >>) when Len < 5 ->
+ {connection_error, frame_size_error, 'PUSH_PROMISE frames with padding flag MUST have a length >= 5. (RFC7540 4.2, RFC7540 6.6)'};
+parse(<< _:24, 5:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'PUSH_PROMISE frames MUST be associated with a stream. (RFC7540 6.6)'};
+parse(<< Len0:24, 5:8, _:4, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 - 4 ->
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.6)'};
+parse(<< Len0:24, 5:8, _:4, 0:1, FlagEndHeaders:1, _:3, StreamID:31, _:1, PromisedStreamID:31, Rest0/bits >>)
+ when byte_size(Rest0) >= Len0 - 4 ->
+ Len = Len0 - 4,
+ << HeaderBlockFragment:Len/binary, Rest/bits >> = Rest0,
+ {ok, {push_promise, StreamID, parse_head_fin(FlagEndHeaders), PromisedStreamID, HeaderBlockFragment}, Rest};
+parse(<< Len0:24, 5:8, _:4, 1:1, FlagEndHeaders:1, _:2, StreamID:31, PadLen:8, _:1, PromisedStreamID:31, Rest0/bits >>)
+ when byte_size(Rest0) >= Len0 - 5 ->
+ Len = Len0 - 5,
+ case Rest0 of
+ << HeaderBlockFragment:Len/binary, 0:PadLen/unit:8, Rest/bits >> ->
+ {ok, {push_promise, StreamID, parse_head_fin(FlagEndHeaders), PromisedStreamID, HeaderBlockFragment}, Rest};
+ _ ->
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.6)'}
+ end;
+%%
+%% PING frames.
+%%
+parse(<< 8:24, 6:8, _:7, 1:1, _:1, 0:31, Opaque:64, Rest/bits >>) ->
+ {ok, {ping_ack, Opaque}, Rest};
+parse(<< 8:24, 6:8, _:7, 0:1, _:1, 0:31, Opaque:64, Rest/bits >>) ->
+ {ok, {ping, Opaque}, Rest};
+parse(<< 8:24, 6:8, _:104, _/bits >>) ->
+ {connection_error, protocol_error, 'PING frames MUST NOT be associated with a stream. (RFC7540 6.7)'};
+parse(<< Len:24, 6:8, _/bits >>) when Len =/= 8 ->
+ {connection_error, frame_size_error, 'PING frames MUST be 8 bytes wide. (RFC7540 6.7)'};
+%%
+%% GOAWAY frames.
+%%
+parse(<< Len0:24, 7:8, _:9, 0:31, _:1, LastStreamID:31, ErrorCode:32, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 8 ->
+ Len = Len0 - 8,
+ << DebugData:Len/binary, Rest/bits >> = Rest0,
+ {ok, {goaway, LastStreamID, parse_error_code(ErrorCode), DebugData}, Rest};
+parse(<< Len:24, 7:8, _:40, _/bits >>) when Len < 8 ->
+ {connection_error, frame_size_error, 'GOAWAY frames MUST have a length >= 8. (RFC7540 4.2, RFC7540 6.8)'};
+parse(<< _:24, 7:8, _:40, _/bits >>) ->
+ {connection_error, protocol_error, 'GOAWAY frames MUST NOT be associated with a stream. (RFC7540 6.8)'};
+%%
+%% WINDOW_UPDATE frames.
+%%
+parse(<< 4:24, 8:8, _:9, 0:31, _:1, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'WINDOW_UPDATE frames MUST have a non-zero increment. (RFC7540 6.9)'};
+parse(<< 4:24, 8:8, _:9, 0:31, _:1, Increment:31, Rest/bits >>) ->
+ {ok, {window_update, Increment}, Rest};
+parse(<< 4:24, 8:8, _:9, StreamID:31, _:1, 0:31, Rest/bits >>) ->
+ {stream_error, StreamID, protocol_error, 'WINDOW_UPDATE frames MUST have a non-zero increment. (RFC7540 6.9)', Rest};
+parse(<< 4:24, 8:8, _:9, StreamID:31, _:1, Increment:31, Rest/bits >>) ->
+ {ok, {window_update, StreamID, Increment}, Rest};
+parse(<< Len:24, 8:8, _/bits >>) when Len =/= 4->
+ {connection_error, frame_size_error, 'WINDOW_UPDATE frames MUST be 4 bytes wide. (RFC7540 6.9)'};
+%%
+%% CONTINUATION frames.
+%%
+parse(<< _:24, 9:8, _:9, 0:31, _/bits >>) ->
+ {connection_error, protocol_error, 'CONTINUATION frames MUST be associated with a stream. (RFC7540 6.10)'};
+parse(<< Len:24, 9:8, _:5, FlagEndHeaders:1, _:3, StreamID:31, HeaderBlockFragment:Len/binary, Rest/bits >>) ->
+ {ok, {continuation, StreamID, parse_head_fin(FlagEndHeaders), HeaderBlockFragment}, Rest};
+%%
+%% Unknown frames are ignored.
+%%
+parse(<< Len:24, Type:8, _:40, _:Len/binary, Rest/bits >>) when Type > 9 ->
+ {ignore, Rest};
+%%
+%% Incomplete frames.
+%%
+parse(_) ->
+ more.
+
+-ifdef(TEST).
+parse_ping_test() ->
+ Ping = ping(1234567890),
+ _ = [more = parse(binary:part(Ping, 0, I)) || I <- lists:seq(1, byte_size(Ping) - 1)],
+ {ok, {ping, 1234567890}, <<>>} = parse(Ping),
+ {ok, {ping, 1234567890}, << 42 >>} = parse(<< Ping/binary, 42 >>),
+ ok.
+
+parse_windows_update_test() ->
+ WindowUpdate = << 4:24, 8:8, 0:9, 0:31, 0:1, 12345:31 >>,
+ _ = [more = parse(binary:part(WindowUpdate, 0, I)) || I <- lists:seq(1, byte_size(WindowUpdate) - 1)],
+ {ok, {window_update, 12345}, <<>>} = parse(WindowUpdate),
+ {ok, {window_update, 12345}, << 42 >>} = parse(<< WindowUpdate/binary, 42 >>),
+ ok.
+
+parse_settings_test() ->
+ more = parse(<< 0:24, 4:8, 1:8, 0:8 >>),
+ {ok, settings_ack, <<>>} = parse(<< 0:24, 4:8, 1:8, 0:32 >>),
+ {connection_error, protocol_error, _} = parse(<< 0:24, 4:8, 1:8, 0:1, 1:31 >>),
+ ok.
+-endif.
+
+parse_fin(0) -> nofin;
+parse_fin(1) -> fin.
+
+parse_head_fin(0) -> head_nofin;
+parse_head_fin(1) -> head_fin.
+
+parse_exclusive(0) -> shared;
+parse_exclusive(1) -> exclusive.
+
+parse_error_code( 0) -> no_error;
+parse_error_code( 1) -> protocol_error;
+parse_error_code( 2) -> internal_error;
+parse_error_code( 3) -> flow_control_error;
+parse_error_code( 4) -> settings_timeout;
+parse_error_code( 5) -> stream_closed;
+parse_error_code( 6) -> frame_size_error;
+parse_error_code( 7) -> refused_stream;
+parse_error_code( 8) -> cancel;
+parse_error_code( 9) -> compression_error;
+parse_error_code(10) -> connect_error;
+parse_error_code(11) -> enhance_your_calm;
+parse_error_code(12) -> inadequate_security;
+parse_error_code(13) -> http_1_1_required;
+parse_error_code(_) -> unknown_error.
+
+parse_settings_payload(SettingsPayload) ->
+ {ok, {settings, Settings}, <<>>}
+ = parse_settings_payload(SettingsPayload, byte_size(SettingsPayload), #{}),
+ Settings.
+
+parse_settings_payload(Rest, 0, Settings) ->
+ {ok, {settings, Settings}, Rest};
+%% SETTINGS_HEADER_TABLE_SIZE.
+parse_settings_payload(<< 1:16, Value:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{header_table_size => Value});
+%% SETTINGS_ENABLE_PUSH.
+parse_settings_payload(<< 2:16, 0:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{enable_push => false});
+parse_settings_payload(<< 2:16, 1:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{enable_push => true});
+parse_settings_payload(<< 2:16, _:32, _/bits >>, _, _) ->
+ {connection_error, protocol_error, 'The SETTINGS_ENABLE_PUSH value MUST be 0 or 1. (RFC7540 6.5.2)'};
+%% SETTINGS_MAX_CONCURRENT_STREAMS.
+parse_settings_payload(<< 3:16, Value:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{max_concurrent_streams => Value});
+%% SETTINGS_INITIAL_WINDOW_SIZE.
+parse_settings_payload(<< 4:16, Value:32, _/bits >>, _, _) when Value > 16#7fffffff ->
+ {connection_error, flow_control_error, 'The maximum SETTINGS_INITIAL_WINDOW_SIZE value is 0x7fffffff. (RFC7540 6.5.2)'};
+parse_settings_payload(<< 4:16, Value:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{initial_window_size => Value});
+%% SETTINGS_MAX_FRAME_SIZE.
+parse_settings_payload(<< 5:16, Value:32, _/bits >>, _, _) when Value =< 16#3fff ->
+ {connection_error, protocol_error, 'The SETTINGS_MAX_FRAME_SIZE value must be > 0x3fff. (RFC7540 6.5.2)'};
+parse_settings_payload(<< 5:16, Value:32, Rest/bits >>, Len, Settings) when Value =< 16#ffffff ->
+ parse_settings_payload(Rest, Len - 6, Settings#{max_frame_size => Value});
+parse_settings_payload(<< 5:16, _:32, _/bits >>, _, _) ->
+ {connection_error, protocol_error, 'The SETTINGS_MAX_FRAME_SIZE value must be =< 0xffffff. (RFC7540 6.5.2)'};
+%% SETTINGS_MAX_HEADER_LIST_SIZE.
+parse_settings_payload(<< 6:16, Value:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{max_header_list_size => Value});
+%% SETTINGS_ENABLE_CONNECT_PROTOCOL.
+parse_settings_payload(<< 8:16, 0:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{enable_connect_protocol => false});
+parse_settings_payload(<< 8:16, 1:32, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings#{enable_connect_protocol => true});
+parse_settings_payload(<< 8:16, _:32, _/bits >>, _, _) ->
+ {connection_error, protocol_error, 'The SETTINGS_ENABLE_CONNECT_PROTOCOL value MUST be 0 or 1. (draft-h2-websockets-01 3)'};
+%% Ignore unknown settings.
+parse_settings_payload(<< _:48, Rest/bits >>, Len, Settings) ->
+ parse_settings_payload(Rest, Len - 6, Settings).
+
+%% Building.
+
+data(StreamID, IsFin, Data) ->
+ [data_header(StreamID, IsFin, iolist_size(Data)), Data].
+
+data_header(StreamID, IsFin, Len) ->
+ FlagEndStream = flag_fin(IsFin),
+ << Len:24, 0:15, FlagEndStream:1, 0:1, StreamID:31 >>.
+
+%% @todo Check size of HeaderBlock and use CONTINUATION frames if needed.
+headers(StreamID, IsFin, HeaderBlock) ->
+ Len = iolist_size(HeaderBlock),
+ FlagEndStream = flag_fin(IsFin),
+ FlagEndHeaders = 1,
+ [<< Len:24, 1:8, 0:5, FlagEndHeaders:1, 0:1, FlagEndStream:1, 0:1, StreamID:31 >>, HeaderBlock].
+
+priority(StreamID, E, DepStreamID, Weight) ->
+ FlagExclusive = exclusive(E),
+ << 5:24, 2:8, 0:9, StreamID:31, FlagExclusive:1, DepStreamID:31, Weight:8 >>.
+
+rst_stream(StreamID, Reason) ->
+ ErrorCode = error_code(Reason),
+ << 4:24, 3:8, 0:9, StreamID:31, ErrorCode:32 >>.
+
+settings(Settings) ->
+ Payload = settings_payload(Settings),
+ Len = iolist_size(Payload),
+ [<< Len:24, 4:8, 0:40 >>, Payload].
+
+settings_payload(Settings) ->
+ [case Key of
+ header_table_size -> <<1:16, Value:32>>;
+ enable_push when Value -> <<2:16, 1:32>>;
+ enable_push -> <<2:16, 0:32>>;
+ max_concurrent_streams when Value =:= infinity -> <<>>;
+ max_concurrent_streams -> <<3:16, Value:32>>;
+ initial_window_size -> <<4:16, Value:32>>;
+ max_frame_size -> <<5:16, Value:32>>;
+ max_header_list_size when Value =:= infinity -> <<>>;
+ max_header_list_size -> <<6:16, Value:32>>;
+ enable_connect_protocol when Value -> <<8:16, 1:32>>;
+ enable_connect_protocol -> <<8:16, 0:32>>
+ end || {Key, Value} <- maps:to_list(Settings)].
+
+settings_ack() ->
+ << 0:24, 4:8, 1:8, 0:32 >>.
+
+%% @todo Check size of HeaderBlock and use CONTINUATION frames if needed.
+push_promise(StreamID, PromisedStreamID, HeaderBlock) ->
+ Len = iolist_size(HeaderBlock) + 4,
+ FlagEndHeaders = 1,
+ [<< Len:24, 5:8, 0:5, FlagEndHeaders:1, 0:3, StreamID:31, 0:1, PromisedStreamID:31 >>, HeaderBlock].
+
+ping(Opaque) ->
+ << 8:24, 6:8, 0:40, Opaque:64 >>.
+
+ping_ack(Opaque) ->
+ << 8:24, 6:8, 0:7, 1:1, 0:32, Opaque:64 >>.
+
+goaway(LastStreamID, Reason, DebugData) ->
+ ErrorCode = error_code(Reason),
+ Len = iolist_size(DebugData) + 8,
+ [<< Len:24, 7:8, 0:41, LastStreamID:31, ErrorCode:32 >>, DebugData].
+
+window_update(Increment) ->
+ window_update(0, Increment).
+
+window_update(StreamID, Increment) when Increment =< 16#7fffffff ->
+ << 4:24, 8:8, 0:8, StreamID:32, 0:1, Increment:31 >>.
+
+flag_fin(nofin) -> 0;
+flag_fin(fin) -> 1.
+
+exclusive(shared) -> 0;
+exclusive(exclusive) -> 1.
+
+error_code(no_error) -> 0;
+error_code(protocol_error) -> 1;
+error_code(internal_error) -> 2;
+error_code(flow_control_error) -> 3;
+error_code(settings_timeout) -> 4;
+error_code(stream_closed) -> 5;
+error_code(frame_size_error) -> 6;
+error_code(refused_stream) -> 7;
+error_code(cancel) -> 8;
+error_code(compression_error) -> 9;
+error_code(connect_error) -> 10;
+error_code(enhance_your_calm) -> 11;
+error_code(inadequate_security) -> 12;
+error_code(http_1_1_required) -> 13.
diff --git a/server/_build/default/lib/cowlib/src/cow_http2_machine.erl b/server/_build/default/lib/cowlib/src/cow_http2_machine.erl
new file mode 100644
index 0000000..87c7b78
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http2_machine.erl
@@ -0,0 +1,1647 @@
+%% Copyright (c) 2018-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http2_machine).
+
+-export([init/2]).
+-export([init_stream/2]).
+-export([init_upgrade_stream/2]).
+-export([frame/2]).
+-export([ignored_frame/1]).
+-export([timeout/3]).
+-export([prepare_headers/5]).
+-export([prepare_push_promise/4]).
+-export([prepare_trailers/3]).
+-export([send_or_queue_data/4]).
+-export([ensure_window/2]).
+-export([ensure_window/3]).
+-export([update_window/2]).
+-export([update_window/3]).
+-export([reset_stream/2]).
+-export([get_connection_local_buffer_size/1]).
+-export([get_local_setting/2]).
+-export([get_remote_settings/1]).
+-export([get_last_streamid/1]).
+-export([set_last_streamid/1]).
+-export([get_stream_local_buffer_size/2]).
+-export([get_stream_local_state/2]).
+-export([get_stream_remote_state/2]).
+-export([is_lingering_stream/2]).
+
+-type opts() :: #{
+ connection_window_margin_size => 0..16#7fffffff,
+ connection_window_update_threshold => 0..16#7fffffff,
+ enable_connect_protocol => boolean(),
+ initial_connection_window_size => 65535..16#7fffffff,
+ initial_stream_window_size => 0..16#7fffffff,
+ max_connection_window_size => 0..16#7fffffff,
+ max_concurrent_streams => non_neg_integer() | infinity,
+ max_decode_table_size => non_neg_integer(),
+ max_encode_table_size => non_neg_integer(),
+ max_frame_size_received => 16384..16777215,
+ max_frame_size_sent => 16384..16777215 | infinity,
+ max_stream_window_size => 0..16#7fffffff,
+ message_tag => any(),
+ preface_timeout => timeout(),
+ settings_timeout => timeout(),
+ stream_window_data_threshold => 0..16#7fffffff,
+ stream_window_margin_size => 0..16#7fffffff,
+ stream_window_update_threshold => 0..16#7fffffff
+}.
+-export_type([opts/0]).
+
+%% The order of the fields is significant.
+-record(sendfile, {
+ offset :: non_neg_integer(),
+ bytes :: pos_integer(),
+ path :: file:name_all()
+}).
+
+-record(stream, {
+ id = undefined :: cow_http2:streamid(),
+
+ %% Request method.
+ method = undefined :: binary(),
+
+ %% Whether we finished sending data.
+ local = idle :: idle | cow_http2:fin(),
+
+ %% Local flow control window (how much we can send).
+ local_window :: integer(),
+
+ %% Buffered data waiting for the flow control window to increase.
+ local_buffer = queue:new() ::
+ queue:queue({cow_http2:fin(), non_neg_integer(), {data, iodata()} | #sendfile{}}),
+ local_buffer_size = 0 :: non_neg_integer(),
+ local_trailers = undefined :: undefined | cow_http:headers(),
+
+ %% Whether we finished receiving data.
+ remote = idle :: idle | cow_http2:fin(),
+
+ %% Remote flow control window (how much we accept to receive).
+ remote_window :: integer(),
+
+ %% Size expected and read from the request body.
+ remote_expected_size = undefined :: undefined | non_neg_integer(),
+ remote_read_size = 0 :: non_neg_integer(),
+
+ %% Unparsed te header. Used to know if we can send trailers.
+ %% Note that we can always send trailers to the server.
+ te :: undefined | binary()
+}).
+
+-type stream() :: #stream{}.
+
+-type continued_frame() ::
+ {headers, cow_http2:streamid(), cow_http2:fin(), cow_http2:head_fin(), binary()} |
+ {push_promise, cow_http2:streamid(), cow_http2:head_fin(), cow_http2:streamid(), binary()}.
+
+-record(http2_machine, {
+ %% Whether the HTTP/2 endpoint is a client or a server.
+ mode :: client | server,
+
+ %% HTTP/2 SETTINGS customization.
+ opts = #{} :: opts(),
+
+ %% Connection-wide frame processing state.
+ state = settings :: settings | normal
+ | {continuation, request | response | trailers | push_promise, continued_frame()},
+
+ %% Timer for the connection preface.
+ preface_timer = undefined :: undefined | reference(),
+
+ %% Timer for the ack for a SETTINGS frame we sent.
+ settings_timer = undefined :: undefined | reference(),
+
+ %% Settings are separate for each endpoint. In addition, settings
+ %% must be acknowledged before they can be expected to be applied.
+ local_settings = #{
+% header_table_size => 4096,
+% enable_push => true,
+% max_concurrent_streams => infinity,
+ initial_window_size => 65535
+% max_frame_size => 16384
+% max_header_list_size => infinity
+ } :: map(),
+ next_settings = undefined :: undefined | map(),
+ remote_settings = #{
+ initial_window_size => 65535
+ } :: map(),
+
+ %% Connection-wide flow control window.
+ local_window = 65535 :: integer(), %% How much we can send.
+ remote_window = 65535 :: integer(), %% How much we accept to receive.
+
+ %% Stream identifiers.
+ local_streamid :: pos_integer(), %% The next streamid to be used.
+ remote_streamid = 0 :: non_neg_integer(), %% The last streamid received.
+ last_remote_streamid = 16#7fffffff :: non_neg_integer(), %% Used in GOAWAY.
+
+ %% Currently active HTTP/2 streams. Streams may be initiated either
+ %% by the client or by the server through PUSH_PROMISE frames.
+ streams = #{} :: #{cow_http2:streamid() => stream()},
+
+ %% HTTP/2 streams that have recently been reset locally.
+ %% We are expected to keep receiving additional frames after
+ %% sending an RST_STREAM.
+ local_lingering_streams = [] :: [cow_http2:streamid()],
+
+ %% HTTP/2 streams that have recently been reset remotely.
+ %% We keep a few of these around in order to reject subsequent
+ %% frames on these streams.
+ remote_lingering_streams = [] :: [cow_http2:streamid()],
+
+ %% HPACK decoding and encoding state.
+ decode_state = cow_hpack:init() :: cow_hpack:state(),
+ encode_state = cow_hpack:init() :: cow_hpack:state()
+}).
+
+-opaque http2_machine() :: #http2_machine{}.
+-export_type([http2_machine/0]).
+
+-type pseudo_headers() :: #{} %% Trailers
+ | #{ %% Responses.
+ status := cow_http:status()
+ } | #{ %% Normal CONNECT requests.
+ method := binary(),
+ authority := binary()
+ } | #{ %% Other requests and extended CONNECT requests.
+ method := binary(),
+ scheme := binary(),
+ authority := binary(),
+ path := binary(),
+ protocol => binary()
+ }.
+
+%% Returns true when the given StreamID is for a local-initiated stream.
+-define(IS_SERVER_LOCAL(StreamID), ((StreamID rem 2) =:= 0)).
+-define(IS_CLIENT_LOCAL(StreamID), ((StreamID rem 2) =:= 1)).
+-define(IS_LOCAL(Mode, StreamID), (
+ ((Mode =:= server) andalso ?IS_SERVER_LOCAL(StreamID))
+ orelse
+ ((Mode =:= client) andalso ?IS_CLIENT_LOCAL(StreamID))
+)).
+
+-spec init(client | server, opts()) -> {ok, iodata(), http2_machine()}.
+init(client, Opts) ->
+ NextSettings = settings_init(Opts),
+ client_preface(#http2_machine{
+ mode=client,
+ opts=Opts,
+ preface_timer=start_timer(preface_timeout, Opts),
+ settings_timer=start_timer(settings_timeout, Opts),
+ next_settings=NextSettings,
+ local_streamid=1
+ });
+init(server, Opts) ->
+ NextSettings = settings_init(Opts),
+ common_preface(#http2_machine{
+ mode=server,
+ opts=Opts,
+ preface_timer=start_timer(preface_timeout, Opts),
+ settings_timer=start_timer(settings_timeout, Opts),
+ next_settings=NextSettings,
+ local_streamid=2
+ }).
+
+%% @todo In Cowlib 3.0 we should always include MessageTag in the message.
+%% It can be set to 'undefined' if the option is missing.
+start_timer(Name, Opts=#{message_tag := MessageTag}) ->
+ case maps:get(Name, Opts, 5000) of
+ infinity -> undefined;
+ Timeout -> erlang:start_timer(Timeout, self(), {?MODULE, MessageTag, Name})
+ end;
+start_timer(Name, Opts) ->
+ case maps:get(Name, Opts, 5000) of
+ infinity -> undefined;
+ Timeout -> erlang:start_timer(Timeout, self(), {?MODULE, Name})
+ end.
+
+client_preface(State0) ->
+ {ok, CommonPreface, State} = common_preface(State0),
+ {ok, [
+ <<"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n">>,
+ CommonPreface
+ ], State}.
+
+%% We send next_settings and use defaults until we get an ack.
+%%
+%% We also send a WINDOW_UPDATE frame for the connection when
+%% the user specified an initial_connection_window_size.
+common_preface(State=#http2_machine{opts=Opts, next_settings=NextSettings}) ->
+ case maps:get(initial_connection_window_size, Opts, 65535) of
+ 65535 ->
+ {ok, cow_http2:settings(NextSettings), State};
+ Size ->
+ {ok, [
+ cow_http2:settings(NextSettings),
+ cow_http2:window_update(Size - 65535)
+ ], update_window(Size - 65535, State)}
+ end.
+
+settings_init(Opts) ->
+ S0 = setting_from_opt(#{}, Opts, max_decode_table_size,
+ header_table_size, 4096),
+ S1 = setting_from_opt(S0, Opts, max_concurrent_streams,
+ max_concurrent_streams, infinity),
+ S2 = setting_from_opt(S1, Opts, initial_stream_window_size,
+ initial_window_size, 65535),
+ S3 = setting_from_opt(S2, Opts, max_frame_size_received,
+ max_frame_size, 16384),
+ %% @todo max_header_list_size
+ setting_from_opt(S3, Opts, enable_connect_protocol,
+ enable_connect_protocol, false).
+
+setting_from_opt(Settings, Opts, OptName, SettingName, Default) ->
+ case maps:get(OptName, Opts, Default) of
+ Default -> Settings;
+ Value -> Settings#{SettingName => Value}
+ end.
+
+-spec init_stream(binary(), State)
+ -> {ok, cow_http2:streamid(), State} when State::http2_machine().
+init_stream(Method, State=#http2_machine{mode=client, local_streamid=LocalStreamID,
+ local_settings=#{initial_window_size := RemoteWindow},
+ remote_settings=#{initial_window_size := LocalWindow}}) ->
+ Stream = #stream{id=LocalStreamID, method=Method,
+ local_window=LocalWindow, remote_window=RemoteWindow},
+ {ok, LocalStreamID, stream_store(Stream, State#http2_machine{
+ local_streamid=LocalStreamID + 2})}.
+
+-spec init_upgrade_stream(binary(), State)
+ -> {ok, cow_http2:streamid(), State} when State::http2_machine().
+init_upgrade_stream(Method, State=#http2_machine{mode=server, remote_streamid=0,
+ local_settings=#{initial_window_size := RemoteWindow},
+ remote_settings=#{initial_window_size := LocalWindow}}) ->
+ Stream = #stream{id=1, method=Method,
+ remote=fin, remote_expected_size=0,
+ local_window=LocalWindow, remote_window=RemoteWindow, te=undefined},
+ {ok, 1, stream_store(Stream, State#http2_machine{remote_streamid=1})}.
+
+-spec frame(cow_http2:frame(), State)
+ -> {ok, State}
+ | {ok, {data, cow_http2:streamid(), cow_http2:fin(), binary()}, State}
+ | {ok, {headers, cow_http2:streamid(), cow_http2:fin(),
+ cow_http:headers(), pseudo_headers(), non_neg_integer() | undefined}, State}
+ | {ok, {trailers, cow_http2:streamid(), cow_http:headers()}, State}
+ | {ok, {rst_stream, cow_http2:streamid(), cow_http2:error()}, State}
+ | {ok, {push_promise, cow_http2:streamid(), cow_http2:streamid(),
+ cow_http:headers(), pseudo_headers()}, State}
+ | {ok, {goaway, cow_http2:streamid(), cow_http2:error(), binary()}, State}
+ | {send, [{cow_http2:streamid(), cow_http2:fin(),
+ [{data, iodata()} | #sendfile{} | {trailers, cow_http:headers()}]}], State}
+ | {error, {stream_error, cow_http2:streamid(), cow_http2:error(), atom()}, State}
+ | {error, {connection_error, cow_http2:error(), atom()}, State}
+ when State::http2_machine().
+frame(Frame, State=#http2_machine{state=settings, preface_timer=TRef}) ->
+ ok = case TRef of
+ undefined -> ok;
+ _ -> erlang:cancel_timer(TRef, [{async, true}, {info, false}])
+ end,
+ settings_frame(Frame, State#http2_machine{state=normal, preface_timer=undefined});
+frame(Frame, State=#http2_machine{state={continuation, _, _}}) ->
+ maybe_discard_result(continuation_frame(Frame, State));
+frame(settings_ack, State=#http2_machine{state=normal}) ->
+ settings_ack_frame(State);
+frame(Frame, State=#http2_machine{state=normal}) ->
+ Result = case element(1, Frame) of
+ data -> data_frame(Frame, State);
+ headers -> headers_frame(Frame, State);
+ priority -> priority_frame(Frame, State);
+ rst_stream -> rst_stream_frame(Frame, State);
+ settings -> settings_frame(Frame, State);
+ push_promise -> push_promise_frame(Frame, State);
+ ping -> ping_frame(Frame, State);
+ ping_ack -> ping_ack_frame(Frame, State);
+ goaway -> goaway_frame(Frame, State);
+ window_update -> window_update_frame(Frame, State);
+ continuation -> unexpected_continuation_frame(Frame, State);
+ _ -> ignored_frame(State)
+ end,
+ maybe_discard_result(Result).
+
+%% RFC7540 6.9. After sending a GOAWAY frame, the sender can discard frames for
+%% streams initiated by the receiver with identifiers higher than the identified
+%% last stream. However, any frames that alter connection state cannot be
+%% completely ignored. For instance, HEADERS, PUSH_PROMISE, and CONTINUATION
+%% frames MUST be minimally processed to ensure the state maintained for header
+%% compression is consistent.
+maybe_discard_result(FrameResult={ok, Result, State=#http2_machine{mode=Mode,
+ last_remote_streamid=MaxID}})
+ when element(1, Result) =/= goaway ->
+ case element(2, Result) of
+ StreamID when StreamID > MaxID, not ?IS_LOCAL(Mode, StreamID) ->
+ {ok, State};
+ _StreamID ->
+ FrameResult
+ end;
+maybe_discard_result(FrameResult) ->
+ FrameResult.
+
+%% DATA frame.
+
+data_frame({data, StreamID, _, _}, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID})
+ when (?IS_LOCAL(Mode, StreamID) andalso (StreamID >= LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID > RemoteStreamID)) ->
+ {error, {connection_error, protocol_error,
+ 'DATA frame received on a stream in idle state. (RFC7540 5.1)'},
+ State};
+data_frame({data, _, _, Data}, State=#http2_machine{remote_window=ConnWindow})
+ when byte_size(Data) > ConnWindow ->
+ {error, {connection_error, flow_control_error,
+ 'DATA frame overflowed the connection flow control window. (RFC7540 6.9, RFC7540 6.9.1)'},
+ State};
+data_frame(Frame={data, StreamID, _, Data}, State0=#http2_machine{
+ remote_window=ConnWindow, local_lingering_streams=Lingering}) ->
+ DataLen = byte_size(Data),
+ State = State0#http2_machine{remote_window=ConnWindow - DataLen},
+ case stream_get(StreamID, State) of
+ #stream{remote_window=StreamWindow} when StreamWindow < DataLen ->
+ stream_reset(StreamID, State, flow_control_error,
+ 'DATA frame overflowed the stream flow control window. (RFC7540 6.9, RFC7540 6.9.1)');
+ Stream = #stream{remote=nofin} ->
+ data_frame(Frame, State, Stream, DataLen);
+ #stream{remote=idle} ->
+ stream_reset(StreamID, State, protocol_error,
+ 'DATA frame received before a HEADERS frame. (RFC7540 8.1, RFC7540 8.1.2.6)');
+ #stream{remote=fin} ->
+ stream_reset(StreamID, State, stream_closed,
+ 'DATA frame received for a half-closed (remote) stream. (RFC7540 5.1)');
+ undefined ->
+ %% After we send an RST_STREAM frame and terminate a stream,
+ %% the remote endpoint might still be sending us some more
+ %% frames until it can process this RST_STREAM.
+ case lists:member(StreamID, Lingering) of
+ true ->
+ {ok, State};
+ false ->
+ {error, {connection_error, stream_closed,
+ 'DATA frame received for a closed stream. (RFC7540 5.1)'},
+ State}
+ end
+ end.
+
+data_frame(Frame={data, _, IsFin, _}, State0, Stream0=#stream{id=StreamID,
+ remote_window=StreamWindow, remote_read_size=StreamRead}, DataLen) ->
+ Stream = Stream0#stream{remote=IsFin,
+ remote_window=StreamWindow - DataLen,
+ remote_read_size=StreamRead + DataLen},
+ State = stream_store(Stream, State0),
+ case is_body_size_valid(Stream) of
+ true ->
+ {ok, Frame, State};
+ false ->
+ stream_reset(StreamID, State, protocol_error,
+ 'The total size of DATA frames is different than the content-length. (RFC7540 8.1.2.6)')
+ end.
+
+%% It's always valid when no content-length header was specified.
+is_body_size_valid(#stream{remote_expected_size=undefined}) ->
+ true;
+%% We didn't finish reading the body but the size is already larger than expected.
+is_body_size_valid(#stream{remote=nofin, remote_expected_size=Expected,
+ remote_read_size=Read}) when Read > Expected ->
+ false;
+is_body_size_valid(#stream{remote=nofin}) ->
+ true;
+is_body_size_valid(#stream{remote=fin, remote_expected_size=Expected,
+ remote_read_size=Expected}) ->
+ true;
+%% We finished reading the body and the size read is not the one expected.
+is_body_size_valid(_) ->
+ false.
+
+%% HEADERS frame.
+%%
+%% We always close the connection when we detect errors before
+%% decoding the headers to not waste resources on non-compliant
+%% endpoints, making us stricter than the RFC requires.
+
+%% Convenience record to manipulate the tuple.
+%% The order of the fields matter.
+-record(headers, {
+ id :: cow_http2:streamid(),
+ fin :: cow_http2:fin(),
+ head :: cow_http2:head_fin(),
+ data :: binary()
+}).
+
+headers_frame(Frame=#headers{}, State=#http2_machine{mode=Mode}) ->
+ case Mode of
+ server -> server_headers_frame(Frame, State);
+ client -> client_headers_frame(Frame, State)
+ end;
+%% @todo Handle the PRIORITY data, but only if this returns an ok tuple.
+%% @todo Do not lose the PRIORITY information if CONTINUATION frames follow.
+headers_frame({headers, StreamID, IsFin, IsHeadFin,
+ _IsExclusive, _DepStreamID, _Weight, HeaderData},
+ State=#http2_machine{mode=Mode}) ->
+ HeadersFrame = #headers{id=StreamID, fin=IsFin, head=IsHeadFin, data=HeaderData},
+ case Mode of
+ server -> server_headers_frame(HeadersFrame, State);
+ client -> client_headers_frame(HeadersFrame, State)
+ end.
+
+%% Reject HEADERS frames with even-numbered streamid.
+server_headers_frame(#headers{id=StreamID}, State)
+ when ?IS_SERVER_LOCAL(StreamID) ->
+ {error, {connection_error, protocol_error,
+ 'HEADERS frame received with even-numbered streamid. (RFC7540 5.1.1)'},
+ State};
+%% HEADERS frame on an idle stream: new request.
+server_headers_frame(Frame=#headers{id=StreamID, head=IsHeadFin},
+ State=#http2_machine{mode=server, remote_streamid=RemoteStreamID})
+ when StreamID > RemoteStreamID ->
+ case IsHeadFin of
+ head_fin ->
+ headers_decode(Frame, State, request, undefined);
+ head_nofin ->
+ {ok, State#http2_machine{state={continuation, request, Frame}}}
+ end;
+%% Either a HEADERS frame received on (half-)closed stream,
+%% or a HEADERS frame containing the trailers.
+server_headers_frame(Frame=#headers{id=StreamID, fin=IsFin, head=IsHeadFin}, State) ->
+ case stream_get(StreamID, State) of
+ %% Trailers.
+ Stream = #stream{remote=nofin} when IsFin =:= fin ->
+ case IsHeadFin of
+ head_fin ->
+ headers_decode(Frame, State, trailers, Stream);
+ head_nofin ->
+ {ok, State#http2_machine{state={continuation, trailers, Frame}}}
+ end;
+ #stream{remote=nofin} ->
+ {error, {connection_error, protocol_error,
+ 'Trailing HEADERS frame received without the END_STREAM flag set. (RFC7540 8.1, RFC7540 8.1.2.6)'},
+ State};
+ _ ->
+ {error, {connection_error, stream_closed,
+ 'HEADERS frame received on a stream in closed or half-closed state. (RFC7540 5.1)'},
+ State}
+ end.
+
+%% Either a HEADERS frame received on an (half-)closed stream,
+%% or a HEADERS frame containing the response or the trailers.
+client_headers_frame(Frame=#headers{id=StreamID, fin=IsFin, head=IsHeadFin},
+ State=#http2_machine{local_streamid=LocalStreamID, remote_streamid=RemoteStreamID})
+ when (?IS_CLIENT_LOCAL(StreamID) andalso (StreamID < LocalStreamID))
+ orelse ((not ?IS_CLIENT_LOCAL(StreamID)) andalso (StreamID =< RemoteStreamID)) ->
+ case stream_get(StreamID, State) of
+ Stream = #stream{remote=idle} ->
+ case IsHeadFin of
+ head_fin ->
+ headers_decode(Frame, State, response, Stream);
+ head_nofin ->
+ {ok, State#http2_machine{state={continuation, response, Frame}}}
+ end;
+ Stream = #stream{remote=nofin} when IsFin =:= fin ->
+ case IsHeadFin of
+ head_fin ->
+ headers_decode(Frame, State, trailers, Stream);
+ head_nofin ->
+ {ok, State#http2_machine{state={continuation, trailers, Frame}}}
+ end;
+ #stream{remote=nofin} ->
+ {error, {connection_error, protocol_error,
+ 'Trailing HEADERS frame received without the END_STREAM flag set. (RFC7540 8.1, RFC7540 8.1.2.6)'},
+ State};
+ _ ->
+ {error, {connection_error, stream_closed,
+ 'HEADERS frame received on a stream in closed or half-closed state. (RFC7540 5.1)'},
+ State}
+ end;
+%% Reject HEADERS frames received on idle streams.
+client_headers_frame(_, State) ->
+ {error, {connection_error, protocol_error,
+ 'HEADERS frame received on an idle stream. (RFC7540 5.1.1)'},
+ State}.
+
+headers_decode(Frame=#headers{head=head_fin, data=HeaderData},
+ State=#http2_machine{decode_state=DecodeState0}, Type, Stream) ->
+ try cow_hpack:decode(HeaderData, DecodeState0) of
+ {Headers, DecodeState} when Type =:= request ->
+ headers_enforce_concurrency_limit(Frame,
+ State#http2_machine{decode_state=DecodeState}, Type, Stream, Headers);
+ {Headers, DecodeState} ->
+ headers_pseudo_headers(Frame,
+ State#http2_machine{decode_state=DecodeState}, Type, Stream, Headers)
+ catch _:_ ->
+ {error, {connection_error, compression_error,
+ 'Error while trying to decode HPACK-encoded header block. (RFC7540 4.3)'},
+ State}
+ end.
+
+headers_enforce_concurrency_limit(Frame=#headers{id=StreamID},
+ State=#http2_machine{local_settings=LocalSettings, streams=Streams},
+ Type, Stream, Headers) ->
+ MaxConcurrentStreams = maps:get(max_concurrent_streams, LocalSettings, infinity),
+ %% Using < is correct because this new stream is not included
+ %% in the Streams variable yet and so we'll end up with +1 stream.
+ case map_size(Streams) < MaxConcurrentStreams of
+ true ->
+ headers_pseudo_headers(Frame, State, Type, Stream, Headers);
+ false ->
+ {error, {stream_error, StreamID, refused_stream,
+ 'Maximum number of concurrent streams has been reached. (RFC7540 5.1.2)'},
+ State}
+ end.
+
+headers_pseudo_headers(Frame, State=#http2_machine{local_settings=LocalSettings},
+ Type, Stream, Headers0) when Type =:= request; Type =:= push_promise ->
+ IsExtendedConnectEnabled = maps:get(enable_connect_protocol, LocalSettings, false),
+ case request_pseudo_headers(Headers0, #{}) of
+ %% Extended CONNECT method (RFC8441).
+ {ok, PseudoHeaders=#{method := <<"CONNECT">>, scheme := _,
+ authority := _, path := _, protocol := _}, Headers}
+ when IsExtendedConnectEnabled ->
+ headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ {ok, #{method := <<"CONNECT">>, scheme := _,
+ authority := _, path := _}, _}
+ when IsExtendedConnectEnabled ->
+ headers_malformed(Frame, State,
+ 'The :protocol pseudo-header MUST be sent with an extended CONNECT. (RFC8441 4)');
+ {ok, #{protocol := _}, _} ->
+ headers_malformed(Frame, State,
+ 'The :protocol pseudo-header is only defined for the extended CONNECT. (RFC8441 4)');
+ %% Normal CONNECT (no scheme/path).
+ {ok, PseudoHeaders=#{method := <<"CONNECT">>, authority := _}, Headers}
+ when map_size(PseudoHeaders) =:= 2 ->
+ headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ {ok, #{method := <<"CONNECT">>}, _} ->
+ headers_malformed(Frame, State,
+ 'CONNECT requests only use the :method and :authority pseudo-headers. (RFC7540 8.3)');
+ %% Other requests.
+ {ok, PseudoHeaders=#{method := _, scheme := _, path := _}, Headers} ->
+ headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ {ok, _, _} ->
+ headers_malformed(Frame, State,
+ 'A required pseudo-header was not found. (RFC7540 8.1.2.3)');
+ {error, HumanReadable} ->
+ headers_malformed(Frame, State, HumanReadable)
+ end;
+headers_pseudo_headers(Frame=#headers{id=StreamID},
+ State, Type=response, Stream, Headers0) ->
+ case response_pseudo_headers(Headers0, #{}) of
+ {ok, PseudoHeaders=#{status := _}, Headers} ->
+ headers_regular_headers(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ {ok, _, _} ->
+ stream_reset(StreamID, State, protocol_error,
+ 'A required pseudo-header was not found. (RFC7540 8.1.2.4)');
+ {error, HumanReadable} ->
+ stream_reset(StreamID, State, protocol_error, HumanReadable)
+ end;
+headers_pseudo_headers(Frame=#headers{id=StreamID},
+ State, Type=trailers, Stream, Headers) ->
+ case trailers_contain_pseudo_headers(Headers) of
+ false ->
+ headers_regular_headers(Frame, State, Type, Stream, #{}, Headers);
+ true ->
+ stream_reset(StreamID, State, protocol_error,
+ 'Trailer header blocks must not contain pseudo-headers. (RFC7540 8.1.2.1)')
+ end.
+
+headers_malformed(#headers{id=StreamID}, State, HumanReadable) ->
+ {error, {stream_error, StreamID, protocol_error, HumanReadable}, State}.
+
+request_pseudo_headers([{<<":method">>, _}|_], #{method := _}) ->
+ {error, 'Multiple :method pseudo-headers were found. (RFC7540 8.1.2.3)'};
+request_pseudo_headers([{<<":method">>, Method}|Tail], PseudoHeaders) ->
+ request_pseudo_headers(Tail, PseudoHeaders#{method => Method});
+request_pseudo_headers([{<<":scheme">>, _}|_], #{scheme := _}) ->
+ {error, 'Multiple :scheme pseudo-headers were found. (RFC7540 8.1.2.3)'};
+request_pseudo_headers([{<<":scheme">>, Scheme}|Tail], PseudoHeaders) ->
+ request_pseudo_headers(Tail, PseudoHeaders#{scheme => Scheme});
+request_pseudo_headers([{<<":authority">>, _}|_], #{authority := _}) ->
+ {error, 'Multiple :authority pseudo-headers were found. (RFC7540 8.1.2.3)'};
+request_pseudo_headers([{<<":authority">>, Authority}|Tail], PseudoHeaders) ->
+ request_pseudo_headers(Tail, PseudoHeaders#{authority => Authority});
+request_pseudo_headers([{<<":path">>, _}|_], #{path := _}) ->
+ {error, 'Multiple :path pseudo-headers were found. (RFC7540 8.1.2.3)'};
+request_pseudo_headers([{<<":path">>, Path}|Tail], PseudoHeaders) ->
+ request_pseudo_headers(Tail, PseudoHeaders#{path => Path});
+request_pseudo_headers([{<<":protocol">>, _}|_], #{protocol := _}) ->
+ {error, 'Multiple :protocol pseudo-headers were found. (RFC7540 8.1.2.3)'};
+request_pseudo_headers([{<<":protocol">>, Protocol}|Tail], PseudoHeaders) ->
+ request_pseudo_headers(Tail, PseudoHeaders#{protocol => Protocol});
+request_pseudo_headers([{<<":", _/bits>>, _}|_], _) ->
+ {error, 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)'};
+request_pseudo_headers(Headers, PseudoHeaders) ->
+ {ok, PseudoHeaders, Headers}.
+
+response_pseudo_headers([{<<":status">>, _}|_], #{status := _}) ->
+ {error, 'Multiple :status pseudo-headers were found. (RFC7540 8.1.2.3)'};
+response_pseudo_headers([{<<":status">>, Status}|Tail], PseudoHeaders) ->
+ try cow_http:status_to_integer(Status) of
+ IntStatus ->
+ response_pseudo_headers(Tail, PseudoHeaders#{status => IntStatus})
+ catch _:_ ->
+ {error, 'The :status pseudo-header value is invalid. (RFC7540 8.1.2.4)'}
+ end;
+response_pseudo_headers([{<<":", _/bits>>, _}|_], _) ->
+ {error, 'An unknown or invalid pseudo-header was found. (RFC7540 8.1.2.1)'};
+response_pseudo_headers(Headers, PseudoHeaders) ->
+ {ok, PseudoHeaders, Headers}.
+
+trailers_contain_pseudo_headers([]) ->
+ false;
+trailers_contain_pseudo_headers([{<<":", _/bits>>, _}|_]) ->
+ true;
+trailers_contain_pseudo_headers([_|Tail]) ->
+ trailers_contain_pseudo_headers(Tail).
+
+%% Rejecting invalid regular headers might be a bit too strong for clients.
+headers_regular_headers(Frame=#headers{id=StreamID},
+ State, Type, Stream, PseudoHeaders, Headers) ->
+ case regular_headers(Headers, Type) of
+ ok when Type =:= request ->
+ request_expected_size(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ ok when Type =:= push_promise ->
+ push_promise_frame(Frame, State, Stream, PseudoHeaders, Headers);
+ ok when Type =:= response ->
+ response_expected_size(Frame, State, Type, Stream, PseudoHeaders, Headers);
+ ok when Type =:= trailers ->
+ trailers_frame(Frame, State, Stream, Headers);
+ {error, HumanReadable} when Type =:= request ->
+ headers_malformed(Frame, State, HumanReadable);
+ {error, HumanReadable} ->
+ stream_reset(StreamID, State, protocol_error, HumanReadable)
+ end.
+
+regular_headers([{<<>>, _}|_], _) ->
+ {error, 'Empty header names are not valid regular headers. (CVE-2019-9516)'};
+regular_headers([{<<":", _/bits>>, _}|_], _) ->
+ {error, 'Pseudo-headers were found after regular headers. (RFC7540 8.1.2.1)'};
+regular_headers([{<<"connection">>, _}|_], _) ->
+ {error, 'The connection header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"keep-alive">>, _}|_], _) ->
+ {error, 'The keep-alive header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"proxy-authenticate">>, _}|_], _) ->
+ {error, 'The proxy-authenticate header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"proxy-authorization">>, _}|_], _) ->
+ {error, 'The proxy-authorization header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"transfer-encoding">>, _}|_], _) ->
+ {error, 'The transfer-encoding header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"upgrade">>, _}|_], _) ->
+ {error, 'The upgrade header is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"te">>, Value}|_], request) when Value =/= <<"trailers">> ->
+ {error, 'The te header with a value other than "trailers" is not allowed. (RFC7540 8.1.2.2)'};
+regular_headers([{<<"te">>, _}|_], Type) when Type =/= request ->
+ {error, 'The te header is only allowed in request headers. (RFC7540 8.1.2.2)'};
+regular_headers([{Name, _}|Tail], Type) ->
+ Pattern = [
+ <<$A>>, <<$B>>, <<$C>>, <<$D>>, <<$E>>, <<$F>>, <<$G>>, <<$H>>, <<$I>>,
+ <<$J>>, <<$K>>, <<$L>>, <<$M>>, <<$N>>, <<$O>>, <<$P>>, <<$Q>>, <<$R>>,
+ <<$S>>, <<$T>>, <<$U>>, <<$V>>, <<$W>>, <<$X>>, <<$Y>>, <<$Z>>
+ ],
+ case binary:match(Name, Pattern) of
+ nomatch -> regular_headers(Tail, Type);
+ _ -> {error, 'Header names must be lowercase. (RFC7540 8.1.2)'}
+ end;
+regular_headers([], _) ->
+ ok.
+
+request_expected_size(Frame=#headers{fin=IsFin}, State, Type, Stream, PseudoHeaders, Headers) ->
+ case [CL || {<<"content-length">>, CL} <- Headers] of
+ [] when IsFin =:= fin ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [] ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, undefined);
+ [<<"0">>] when IsFin =:= fin ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [_] when IsFin =:= fin ->
+ headers_malformed(Frame, State,
+ 'HEADERS frame with the END_STREAM flag contains a non-zero content-length. (RFC7540 8.1.2.6)');
+ [BinLen] ->
+ headers_parse_expected_size(Frame, State, Type, Stream,
+ PseudoHeaders, Headers, BinLen);
+ _ ->
+ headers_malformed(Frame, State,
+ 'Multiple content-length headers were received. (RFC7230 3.3.2)')
+ end.
+
+response_expected_size(Frame=#headers{id=StreamID, fin=IsFin}, State, Type,
+ Stream=#stream{method=Method}, PseudoHeaders=#{status := Status}, Headers) ->
+ case [CL || {<<"content-length">>, CL} <- Headers] of
+ [] when IsFin =:= fin ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [] ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, undefined);
+ [_] when Status >= 100, Status =< 199 ->
+ stream_reset(StreamID, State, protocol_error,
+ 'Content-length header received in a 1xx response. (RFC7230 3.3.2)');
+ [_] when Status =:= 204 ->
+ stream_reset(StreamID, State, protocol_error,
+ 'Content-length header received in a 204 response. (RFC7230 3.3.2)');
+ [_] when Status >= 200, Status =< 299, Method =:= <<"CONNECT">> ->
+ stream_reset(StreamID, State, protocol_error,
+ 'Content-length header received in a 2xx response to a CONNECT request. (RFC7230 3.3.2).');
+ %% Responses to HEAD requests, and 304 responses may contain
+ %% a content-length header that must be ignored. (RFC7230 3.3.2)
+ [_] when Method =:= <<"HEAD">> ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [_] when Status =:= 304 ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [<<"0">>] when IsFin =:= fin ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, 0);
+ [_] when IsFin =:= fin ->
+ stream_reset(StreamID, State, protocol_error,
+ 'HEADERS frame with the END_STREAM flag contains a non-zero content-length. (RFC7540 8.1.2.6)');
+ [BinLen] ->
+ headers_parse_expected_size(Frame, State, Type, Stream,
+ PseudoHeaders, Headers, BinLen);
+ _ ->
+ stream_reset(StreamID, State, protocol_error,
+ 'Multiple content-length headers were received. (RFC7230 3.3.2)')
+ end.
+
+headers_parse_expected_size(Frame=#headers{id=StreamID},
+ State, Type, Stream, PseudoHeaders, Headers, BinLen) ->
+ try cow_http_hd:parse_content_length(BinLen) of
+ Len ->
+ headers_frame(Frame, State, Type, Stream, PseudoHeaders, Headers, Len)
+ catch
+ _:_ ->
+ HumanReadable = 'The content-length header is invalid. (RFC7230 3.3.2)',
+ case Type of
+ request -> headers_malformed(Frame, State, HumanReadable);
+ response -> stream_reset(StreamID, State, protocol_error, HumanReadable)
+ end
+ end.
+
+headers_frame(#headers{id=StreamID, fin=IsFin}, State0=#http2_machine{
+ local_settings=#{initial_window_size := RemoteWindow},
+ remote_settings=#{initial_window_size := LocalWindow}},
+ Type, Stream0, PseudoHeaders, Headers, Len) ->
+ {Stream, State1} = case Type of
+ request ->
+ TE = case lists:keyfind(<<"te">>, 1, Headers) of
+ {_, TE0} -> TE0;
+ false -> undefined
+ end,
+ {#stream{id=StreamID, method=maps:get(method, PseudoHeaders),
+ remote=IsFin, remote_expected_size=Len,
+ local_window=LocalWindow, remote_window=RemoteWindow, te=TE},
+ State0#http2_machine{remote_streamid=StreamID}};
+ response ->
+ Stream1 = case PseudoHeaders of
+ #{status := Status} when Status >= 100, Status =< 199 -> Stream0;
+ _ -> Stream0#stream{remote=IsFin, remote_expected_size=Len}
+ end,
+ {Stream1, State0}
+ end,
+ State = stream_store(Stream, State1),
+ {ok, {headers, StreamID, IsFin, Headers, PseudoHeaders, Len}, State}.
+
+trailers_frame(#headers{id=StreamID}, State0, Stream0, Headers) ->
+ Stream = Stream0#stream{remote=fin},
+ State = stream_store(Stream, State0),
+ case is_body_size_valid(Stream) of
+ true ->
+ {ok, {trailers, StreamID, Headers}, State};
+ false ->
+ stream_reset(StreamID, State, protocol_error,
+ 'The total size of DATA frames is different than the content-length. (RFC7540 8.1.2.6)')
+ end.
+
+%% PRIORITY frame.
+%%
+%% @todo Handle PRIORITY frames.
+
+priority_frame(_Frame, State) ->
+ {ok, State}.
+
+%% RST_STREAM frame.
+
+rst_stream_frame({rst_stream, StreamID, _}, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID})
+ when (?IS_LOCAL(Mode, StreamID) andalso (StreamID >= LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID > RemoteStreamID)) ->
+ {error, {connection_error, protocol_error,
+ 'RST_STREAM frame received on a stream in idle state. (RFC7540 5.1)'},
+ State};
+rst_stream_frame({rst_stream, StreamID, Reason}, State=#http2_machine{
+ streams=Streams0, remote_lingering_streams=Lingering0}) ->
+ Streams = maps:remove(StreamID, Streams0),
+ %% We only keep up to 10 streams in this state. @todo Make it configurable?
+ Lingering = [StreamID|lists:sublist(Lingering0, 10 - 1)],
+ {ok, {rst_stream, StreamID, Reason},
+ State#http2_machine{streams=Streams, remote_lingering_streams=Lingering}}.
+
+%% SETTINGS frame.
+
+settings_frame({settings, Settings}, State0=#http2_machine{
+ opts=Opts, remote_settings=Settings0}) ->
+ State1 = State0#http2_machine{remote_settings=maps:merge(Settings0, Settings)},
+ State2 = maps:fold(fun
+ (header_table_size, NewSize, State=#http2_machine{encode_state=EncodeState0}) ->
+ MaxSize = maps:get(max_encode_table_size, Opts, 4096),
+ EncodeState = cow_hpack:set_max_size(min(NewSize, MaxSize), EncodeState0),
+ State#http2_machine{encode_state=EncodeState};
+ (initial_window_size, NewWindowSize, State) ->
+ OldWindowSize = maps:get(initial_window_size, Settings0, 65535),
+ streams_update_local_window(State, NewWindowSize - OldWindowSize);
+ (_, _, State) ->
+ State
+ end, State1, Settings),
+ case Settings of
+ #{initial_window_size := _} -> send_data(State2);
+ _ -> {ok, State2}
+ end;
+%% We expect to receive a SETTINGS frame as part of the preface.
+settings_frame(_F, State=#http2_machine{mode=server}) ->
+ {error, {connection_error, protocol_error,
+ 'The preface sequence must be followed by a SETTINGS frame. (RFC7540 3.5)'},
+ State};
+settings_frame(_F, State) ->
+ {error, {connection_error, protocol_error,
+ 'The preface must begin with a SETTINGS frame. (RFC7540 3.5)'},
+ State}.
+
+%% When SETTINGS_INITIAL_WINDOW_SIZE changes we need to update
+%% the local stream windows for all active streams and perhaps
+%% resume sending data.
+streams_update_local_window(State=#http2_machine{streams=Streams0}, Increment) ->
+ Streams = maps:map(fun(_, S=#stream{local_window=StreamWindow}) ->
+ S#stream{local_window=StreamWindow + Increment}
+ end, Streams0),
+ State#http2_machine{streams=Streams}.
+
+%% Ack for a previously sent SETTINGS frame.
+
+settings_ack_frame(State0=#http2_machine{settings_timer=TRef,
+ local_settings=Local0, next_settings=NextSettings}) ->
+ ok = case TRef of
+ undefined -> ok;
+ _ -> erlang:cancel_timer(TRef, [{async, true}, {info, false}])
+ end,
+ Local = maps:merge(Local0, NextSettings),
+ State1 = State0#http2_machine{settings_timer=undefined,
+ local_settings=Local, next_settings=#{}},
+ {ok, maps:fold(fun
+ (header_table_size, MaxSize, State=#http2_machine{decode_state=DecodeState0}) ->
+ DecodeState = cow_hpack:set_max_size(MaxSize, DecodeState0),
+ State#http2_machine{decode_state=DecodeState};
+ (initial_window_size, NewWindowSize, State) ->
+ OldWindowSize = maps:get(initial_window_size, Local0, 65535),
+ streams_update_remote_window(State, NewWindowSize - OldWindowSize);
+ (_, _, State) ->
+ State
+ end, State1, NextSettings)}.
+
+%% When we receive an ack to a SETTINGS frame we sent we need to update
+%% the remote stream windows for all active streams.
+streams_update_remote_window(State=#http2_machine{streams=Streams0}, Increment) ->
+ Streams = maps:map(fun(_, S=#stream{remote_window=StreamWindow}) ->
+ S#stream{remote_window=StreamWindow + Increment}
+ end, Streams0),
+ State#http2_machine{streams=Streams}.
+
+%% PUSH_PROMISE frame.
+
+%% Convenience record to manipulate the tuple.
+%% The order of the fields matter.
+-record(push_promise, {
+ id :: cow_http2:streamid(),
+ head :: cow_http2:head_fin(),
+ promised_id :: cow_http2:streamid(),
+ data :: binary()
+}).
+
+push_promise_frame(_, State=#http2_machine{mode=server}) ->
+ {error, {connection_error, protocol_error,
+ 'PUSH_PROMISE frames MUST NOT be sent by the client. (RFC7540 6.6)'},
+ State};
+push_promise_frame(_, State=#http2_machine{local_settings=#{enable_push := false}}) ->
+ {error, {connection_error, protocol_error,
+ 'PUSH_PROMISE frame received despite SETTINGS_ENABLE_PUSH set to 0. (RFC7540 6.6)'},
+ State};
+push_promise_frame(#push_promise{promised_id=PromisedStreamID},
+ State=#http2_machine{remote_streamid=RemoteStreamID})
+ when PromisedStreamID =< RemoteStreamID ->
+ {error, {connection_error, protocol_error,
+ 'PUSH_PROMISE frame received for a promised stream in closed or half-closed state. (RFC7540 5.1, RFC7540 6.6)'},
+ State};
+push_promise_frame(#push_promise{id=StreamID}, State)
+ when not ?IS_CLIENT_LOCAL(StreamID) ->
+ {error, {connection_error, protocol_error,
+ 'PUSH_PROMISE frame received on a server-initiated stream. (RFC7540 6.6)'},
+ State};
+push_promise_frame(Frame=#push_promise{id=StreamID, head=IsHeadFin,
+ promised_id=PromisedStreamID, data=HeaderData}, State) ->
+ case stream_get(StreamID, State) of
+ Stream=#stream{remote=idle} ->
+ case IsHeadFin of
+ head_fin ->
+ headers_decode(#headers{id=PromisedStreamID,
+ fin=fin, head=IsHeadFin, data=HeaderData},
+ State, push_promise, Stream);
+ head_nofin ->
+ {ok, State#http2_machine{state={continuation, push_promise, Frame}}}
+ end;
+ _ ->
+%% @todo Check if the stream is lingering. If it is, decode the frame
+%% and do what? That's the big question and why it's not implemented yet.
+% However, an endpoint that
+% has sent RST_STREAM on the associated stream MUST handle PUSH_PROMISE
+% frames that might have been created before the RST_STREAM frame is
+% received and processed. (RFC7540 6.6)
+ {error, {connection_error, stream_closed,
+ 'PUSH_PROMISE frame received on a stream in closed or half-closed state. (RFC7540 5.1, RFC7540 6.6)'},
+ State}
+ end.
+
+push_promise_frame(#headers{id=PromisedStreamID},
+ State0=#http2_machine{
+ local_settings=#{initial_window_size := RemoteWindow},
+ remote_settings=#{initial_window_size := LocalWindow}},
+ #stream{id=StreamID}, PseudoHeaders=#{method := Method}, Headers) ->
+ TE = case lists:keyfind(<<"te">>, 1, Headers) of
+ {_, TE0} -> TE0;
+ false -> undefined
+ end,
+ PromisedStream = #stream{id=PromisedStreamID, method=Method,
+ local=fin, local_window=LocalWindow,
+ remote_window=RemoteWindow, te=TE},
+ State = stream_store(PromisedStream,
+ State0#http2_machine{remote_streamid=PromisedStreamID}),
+ {ok, {push_promise, StreamID, PromisedStreamID, Headers, PseudoHeaders}, State}.
+
+%% PING frame.
+
+ping_frame({ping, _}, State) ->
+ {ok, State}.
+
+%% Ack for a previously sent PING frame.
+%%
+%% @todo Might want to check contents but probably a waste of time.
+
+ping_ack_frame({ping_ack, _}, State) ->
+ {ok, State}.
+
+%% GOAWAY frame.
+
+goaway_frame(Frame={goaway, _, _, _}, State) ->
+ {ok, Frame, State}.
+
+%% WINDOW_UPDATE frame.
+
+%% Connection-wide WINDOW_UPDATE frame.
+window_update_frame({window_update, Increment}, State=#http2_machine{local_window=ConnWindow})
+ when ConnWindow + Increment > 16#7fffffff ->
+ {error, {connection_error, flow_control_error,
+ 'The flow control window must not be greater than 2^31-1. (RFC7540 6.9.1)'},
+ State};
+window_update_frame({window_update, Increment}, State=#http2_machine{local_window=ConnWindow}) ->
+ send_data(State#http2_machine{local_window=ConnWindow + Increment});
+%% Stream-specific WINDOW_UPDATE frame.
+window_update_frame({window_update, StreamID, _}, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID})
+ when (?IS_LOCAL(Mode, StreamID) andalso (StreamID >= LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID > RemoteStreamID)) ->
+ {error, {connection_error, protocol_error,
+ 'WINDOW_UPDATE frame received on a stream in idle state. (RFC7540 5.1)'},
+ State};
+window_update_frame({window_update, StreamID, Increment},
+ State0=#http2_machine{remote_lingering_streams=Lingering}) ->
+ case stream_get(StreamID, State0) of
+ #stream{local_window=StreamWindow} when StreamWindow + Increment > 16#7fffffff ->
+ stream_reset(StreamID, State0, flow_control_error,
+ 'The flow control window must not be greater than 2^31-1. (RFC7540 6.9.1)');
+ Stream0 = #stream{local_window=StreamWindow} ->
+ send_data(Stream0#stream{local_window=StreamWindow + Increment}, State0);
+ undefined ->
+ %% WINDOW_UPDATE frames may be received for a short period of time
+ %% after a stream is closed. They must be ignored.
+ case lists:member(StreamID, Lingering) of
+ false -> {ok, State0};
+ true -> stream_reset(StreamID, State0, stream_closed,
+ 'WINDOW_UPDATE frame received after the stream was reset. (RFC7540 5.1)')
+ end
+ end.
+
+%% CONTINUATION frame.
+
+%% Convenience record to manipulate the tuple.
+%% The order of the fields matter.
+-record(continuation, {
+ id :: cow_http2:streamid(),
+ head :: cow_http2:head_fin(),
+ data :: binary()
+}).
+
+unexpected_continuation_frame(#continuation{}, State) ->
+ {error, {connection_error, protocol_error,
+ 'CONTINUATION frames MUST be preceded by a HEADERS or PUSH_PROMISE frame. (RFC7540 6.10)'},
+ State}.
+
+continuation_frame(#continuation{id=StreamID, head=head_fin, data=HeaderFragment1},
+ State=#http2_machine{state={continuation, Type,
+ Frame=#headers{id=StreamID, data=HeaderFragment0}}}) ->
+ HeaderData = <<HeaderFragment0/binary, HeaderFragment1/binary>>,
+ headers_decode(Frame#headers{head=head_fin, data=HeaderData},
+ State#http2_machine{state=normal}, Type, stream_get(StreamID, State));
+continuation_frame(#continuation{id=StreamID, head=head_fin, data=HeaderFragment1},
+ State=#http2_machine{state={continuation, Type, #push_promise{
+ id=StreamID, promised_id=PromisedStreamID, data=HeaderFragment0}}}) ->
+ HeaderData = <<HeaderFragment0/binary, HeaderFragment1/binary>>,
+ headers_decode(#headers{id=PromisedStreamID, fin=fin, head=head_fin, data=HeaderData},
+ State#http2_machine{state=normal}, Type, undefined);
+continuation_frame(#continuation{id=StreamID, data=HeaderFragment1},
+ State=#http2_machine{state={continuation, Type, ContinuedFrame0}})
+ when element(2, ContinuedFrame0) =:= StreamID ->
+ ContinuedFrame = case ContinuedFrame0 of
+ #headers{data=HeaderFragment0} ->
+ HeaderData = <<HeaderFragment0/binary, HeaderFragment1/binary>>,
+ ContinuedFrame0#headers{data=HeaderData};
+ #push_promise{data=HeaderFragment0} ->
+ HeaderData = <<HeaderFragment0/binary, HeaderFragment1/binary>>,
+ ContinuedFrame0#push_promise{data=HeaderData}
+ end,
+ {ok, State#http2_machine{state={continuation, Type, ContinuedFrame}}};
+continuation_frame(_F, State) ->
+ {error, {connection_error, protocol_error,
+ 'An invalid frame was received in the middle of a header block. (RFC7540 6.2)'},
+ State}.
+
+%% Ignored frames.
+
+-spec ignored_frame(State)
+ -> {ok, State}
+ | {error, {connection_error, protocol_error, atom()}, State}
+ when State::http2_machine().
+ignored_frame(State=#http2_machine{state={continuation, _, _}}) ->
+ {error, {connection_error, protocol_error,
+ 'An invalid frame was received in the middle of a header block. (RFC7540 6.2)'},
+ State};
+%% @todo It might be useful to error out when we receive
+%% too many unknown frames. (RFC7540 10.5)
+ignored_frame(State) ->
+ {ok, State}.
+
+%% Timeouts.
+
+-spec timeout(preface_timeout | settings_timeout, reference(), State)
+ -> {ok, State}
+ | {error, {connection_error, cow_http2:error(), atom()}, State}
+ when State::http2_machine().
+timeout(preface_timeout, TRef, State=#http2_machine{preface_timer=TRef}) ->
+ {error, {connection_error, protocol_error,
+ 'The preface was not received in a reasonable amount of time.'},
+ State};
+timeout(settings_timeout, TRef, State=#http2_machine{settings_timer=TRef}) ->
+ {error, {connection_error, settings_timeout,
+ 'The SETTINGS ack was not received within the configured time. (RFC7540 6.5.3)'},
+ State};
+timeout(_, _, State) ->
+ {ok, State}.
+
+%% Functions for sending a message header or body. Note that
+%% this module does not send data directly, instead it returns
+%% a value that can then be used to send the frames.
+
+-spec prepare_headers(cow_http2:streamid(), State, idle | cow_http2:fin(),
+ pseudo_headers(), cow_http:headers())
+ -> {ok, cow_http2:fin(), iodata(), State} when State::http2_machine().
+prepare_headers(StreamID, State=#http2_machine{encode_state=EncodeState0},
+ IsFin0, PseudoHeaders, Headers0) ->
+ Stream = #stream{method=Method, local=idle} = stream_get(StreamID, State),
+ IsFin = case {IsFin0, Method} of
+ {idle, _} -> nofin;
+ {_, <<"HEAD">>} -> fin;
+ _ -> IsFin0
+ end,
+ Headers = merge_pseudo_headers(PseudoHeaders, remove_http11_headers(Headers0)),
+ {HeaderBlock, EncodeState} = cow_hpack:encode(Headers, EncodeState0),
+ {ok, IsFin, HeaderBlock, stream_store(Stream#stream{local=IsFin0},
+ State#http2_machine{encode_state=EncodeState})}.
+
+-spec prepare_push_promise(cow_http2:streamid(), State, pseudo_headers(), cow_http:headers())
+ -> {ok, cow_http2:streamid(), iodata(), State}
+ | {error, no_push} when State::http2_machine().
+prepare_push_promise(_, #http2_machine{remote_settings=#{enable_push := false}}, _, _) ->
+ {error, no_push};
+prepare_push_promise(StreamID, State=#http2_machine{encode_state=EncodeState0,
+ local_settings=#{initial_window_size := RemoteWindow},
+ remote_settings=#{initial_window_size := LocalWindow},
+ local_streamid=LocalStreamID}, PseudoHeaders, Headers0) ->
+ #stream{local=idle} = stream_get(StreamID, State),
+ TE = case lists:keyfind(<<"te">>, 1, Headers0) of
+ {_, TE0} -> TE0;
+ false -> undefined
+ end,
+ Headers = merge_pseudo_headers(PseudoHeaders, remove_http11_headers(Headers0)),
+ {HeaderBlock, EncodeState} = cow_hpack:encode(Headers, EncodeState0),
+ {ok, LocalStreamID, HeaderBlock, stream_store(
+ #stream{id=LocalStreamID, method=maps:get(method, PseudoHeaders),
+ remote=fin, remote_expected_size=0,
+ local_window=LocalWindow, remote_window=RemoteWindow, te=TE},
+ State#http2_machine{encode_state=EncodeState, local_streamid=LocalStreamID + 2})}.
+
+remove_http11_headers(Headers) ->
+ RemoveHeaders0 = [
+ <<"keep-alive">>,
+ <<"proxy-connection">>,
+ <<"transfer-encoding">>,
+ <<"upgrade">>
+ ],
+ RemoveHeaders = case lists:keyfind(<<"connection">>, 1, Headers) of
+ false ->
+ RemoveHeaders0;
+ {_, ConnHd} ->
+ %% We do not need to worry about any "close" header because
+ %% that header name is reserved.
+ Connection = cow_http_hd:parse_connection(ConnHd),
+ Connection ++ [<<"connection">>|RemoveHeaders0]
+ end,
+ lists:filter(fun({Name, _}) ->
+ not lists:member(Name, RemoveHeaders)
+ end, Headers).
+
+merge_pseudo_headers(PseudoHeaders, Headers0) ->
+ lists:foldl(fun
+ ({status, Status}, Acc) when is_integer(Status) ->
+ [{<<":status">>, integer_to_binary(Status)}|Acc];
+ ({Name, Value}, Acc) ->
+ [{iolist_to_binary([$:, atom_to_binary(Name, latin1)]), Value}|Acc]
+ end, Headers0, maps:to_list(PseudoHeaders)).
+
+-spec prepare_trailers(cow_http2:streamid(), State, cow_http:headers())
+ -> {ok, iodata(), State} when State::http2_machine().
+prepare_trailers(StreamID, State=#http2_machine{encode_state=EncodeState0}, Trailers) ->
+ Stream = #stream{local=nofin} = stream_get(StreamID, State),
+ {HeaderBlock, EncodeState} = cow_hpack:encode(Trailers, EncodeState0),
+ {ok, HeaderBlock, stream_store(Stream#stream{local=fin},
+ State#http2_machine{encode_state=EncodeState})}.
+
+-spec send_or_queue_data(cow_http2:streamid(), State, cow_http2:fin(), DataOrFileOrTrailers)
+ -> {ok, State}
+ | {send, [{cow_http2:streamid(), cow_http2:fin(), [DataOrFileOrTrailers]}], State}
+ when State::http2_machine(), DataOrFileOrTrailers::
+ {data, iodata()} | #sendfile{} | {trailers, cow_http:headers()}.
+send_or_queue_data(StreamID, State0=#http2_machine{opts=Opts, local_window=ConnWindow},
+ IsFin0, DataOrFileOrTrailers0) ->
+ %% @todo Probably just ignore if the method was HEAD.
+ Stream0 = #stream{
+ local=nofin,
+ local_window=StreamWindow,
+ local_buffer_size=BufferSize,
+ te=TE0
+ } = stream_get(StreamID, State0),
+ DataOrFileOrTrailers = case DataOrFileOrTrailers0 of
+ {trailers, _} ->
+ %% We only accept TE headers containing exactly "trailers" (RFC7540 8.1.2.1).
+ TE = try cow_http_hd:parse_te(TE0) of
+ {trailers, []} -> trailers;
+ _ -> no_trailers
+ catch _:_ ->
+ %% If we can't parse the TE header, assume we can't send trailers.
+ no_trailers
+ end,
+ case TE of
+ trailers ->
+ DataOrFileOrTrailers0;
+ no_trailers ->
+ {data, <<>>}
+ end;
+ _ ->
+ DataOrFileOrTrailers0
+ end,
+ SendSize = case DataOrFileOrTrailers of
+ {data, D} -> BufferSize + iolist_size(D);
+ #sendfile{bytes=B} -> BufferSize + B;
+ {trailers, _} -> 0
+ end,
+ MinSendSize = maps:get(stream_window_data_threshold, Opts, 16384),
+ if
+ %% If we cannot send the data all at once and the window
+ %% is smaller than we are willing to send at a minimum,
+ %% we queue the data directly.
+ (StreamWindow < MinSendSize)
+ andalso ((StreamWindow < SendSize) orelse (ConnWindow < SendSize)) ->
+ {ok, stream_store(queue_data(Stream0, IsFin0, DataOrFileOrTrailers, in), State0)};
+ true ->
+ case send_or_queue_data(Stream0, State0, [], IsFin0, DataOrFileOrTrailers, in) of
+ {ok, Stream, State, []} ->
+ {ok, stream_store(Stream, State)};
+ {ok, Stream=#stream{local=IsFin}, State, SendData} ->
+ {send, [{StreamID, IsFin, lists:reverse(SendData)}], stream_store(Stream, State)}
+ end
+ end.
+
+%% Internal data sending/queuing functions.
+
+%% @todo Should we ever want to implement the PRIORITY mechanism,
+%% this would be the place to do it. Right now, we just go over
+%% all streams and send what we can until either everything is
+%% sent or we run out of space in the window.
+send_data(State0=#http2_machine{streams=Streams0}) ->
+ Iterator = maps:iterator(Streams0),
+ case send_data_for_all_streams(maps:next(Iterator), Streams0, State0, []) of
+ {ok, Streams, State, []} ->
+ {ok, State#http2_machine{streams=Streams}};
+ {ok, Streams, State, Send} ->
+ {send, Send, State#http2_machine{streams=Streams}}
+ end.
+
+send_data_for_all_streams(none, Streams, State, Send) ->
+ {ok, Streams, State, Send};
+%% While technically we should never get < 0 here, let's be on the safe side.
+send_data_for_all_streams(_, Streams, State=#http2_machine{local_window=ConnWindow}, Send)
+ when ConnWindow =< 0 ->
+ {ok, Streams, State, Send};
+%% We rely on send_data_for_one_stream/3 to do all the necessary checks about the stream.
+send_data_for_all_streams({StreamID, Stream0, Iterator}, Streams, State0, Send) ->
+ case send_data_for_one_stream(Stream0, State0, []) of
+ {ok, Stream, State, []} ->
+ send_data_for_all_streams(maps:next(Iterator),
+ Streams#{StreamID => Stream}, State, Send);
+ %% We need to remove the stream here because we do not use stream_store/2.
+ {ok, #stream{local=fin, remote=fin}, State, SendData} ->
+ send_data_for_all_streams(maps:next(Iterator),
+ maps:remove(StreamID, Streams), State, [{StreamID, fin, SendData}|Send]);
+ {ok, Stream=#stream{local=IsFin}, State, SendData} ->
+ send_data_for_all_streams(maps:next(Iterator),
+ Streams#{StreamID => Stream}, State, [{StreamID, IsFin, SendData}|Send])
+ end.
+
+send_data(Stream0, State0) ->
+ case send_data_for_one_stream(Stream0, State0, []) of
+ {ok, Stream, State, []} ->
+ {ok, stream_store(Stream, State)};
+ {ok, Stream=#stream{id=StreamID, local=IsFin}, State, SendData} ->
+ {send, [{StreamID, IsFin, SendData}], stream_store(Stream, State)}
+ end.
+
+send_data_for_one_stream(Stream=#stream{local=nofin, local_buffer_size=0,
+ local_trailers=Trailers}, State, SendAcc) when Trailers =/= undefined ->
+ {ok, Stream, State, lists:reverse([{trailers, Trailers}|SendAcc])};
+send_data_for_one_stream(Stream=#stream{local=nofin, local_buffer=Q0, local_buffer_size=0},
+ State, SendAcc) ->
+ case queue:len(Q0) of
+ 0 ->
+ {ok, Stream, State, lists:reverse(SendAcc)};
+ 1 ->
+ %% We know there is a final empty data frame in the queue.
+ %% We need to mark the stream as complete.
+ {{value, {fin, 0, _}}, Q} = queue:out(Q0),
+ {ok, Stream#stream{local=fin, local_buffer=Q}, State, lists:reverse(SendAcc)}
+ end;
+send_data_for_one_stream(Stream=#stream{local=IsFin, local_window=StreamWindow,
+ local_buffer_size=BufferSize}, State=#http2_machine{local_window=ConnWindow}, SendAcc)
+ when ConnWindow =< 0; IsFin =:= fin; StreamWindow =< 0; BufferSize =:= 0 ->
+ {ok, Stream, State, lists:reverse(SendAcc)};
+send_data_for_one_stream(Stream0=#stream{local_window=StreamWindow,
+ local_buffer=Q0, local_buffer_size=BufferSize},
+ State0=#http2_machine{opts=Opts, local_window=ConnWindow}, SendAcc0) ->
+ MinSendSize = maps:get(stream_window_data_threshold, Opts, 16384),
+ if
+ %% If we cannot send the entire buffer at once and the window
+ %% is smaller than we are willing to send at a minimum, do nothing.
+ %%
+ %% We only do this check the first time we go through this function;
+ %% we want to send as much data as possible IF we send some.
+ (SendAcc0 =:= []) andalso (StreamWindow < MinSendSize)
+ andalso ((StreamWindow < BufferSize) orelse (ConnWindow < BufferSize)) ->
+ {ok, Stream0, State0, []};
+ true ->
+ %% We know there is an item in the queue.
+ {{value, {IsFin, DataSize, Data}}, Q} = queue:out(Q0),
+ Stream1 = Stream0#stream{local_buffer=Q, local_buffer_size=BufferSize - DataSize},
+ {ok, Stream, State, SendAcc}
+ = send_or_queue_data(Stream1, State0, SendAcc0, IsFin, Data, in_r),
+ send_data_for_one_stream(Stream, State, SendAcc)
+ end.
+
+%% We can send trailers immediately if the queue is empty, otherwise we queue.
+%% We always send trailer frames even if the window is empty.
+send_or_queue_data(Stream=#stream{local_buffer_size=0},
+ State, SendAcc, fin, {trailers, Trailers}, _) ->
+ {ok, Stream, State, [{trailers, Trailers}|SendAcc]};
+send_or_queue_data(Stream, State, SendAcc, fin, {trailers, Trailers}, _) ->
+ {ok, Stream#stream{local_trailers=Trailers}, State, SendAcc};
+%% Send data immediately if we can, buffer otherwise.
+send_or_queue_data(Stream=#stream{local_window=StreamWindow},
+ State=#http2_machine{local_window=ConnWindow},
+ SendAcc, IsFin, Data, In)
+ when ConnWindow =< 0; StreamWindow =< 0 ->
+ {ok, queue_data(Stream, IsFin, Data, In), State, SendAcc};
+send_or_queue_data(Stream=#stream{local_window=StreamWindow},
+ State=#http2_machine{opts=Opts, remote_settings=RemoteSettings,
+ local_window=ConnWindow}, SendAcc, IsFin, Data, In) ->
+ RemoteMaxFrameSize = maps:get(max_frame_size, RemoteSettings, 16384),
+ ConfiguredMaxFrameSize = maps:get(max_frame_size_sent, Opts, infinity),
+ MaxSendSize = min(
+ min(ConnWindow, StreamWindow),
+ min(RemoteMaxFrameSize, ConfiguredMaxFrameSize)
+ ),
+ case Data of
+ File = #sendfile{bytes=Bytes} when Bytes =< MaxSendSize ->
+ {ok, Stream#stream{local=IsFin, local_window=StreamWindow - Bytes},
+ State#http2_machine{local_window=ConnWindow - Bytes},
+ [File|SendAcc]};
+ File = #sendfile{offset=Offset, bytes=Bytes} ->
+ send_or_queue_data(Stream#stream{local_window=StreamWindow - MaxSendSize},
+ State#http2_machine{local_window=ConnWindow - MaxSendSize},
+ [File#sendfile{bytes=MaxSendSize}|SendAcc], IsFin,
+ File#sendfile{offset=Offset + MaxSendSize, bytes=Bytes - MaxSendSize}, In);
+ {data, Iolist0} ->
+ IolistSize = iolist_size(Iolist0),
+ if
+ IolistSize =< MaxSendSize ->
+ {ok, Stream#stream{local=IsFin, local_window=StreamWindow - IolistSize},
+ State#http2_machine{local_window=ConnWindow - IolistSize},
+ [{data, Iolist0}|SendAcc]};
+ true ->
+ {Iolist, More} = cow_iolists:split(MaxSendSize, Iolist0),
+ send_or_queue_data(Stream#stream{local_window=StreamWindow - MaxSendSize},
+ State#http2_machine{local_window=ConnWindow - MaxSendSize},
+ [{data, Iolist}|SendAcc], IsFin, {data, More}, In)
+ end
+ end.
+
+queue_data(Stream=#stream{local_buffer=Q0, local_buffer_size=Size0}, IsFin, Data, In) ->
+ DataSize = case Data of
+ {sendfile, _, Bytes, _} -> Bytes;
+ {data, Iolist} -> iolist_size(Iolist)
+ end,
+ %% Never queue non-final empty data frames.
+ case {DataSize, IsFin} of
+ {0, nofin} ->
+ Stream;
+ _ ->
+ Q = queue:In({IsFin, DataSize, Data}, Q0),
+ Stream#stream{local_buffer=Q, local_buffer_size=Size0 + DataSize}
+ end.
+
+%% Public interface to update the flow control window.
+%%
+%% The ensure_window function applies heuristics to avoid updating the
+%% window when it is not necessary. The update_window function updates
+%% the window unconditionally.
+%%
+%% The ensure_window function should be called when requesting more
+%% data (for example when reading a request or response body) as well
+%% as when receiving new data. Failure to do so may result in the
+%% window being depleted.
+%%
+%% The heuristics dictating whether the window must be updated and
+%% what the window size is depends on three options (margin, max
+%% and threshold) along with the Size argument. The window increment
+%% returned by this function may therefore be smaller than the Size
+%% argument. On the other hand the total window allocated over many
+%% calls may end up being larger than the initial Size argument. As
+%% a result, it is the responsibility of the caller to ensure that
+%% the Size argument is never lower than 0.
+
+-spec ensure_window(non_neg_integer(), State)
+ -> ok | {ok, pos_integer(), State} when State::http2_machine().
+ensure_window(Size, State=#http2_machine{opts=Opts, remote_window=RemoteWindow}) ->
+ case ensure_window(Size, RemoteWindow, connection, Opts) of
+ ok ->
+ ok;
+ {ok, Increment} ->
+ {ok, Increment, State#http2_machine{remote_window=RemoteWindow + Increment}}
+ end.
+
+-spec ensure_window(cow_http2:streamid(), non_neg_integer(), State)
+ -> ok | {ok, pos_integer(), State} when State::http2_machine().
+ensure_window(StreamID, Size, State=#http2_machine{opts=Opts}) ->
+ case stream_get(StreamID, State) of
+ %% For simplicity's sake, we do not consider attempts to ensure the window
+ %% of a terminated stream to be errors. We simply act as if the stream
+ %% window is large enough.
+ undefined ->
+ ok;
+ Stream = #stream{remote_window=RemoteWindow} ->
+ case ensure_window(Size, RemoteWindow, stream, Opts) of
+ ok ->
+ ok;
+ {ok, Increment} ->
+ {ok, Increment, stream_store(Stream#stream{remote_window=RemoteWindow + Increment}, State)}
+ end
+ end.
+
+%% No need to update the window when we are not expecting data.
+ensure_window(0, _, _, _) ->
+ ok;
+%% No need to update the window when it is already high enough.
+ensure_window(Size, Window, _, _) when Size =< Window ->
+ ok;
+ensure_window(Size0, Window, Type, Opts) ->
+ Threshold = ensure_window_threshold(Type, Opts),
+ if
+ %% We do not update the window when it is higher than the threshold.
+ Window > Threshold ->
+ ok;
+ true ->
+ Margin = ensure_window_margin(Type, Opts),
+ Size = Size0 + Margin,
+ MaxWindow = ensure_window_max(Type, Opts),
+ Increment = if
+ %% We cannot go above the maximum window size.
+ Size > MaxWindow -> MaxWindow - Window;
+ true -> Size - Window
+ end,
+ case Increment of
+ 0 -> ok;
+ _ -> {ok, Increment}
+ end
+ end.
+
+%% Margin defaults to the default initial window size.
+ensure_window_margin(connection, Opts) ->
+ maps:get(connection_window_margin_size, Opts, 65535);
+ensure_window_margin(stream, Opts) ->
+ maps:get(stream_window_margin_size, Opts, 65535).
+
+%% Max window defaults to the max value allowed by the protocol.
+ensure_window_max(connection, Opts) ->
+ maps:get(max_connection_window_size, Opts, 16#7fffffff);
+ensure_window_max(stream, Opts) ->
+ maps:get(max_stream_window_size, Opts, 16#7fffffff).
+
+%% Threshold defaults to 10 times the default frame size.
+ensure_window_threshold(connection, Opts) ->
+ maps:get(connection_window_update_threshold, Opts, 163840);
+ensure_window_threshold(stream, Opts) ->
+ maps:get(stream_window_update_threshold, Opts, 163840).
+
+-spec update_window(1..16#7fffffff, State)
+ -> State when State::http2_machine().
+update_window(Size, State=#http2_machine{remote_window=RemoteWindow})
+ when Size > 0 ->
+ State#http2_machine{remote_window=RemoteWindow + Size}.
+
+-spec update_window(cow_http2:streamid(), 1..16#7fffffff, State)
+ -> State when State::http2_machine().
+update_window(StreamID, Size, State)
+ when Size > 0 ->
+ Stream = #stream{remote_window=RemoteWindow} = stream_get(StreamID, State),
+ stream_store(Stream#stream{remote_window=RemoteWindow + Size}, State).
+
+%% Public interface to reset streams.
+
+-spec reset_stream(cow_http2:streamid(), State)
+ -> {ok, State} | {error, not_found} when State::http2_machine().
+reset_stream(StreamID, State=#http2_machine{streams=Streams0}) ->
+ case maps:take(StreamID, Streams0) of
+ {_, Streams} ->
+ {ok, stream_linger(StreamID, State#http2_machine{streams=Streams})};
+ error ->
+ {error, not_found}
+ end.
+
+%% Retrieve the buffer size for all streams.
+
+-spec get_connection_local_buffer_size(http2_machine()) -> non_neg_integer().
+get_connection_local_buffer_size(#http2_machine{streams=Streams}) ->
+ maps:fold(fun(_, #stream{local_buffer_size=Size}, Acc) ->
+ Acc + Size
+ end, 0, Streams).
+
+%% Retrieve a setting value, or its default value if not set.
+
+-spec get_local_setting(atom(), http2_machine()) -> atom() | integer().
+get_local_setting(Key, #http2_machine{local_settings=Settings}) ->
+ maps:get(Key, Settings, default_setting_value(Key)).
+
+-spec get_remote_settings(http2_machine()) -> map().
+get_remote_settings(#http2_machine{mode=Mode, remote_settings=Settings}) ->
+ Defaults0 = #{
+ header_table_size => default_setting_value(header_table_size),
+ enable_push => default_setting_value(enable_push),
+ max_concurrent_streams => default_setting_value(max_concurrent_streams),
+ initial_window_size => default_setting_value(initial_window_size),
+ max_frame_size => default_setting_value(max_frame_size),
+ max_header_list_size => default_setting_value(max_header_list_size)
+ },
+ Defaults = case Mode of
+ server ->
+ Defaults0#{enable_connect_protocol => default_setting_value(enable_connect_protocol)};
+ client ->
+ Defaults0
+ end,
+ maps:merge(Defaults, Settings).
+
+default_setting_value(header_table_size) -> 4096;
+default_setting_value(enable_push) -> true;
+default_setting_value(max_concurrent_streams) -> infinity;
+default_setting_value(initial_window_size) -> 65535;
+default_setting_value(max_frame_size) -> 16384;
+default_setting_value(max_header_list_size) -> infinity;
+default_setting_value(enable_connect_protocol) -> false.
+
+%% Function to obtain the last known streamid received
+%% for the purposes of sending a GOAWAY frame and closing the connection.
+
+-spec get_last_streamid(http2_machine()) -> cow_http2:streamid().
+get_last_streamid(#http2_machine{remote_streamid=RemoteStreamID}) ->
+ RemoteStreamID.
+
+%% Set last accepted streamid to the last known streamid, for the purpose
+%% ignoring frames for remote streams created after sending GOAWAY.
+
+-spec set_last_streamid(http2_machine()) -> {cow_http2:streamid(), http2_machine()}.
+set_last_streamid(State=#http2_machine{remote_streamid=StreamID,
+ last_remote_streamid=LastStreamID}) when StreamID =< LastStreamID->
+ {StreamID, State#http2_machine{last_remote_streamid = StreamID}}.
+
+%% Retrieve the local buffer size for a stream.
+
+-spec get_stream_local_buffer_size(cow_http2:streamid(), http2_machine())
+ -> {ok, non_neg_integer()} | {error, not_found | closed}.
+get_stream_local_buffer_size(StreamID, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID}) ->
+ case stream_get(StreamID, State) of
+ #stream{local_buffer_size=Size} ->
+ {ok, Size};
+ undefined when (?IS_LOCAL(Mode, StreamID) andalso (StreamID < LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID =< RemoteStreamID)) ->
+ {error, closed};
+ undefined ->
+ {error, not_found}
+ end.
+
+%% Retrieve the local state for a stream, including the state in the queue.
+
+-spec get_stream_local_state(cow_http2:streamid(), http2_machine())
+ -> {ok, idle | cow_http2:fin(), empty | nofin | fin} | {error, not_found | closed}.
+get_stream_local_state(StreamID, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID}) ->
+ case stream_get(StreamID, State) of
+ #stream{local=IsFin, local_buffer=Q, local_trailers=undefined} ->
+ IsQueueFin = case queue:peek_r(Q) of
+ empty -> empty;
+ {value, {IsQueueFin0, _, _}} -> IsQueueFin0
+ end,
+ {ok, IsFin, IsQueueFin};
+ %% Trailers are queued so the local state is fin after the queue is drained.
+ #stream{local=IsFin} ->
+ {ok, IsFin, fin};
+ undefined when (?IS_LOCAL(Mode, StreamID) andalso (StreamID < LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID =< RemoteStreamID)) ->
+ {error, closed};
+ undefined ->
+ {error, not_found}
+ end.
+
+%% Retrieve the remote state for a stream.
+
+-spec get_stream_remote_state(cow_http2:streamid(), http2_machine())
+ -> {ok, idle | cow_http2:fin()} | {error, not_found | closed}.
+get_stream_remote_state(StreamID, State=#http2_machine{mode=Mode,
+ local_streamid=LocalStreamID, remote_streamid=RemoteStreamID}) ->
+ case stream_get(StreamID, State) of
+ #stream{remote=IsFin} ->
+ {ok, IsFin};
+ undefined when (?IS_LOCAL(Mode, StreamID) andalso (StreamID < LocalStreamID))
+ orelse ((not ?IS_LOCAL(Mode, StreamID)) andalso (StreamID =< RemoteStreamID)) ->
+ {error, closed};
+ undefined ->
+ {error, not_found}
+ end.
+
+%% Query whether the stream was reset recently by the remote endpoint.
+
+-spec is_lingering_stream(cow_http2:streamid(), http2_machine()) -> boolean().
+is_lingering_stream(StreamID, #http2_machine{
+ local_lingering_streams=Local, remote_lingering_streams=Remote}) ->
+ case lists:member(StreamID, Local) of
+ true -> true;
+ false -> lists:member(StreamID, Remote)
+ end.
+
+%% Stream-related functions.
+
+stream_get(StreamID, #http2_machine{streams=Streams}) ->
+ maps:get(StreamID, Streams, undefined).
+
+stream_store(#stream{id=StreamID, local=fin, remote=fin},
+ State=#http2_machine{streams=Streams0}) ->
+ Streams = maps:remove(StreamID, Streams0),
+ State#http2_machine{streams=Streams};
+stream_store(Stream=#stream{id=StreamID},
+ State=#http2_machine{streams=Streams}) ->
+ State#http2_machine{streams=Streams#{StreamID => Stream}}.
+
+%% @todo Don't send an RST_STREAM if one was already sent.
+stream_reset(StreamID, State, Reason, HumanReadable) ->
+ {error, {stream_error, StreamID, Reason, HumanReadable},
+ stream_linger(StreamID, State)}.
+
+stream_linger(StreamID, State=#http2_machine{local_lingering_streams=Lingering0}) ->
+ %% We only keep up to 100 streams in this state. @todo Make it configurable?
+ Lingering = [StreamID|lists:sublist(Lingering0, 100 - 1)],
+ State#http2_machine{local_lingering_streams=Lingering}.
diff --git a/server/_build/default/lib/cowlib/src/cow_http_hd.erl b/server/_build/default/lib/cowlib/src/cow_http_hd.erl
new file mode 100644
index 0000000..f0e4fba
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http_hd.erl
@@ -0,0 +1,3642 @@
+%% Copyright (c) 2014-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http_hd).
+
+%% Functions are ordered by header name, with the parse
+%% function before the build function.
+
+-export([parse_accept/1]).
+-export([parse_accept_charset/1]).
+% @todo -export([parse_accept_datetime/1]). RFC7089
+-export([parse_accept_encoding/1]).
+% @todo -export([parse_accept_features/1]). RFC2295
+-export([parse_accept_language/1]).
+-export([parse_accept_ranges/1]).
+% @todo -export([parse_access_control_allow_credentials/1]). CORS
+-export([access_control_allow_credentials/0]).
+% @todo -export([parse_access_control_allow_headers/1]). CORS
+-export([access_control_allow_headers/1]).
+% @todo -export([parse_access_control_allow_methods/1]). CORS
+-export([access_control_allow_methods/1]).
+% @todo -export([parse_access_control_allow_origin/1]). CORS
+-export([access_control_allow_origin/1]).
+% @todo -export([parse_access_control_expose_headers/1]). CORS
+-export([access_control_expose_headers/1]).
+% @todo -export([parse_access_control_max_age/1]). CORS
+-export([access_control_max_age/1]).
+-export([parse_access_control_request_headers/1]).
+-export([parse_access_control_request_method/1]).
+-export([parse_age/1]).
+-export([parse_allow/1]).
+% @todo -export([parse_alternates/1]). RFC2295
+% @todo -export([parse_authentication_info/1]). RFC2617
+-export([parse_authorization/1]).
+-export([parse_cache_control/1]).
+-export([parse_connection/1]).
+% @todo -export([parse_content_disposition/1]). RFC6266
+-export([parse_content_encoding/1]).
+-export([parse_content_language/1]).
+-export([parse_content_length/1]).
+% @todo -export([parse_content_location/1]). RFC7231
+% @todo -export([parse_content_md5/1]). RFC2616 (deprecated)
+-export([parse_content_range/1]).
+% @todo -export([parse_content_security_policy/1]). CSP
+% @todo -export([parse_content_security_policy_report_only/1]). CSP
+-export([parse_content_type/1]).
+-export([parse_cookie/1]).
+-export([parse_date/1]).
+% @todo -export([parse_digest/1]). RFC3230
+% @todo -export([parse_dnt/1]). http://donottrack.us/
+-export([parse_etag/1]).
+-export([parse_expect/1]).
+-export([parse_expires/1]).
+% @todo -export([parse_forwarded/1]). RFC7239
+% @todo -export([parse_from/1]). RFC7231
+-export([parse_host/1]).
+-export([parse_http2_settings/1]).
+-export([parse_if_match/1]).
+-export([parse_if_modified_since/1]).
+-export([parse_if_none_match/1]).
+-export([parse_if_range/1]).
+-export([parse_if_unmodified_since/1]).
+% @todo -export([parse_last_event_id/1]). eventsource
+-export([parse_last_modified/1]).
+-export([parse_link/1]).
+% @todo -export([parse_location/1]). RFC7231
+-export([parse_max_forwards/1]).
+% @todo -export([parse_memento_datetime/1]). RFC7089
+% @todo -export([parse_negotiate/1]). RFC2295
+-export([parse_origin/1]).
+-export([parse_pragma/1]).
+% @todo -export([parse_prefer/1]). RFC7240
+-export([parse_proxy_authenticate/1]).
+% @todo -export([parse_proxy_authentication_info/1]). RFC2617
+-export([parse_proxy_authorization/1]).
+% @todo -export([parse_proxy_support/1]). RFC4559
+% @todo -export([parse_public_key_pins/1]). Key Pinning (upcoming)
+% @todo -export([parse_public_key_pins_report_only/1]). Key Pinning (upcoming)
+-export([parse_range/1]).
+% @todo -export([parse_referer/1]). RFC7231
+% @todo -export([parse_refresh/1]). Non-standard (examples: "5", "5; url=http://example.com/")
+-export([parse_retry_after/1]).
+-export([parse_sec_websocket_accept/1]).
+-export([parse_sec_websocket_extensions/1]).
+-export([parse_sec_websocket_key/1]).
+% @todo -export([parse_sec_websocket_origin/1]). Websocket drafts 7 and 8
+-export([parse_sec_websocket_protocol_req/1]).
+-export([parse_sec_websocket_protocol_resp/1]).
+-export([parse_sec_websocket_version_req/1]).
+-export([parse_sec_websocket_version_resp/1]).
+% @todo -export([parse_server/1]). RFC7231
+-export([parse_set_cookie/1]).
+% @todo -export([parse_strict_transport_security/1]). RFC6797
+% @todo -export([parse_tcn/1]). RFC2295
+-export([parse_te/1]).
+-export([parse_trailer/1]).
+-export([parse_transfer_encoding/1]).
+-export([parse_upgrade/1]).
+% @todo -export([parse_user_agent/1]). RFC7231
+% @todo -export([parse_variant_vary/1]). RFC2295
+-export([parse_variant_key/2]).
+-export([variant_key/1]).
+-export([parse_variants/1]).
+-export([variants/1]).
+-export([parse_vary/1]).
+% @todo -export([parse_via/1]). RFC7230
+% @todo -export([parse_want_digest/1]). RFC3230
+% @todo -export([parse_warning/1]). RFC7234
+-export([parse_www_authenticate/1]).
+% @todo -export([parse_x_content_duration/1]). Gecko/MDN (value: float)
+% @todo -export([parse_x_dns_prefetch_control/1]). Various (value: "on"|"off")
+-export([parse_x_forwarded_for/1]).
+% @todo -export([parse_x_frame_options/1]). RFC7034
+
+-type etag() :: {weak | strong, binary()}.
+-export_type([etag/0]).
+
+-type media_type() :: {binary(), binary(), [{binary(), binary()}]}.
+-export_type([media_type/0]).
+
+-type qvalue() :: 0..1000.
+-export_type([qvalue/0]).
+
+-type websocket_version() :: 0..255.
+-export_type([websocket_version/0]).
+
+-include("cow_inline.hrl").
+-include("cow_parse.hrl").
+
+-ifdef(TEST).
+-include_lib("proper/include/proper.hrl").
+
+vector(Min, Max, Dom) -> ?LET(N, choose(Min, Max), vector(N, Dom)).
+small_list(Dom) -> vector(0, 10, Dom).
+small_non_empty_list(Dom) -> vector(1, 10, Dom).
+
+alpha_chars() -> "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".
+alphanum_chars() -> "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".
+digit_chars() -> "0123456789".
+
+ows() -> list(elements([$\s, $\t])).
+alpha() -> elements(alpha_chars()).
+alphanum() -> elements(alphanum_chars()).
+digit() -> elements(digit_chars()).
+
+tchar() ->
+ frequency([
+ {1, elements([$!, $#, $$, $%, $&, $', $*, $+, $-, $., $^, $_, $`, $|, $~])},
+ {99, elements(alphanum_chars())}
+ ]).
+
+token() ->
+ ?LET(T,
+ non_empty(list(tchar())),
+ list_to_binary(T)).
+
+abnf_char() ->
+ integer(1, 127).
+
+vchar() ->
+ integer(33, 126).
+
+obs_text() ->
+ integer(128, 255).
+
+qdtext() ->
+ frequency([
+ {99, elements("\t\s!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~")},
+ {1, obs_text()}
+ ]).
+
+quoted_pair() ->
+ [$\\, frequency([
+ {99, elements("\t\s!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~")},
+ {1, obs_text()}
+ ])].
+
+quoted_string() ->
+ [$", list(frequency([{100, qdtext()}, {1, quoted_pair()}])), $"].
+
+%% Helper function for ( token / quoted-string ) values.
+unquote([$", V, $"]) -> unquote(V, <<>>);
+unquote(V) -> V.
+
+unquote([], Acc) -> Acc;
+unquote([[$\\, C]|Tail], Acc) -> unquote(Tail, << Acc/binary, C >>);
+unquote([C|Tail], Acc) -> unquote(Tail, << Acc/binary, C >>).
+
+parameter() ->
+ ?SUCHTHAT({K, _, _, _},
+ {token(), oneof([token(), quoted_string()]), ows(), ows()},
+ K =/= <<"q">>).
+
+weight() ->
+ frequency([
+ {90, integer(0, 1000)},
+ {10, undefined}
+ ]).
+
+%% Helper function for weight's qvalue formatting.
+qvalue_to_iodata(0) -> <<"0">>;
+qvalue_to_iodata(Q) when Q < 10 -> [<<"0.00">>, integer_to_binary(Q)];
+qvalue_to_iodata(Q) when Q < 100 -> [<<"0.0">>, integer_to_binary(Q)];
+qvalue_to_iodata(Q) when Q < 1000 -> [<<"0.">>, integer_to_binary(Q)];
+qvalue_to_iodata(1000) -> <<"1">>.
+-endif.
+
+%% Accept header.
+
+-spec parse_accept(binary()) -> [{media_type(), qvalue(), [binary() | {binary(), binary()}]}].
+parse_accept(<<"*/*">>) ->
+ [{{<<"*">>, <<"*">>, []}, 1000, []}];
+parse_accept(Accept) ->
+ media_range_list(Accept, []).
+
+media_range_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> ?LOWER(media_range_type, R, Acc, <<>>);
+media_range_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> media_range_list(R, Acc);
+media_range_list(<<>>, Acc) -> lists:reverse(Acc).
+
+media_range_type(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> ?LOWER(media_range_type, R, Acc, T);
+media_range_type(<< $/, C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> ?LOWER(media_range_subtype, R, Acc, T, <<>>);
+%% Special clause for badly behaving user agents that send * instead of */*.
+media_range_type(<< $;, R/bits >>, Acc, <<"*">>) -> media_range_before_param(R, Acc, <<"*">>, <<"*">>, []).
+
+media_range_subtype(<< C, R/bits >>, Acc, T, S) when ?IS_TOKEN(C) -> ?LOWER(media_range_subtype, R, Acc, T, S);
+media_range_subtype(R, Acc, T, S) -> media_range_param_sep(R, Acc, T, S, []).
+
+media_range_param_sep(<<>>, Acc, T, S, P) -> lists:reverse([{{T, S, lists:reverse(P)}, 1000, []}|Acc]);
+media_range_param_sep(<< $,, R/bits >>, Acc, T, S, P) -> media_range_list(R, [{{T, S, lists:reverse(P)}, 1000, []}|Acc]);
+media_range_param_sep(<< $;, R/bits >>, Acc, T, S, P) -> media_range_before_param(R, Acc, T, S, P);
+media_range_param_sep(<< C, R/bits >>, Acc, T, S, P) when ?IS_WS(C) -> media_range_param_sep(R, Acc, T, S, P).
+
+media_range_before_param(<< C, R/bits >>, Acc, T, S, P) when ?IS_WS(C) -> media_range_before_param(R, Acc, T, S, P);
+media_range_before_param(<< $q, $=, R/bits >>, Acc, T, S, P) -> media_range_weight(R, Acc, T, S, P);
+media_range_before_param(<< "charset=", $", R/bits >>, Acc, T, S, P) -> media_range_charset_quoted(R, Acc, T, S, P, <<>>);
+media_range_before_param(<< "charset=", R/bits >>, Acc, T, S, P) -> media_range_charset(R, Acc, T, S, P, <<>>);
+media_range_before_param(<< C, R/bits >>, Acc, T, S, P) when ?IS_TOKEN(C) -> ?LOWER(media_range_param, R, Acc, T, S, P, <<>>).
+
+media_range_charset_quoted(<< $", R/bits >>, Acc, T, S, P, V) ->
+ media_range_param_sep(R, Acc, T, S, [{<<"charset">>, V}|P]);
+media_range_charset_quoted(<< $\\, C, R/bits >>, Acc, T, S, P, V) when ?IS_VCHAR_OBS(C) ->
+ ?LOWER(media_range_charset_quoted, R, Acc, T, S, P, V);
+media_range_charset_quoted(<< C, R/bits >>, Acc, T, S, P, V) when ?IS_VCHAR_OBS(C) ->
+ ?LOWER(media_range_charset_quoted, R, Acc, T, S, P, V).
+
+media_range_charset(<< C, R/bits >>, Acc, T, S, P, V) when ?IS_TOKEN(C) ->
+ ?LOWER(media_range_charset, R, Acc, T, S, P, V);
+media_range_charset(R, Acc, T, S, P, V) ->
+ media_range_param_sep(R, Acc, T, S, [{<<"charset">>, V}|P]).
+
+media_range_param(<< $=, $", R/bits >>, Acc, T, S, P, K) -> media_range_quoted(R, Acc, T, S, P, K, <<>>);
+media_range_param(<< $=, C, R/bits >>, Acc, T, S, P, K) when ?IS_TOKEN(C) -> media_range_value(R, Acc, T, S, P, K, << C >>);
+media_range_param(<< C, R/bits >>, Acc, T, S, P, K) when ?IS_TOKEN(C) -> ?LOWER(media_range_param, R, Acc, T, S, P, K).
+
+media_range_quoted(<< $", R/bits >>, Acc, T, S, P, K, V) -> media_range_param_sep(R, Acc, T, S, [{K, V}|P]);
+media_range_quoted(<< $\\, C, R/bits >>, Acc, T, S, P, K, V) when ?IS_VCHAR_OBS(C) -> media_range_quoted(R, Acc, T, S, P, K, << V/binary, C >>);
+media_range_quoted(<< C, R/bits >>, Acc, T, S, P, K, V) when ?IS_VCHAR_OBS(C) -> media_range_quoted(R, Acc, T, S, P, K, << V/binary, C >>).
+
+media_range_value(<< C, R/bits >>, Acc, T, S, P, K, V) when ?IS_TOKEN(C) -> media_range_value(R, Acc, T, S, P, K, << V/binary, C >>);
+media_range_value(R, Acc, T, S, P, K, V) -> media_range_param_sep(R, Acc, T, S, [{K, V}|P]).
+
+media_range_weight(<< "1.000", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 1000, []);
+media_range_weight(<< "1.00", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 1000, []);
+media_range_weight(<< "1.0", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 1000, []);
+media_range_weight(<< "1.", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 1000, []);
+media_range_weight(<< "1", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 1000, []);
+media_range_weight(<< "0.", A, B, C, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100 + (B - $0) * 10 + (C - $0), []);
+media_range_weight(<< "0.", A, B, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A), ?IS_DIGIT(B) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100 + (B - $0) * 10, []);
+media_range_weight(<< "0.", A, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100, []);
+media_range_weight(<< "0.", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 0, []);
+media_range_weight(<< "0", R/bits >>, Acc, T, S, P) -> accept_ext_sep(R, Acc, T, S, P, 0, []);
+%% Special clauses for badly behaving user agents that send .123 instead of 0.123.
+media_range_weight(<< ".", A, B, C, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100 + (B - $0) * 10 + (C - $0), []);
+media_range_weight(<< ".", A, B, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A), ?IS_DIGIT(B) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100 + (B - $0) * 10, []);
+media_range_weight(<< ".", A, R/bits >>, Acc, T, S, P) when ?IS_DIGIT(A) ->
+ accept_ext_sep(R, Acc, T, S, P, (A - $0) * 100, []).
+
+accept_ext_sep(<<>>, Acc, T, S, P, Q, E) -> lists:reverse([{{T, S, lists:reverse(P)}, Q, lists:reverse(E)}|Acc]);
+accept_ext_sep(<< $,, R/bits >>, Acc, T, S, P, Q, E) -> media_range_list(R, [{{T, S, lists:reverse(P)}, Q, lists:reverse(E)}|Acc]);
+accept_ext_sep(<< $;, R/bits >>, Acc, T, S, P, Q, E) -> accept_before_ext(R, Acc, T, S, P, Q, E);
+accept_ext_sep(<< C, R/bits >>, Acc, T, S, P, Q, E) when ?IS_WS(C) -> accept_ext_sep(R, Acc, T, S, P, Q, E).
+
+accept_before_ext(<< C, R/bits >>, Acc, T, S, P, Q, E) when ?IS_WS(C) -> accept_before_ext(R, Acc, T, S, P, Q, E);
+accept_before_ext(<< C, R/bits >>, Acc, T, S, P, Q, E) when ?IS_TOKEN(C) -> ?LOWER(accept_ext, R, Acc, T, S, P, Q, E, <<>>).
+
+accept_ext(<< $=, $", R/bits >>, Acc, T, S, P, Q, E, K) -> accept_quoted(R, Acc, T, S, P, Q, E, K, <<>>);
+accept_ext(<< $=, C, R/bits >>, Acc, T, S, P, Q, E, K) when ?IS_TOKEN(C) -> accept_value(R, Acc, T, S, P, Q, E, K, << C >>);
+accept_ext(<< C, R/bits >>, Acc, T, S, P, Q, E, K) when ?IS_TOKEN(C) -> ?LOWER(accept_ext, R, Acc, T, S, P, Q, E, K);
+accept_ext(R, Acc, T, S, P, Q, E, K) -> accept_ext_sep(R, Acc, T, S, P, Q, [K|E]).
+
+accept_quoted(<< $", R/bits >>, Acc, T, S, P, Q, E, K, V) -> accept_ext_sep(R, Acc, T, S, P, Q, [{K, V}|E]);
+accept_quoted(<< $\\, C, R/bits >>, Acc, T, S, P, Q, E, K, V) when ?IS_VCHAR_OBS(C) -> accept_quoted(R, Acc, T, S, P, Q, E, K, << V/binary, C >>);
+accept_quoted(<< C, R/bits >>, Acc, T, S, P, Q, E, K, V) when ?IS_VCHAR_OBS(C) -> accept_quoted(R, Acc, T, S, P, Q, E, K, << V/binary, C >>).
+
+accept_value(<< C, R/bits >>, Acc, T, S, P, Q, E, K, V) when ?IS_TOKEN(C) -> accept_value(R, Acc, T, S, P, Q, E, K, << V/binary, C >>);
+accept_value(R, Acc, T, S, P, Q, E, K, V) -> accept_ext_sep(R, Acc, T, S, P, Q, [{K, V}|E]).
+
+-ifdef(TEST).
+accept_ext() ->
+ oneof([token(), parameter()]).
+
+accept_exts() ->
+ frequency([
+ {90, []},
+ {10, small_list(accept_ext())}
+ ]).
+
+accept_param() ->
+ frequency([
+ {90, parameter()},
+ {10, {<<"charset">>, oneof([token(), quoted_string()]), <<>>, <<>>}}
+ ]).
+
+accept_params() ->
+ small_list(accept_param()).
+
+accept() ->
+ ?LET({T, S, P, W, E},
+ {token(), token(), accept_params(), weight(), accept_exts()},
+ {T, S, P, W, E, iolist_to_binary([T, $/, S,
+ [[OWS1, $;, OWS2, K, $=, V] || {K, V, OWS1, OWS2} <- P],
+ case W of
+ undefined -> [];
+ _ -> [
+ [<<";q=">>, qvalue_to_iodata(W)],
+ [case Ext of
+ {K, V, OWS1, OWS2} -> [OWS1, $;, OWS2, K, $=, V];
+ K -> [$;, K]
+ end || Ext <- E]]
+ end])}
+ ).
+
+prop_parse_accept() ->
+ ?FORALL(L,
+ vector(1, 50, accept()),
+ begin
+ << _, Accept/binary >> = iolist_to_binary([[$,, A] || {_, _, _, _, _, A} <- L]),
+ ResL = parse_accept(Accept),
+ CheckedL = [begin
+ ExpectedP = [case ?LOWER(K) of
+ <<"charset">> -> {<<"charset">>, ?LOWER(unquote(V))};
+ LowK -> {LowK, unquote(V)}
+ end || {K, V, _, _} <- P],
+ ExpectedE = [case Ext of
+ {K, V, _, _} -> {?LOWER(K), unquote(V)};
+ K -> ?LOWER(K)
+ end || Ext <- E],
+ ResT =:= ?LOWER(T)
+ andalso ResS =:= ?LOWER(S)
+ andalso ResP =:= ExpectedP
+ andalso (ResW =:= W orelse (W =:= undefined andalso ResW =:= 1000))
+ andalso ((W =:= undefined andalso ResE =:= []) orelse (W =/= undefined andalso ResE =:= ExpectedE))
+ end || {{T, S, P, W, E, _}, {{ResT, ResS, ResP}, ResW, ResE}} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end
+ ).
+
+parse_accept_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<" ">>, []},
+ {<<"audio/*; q=0.2, audio/basic">>, [
+ {{<<"audio">>, <<"*">>, []}, 200, []},
+ {{<<"audio">>, <<"basic">>, []}, 1000, []}
+ ]},
+ {<<"text/plain; q=0.5, text/html, "
+ "text/x-dvi; q=0.8, text/x-c">>, [
+ {{<<"text">>, <<"plain">>, []}, 500, []},
+ {{<<"text">>, <<"html">>, []}, 1000, []},
+ {{<<"text">>, <<"x-dvi">>, []}, 800, []},
+ {{<<"text">>, <<"x-c">>, []}, 1000, []}
+ ]},
+ {<<"text/*, text/html, text/html;level=1, */*">>, [
+ {{<<"text">>, <<"*">>, []}, 1000, []},
+ {{<<"text">>, <<"html">>, []}, 1000, []},
+ {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []},
+ {{<<"*">>, <<"*">>, []}, 1000, []}
+ ]},
+ {<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5">>, [
+ {{<<"text">>, <<"*">>, []}, 300, []},
+ {{<<"text">>, <<"html">>, []}, 700, []},
+ {{<<"text">>, <<"html">>, [{<<"level">>, <<"1">>}]}, 1000, []},
+ {{<<"text">>, <<"html">>, [{<<"level">>, <<"2">>}]}, 400, []},
+ {{<<"*">>, <<"*">>, []}, 500, []}
+ ]},
+ {<<"text/html;level=1;quoted=\"hi hi hi\";"
+ "q=0.123;standalone;complex=gits, text/plain">>, [
+ {{<<"text">>, <<"html">>,
+ [{<<"level">>, <<"1">>}, {<<"quoted">>, <<"hi hi hi">>}]}, 123,
+ [<<"standalone">>, {<<"complex">>, <<"gits">>}]},
+ {{<<"text">>, <<"plain">>, []}, 1000, []}
+ ]},
+ {<<"text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2">>, [
+ {{<<"text">>, <<"html">>, []}, 1000, []},
+ {{<<"image">>, <<"gif">>, []}, 1000, []},
+ {{<<"image">>, <<"jpeg">>, []}, 1000, []},
+ {{<<"*">>, <<"*">>, []}, 200, []},
+ {{<<"*">>, <<"*">>, []}, 200, []}
+ ]},
+ {<<"text/plain; charset=UTF-8">>, [
+ {{<<"text">>, <<"plain">>, [{<<"charset">>, <<"utf-8">>}]}, 1000, []}
+ ]}
+ ],
+ [{V, fun() -> R = parse_accept(V) end} || {V, R} <- Tests].
+
+parse_accept_error_test_() ->
+ Tests = [
+ <<"audio/basic, */;q=0.5">>,
+ <<"audio/, audio/basic">>,
+ <<"aud\tio/basic">>,
+ <<"audio/basic;t=\"zero \\", 0, " woo\"">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_accept(V)) end} || V <- Tests].
+
+horse_parse_accept() ->
+ horse:repeat(20000,
+ parse_accept(<<"text/*;q=0.3, text/html;q=0.7, text/html;level=1, "
+ "text/html;level=2;q=0.4, */*;q=0.5">>)
+ ).
+-endif.
+
+%% Accept-Charset header.
+
+-spec parse_accept_charset(binary()) -> [{binary(), qvalue()}].
+parse_accept_charset(Charset) ->
+ nonempty(conneg_list(Charset, [])).
+
+conneg_list(<<>>, Acc) -> lists:reverse(Acc);
+conneg_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> conneg_list(R, Acc);
+conneg_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> ?LOWER(conneg, R, Acc, <<>>).
+
+conneg(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> ?LOWER(conneg, R, Acc, T);
+conneg(R, Acc, T) -> conneg_param_sep(R, Acc, T).
+
+conneg_param_sep(<<>>, Acc, T) -> lists:reverse([{T, 1000}|Acc]);
+conneg_param_sep(<< $,, R/bits >>, Acc, T) -> conneg_list(R, [{T, 1000}|Acc]);
+conneg_param_sep(<< $;, R/bits >>, Acc, T) -> conneg_before_weight(R, Acc, T);
+conneg_param_sep(<< C, R/bits >>, Acc, T) when ?IS_WS(C) -> conneg_param_sep(R, Acc, T).
+
+conneg_before_weight(<< C, R/bits >>, Acc, T) when ?IS_WS(C) -> conneg_before_weight(R, Acc, T);
+conneg_before_weight(<< $q, $=, R/bits >>, Acc, T) -> conneg_weight(R, Acc, T);
+%% Special clause for broken user agents that confuse ; and , separators.
+conneg_before_weight(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> ?LOWER(conneg, R, [{T, 1000}|Acc], <<>>).
+
+conneg_weight(<< "1.000", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 1000}|Acc]);
+conneg_weight(<< "1.00", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 1000}|Acc]);
+conneg_weight(<< "1.0", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 1000}|Acc]);
+conneg_weight(<< "1.", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 1000}|Acc]);
+conneg_weight(<< "1", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 1000}|Acc]);
+conneg_weight(<< "0.", A, B, C, R/bits >>, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ conneg_list_sep(R, [{T, (A - $0) * 100 + (B - $0) * 10 + (C - $0)}|Acc]);
+conneg_weight(<< "0.", A, B, R/bits >>, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B) ->
+ conneg_list_sep(R, [{T, (A - $0) * 100 + (B - $0) * 10}|Acc]);
+conneg_weight(<< "0.", A, R/bits >>, Acc, T) when ?IS_DIGIT(A) ->
+ conneg_list_sep(R, [{T, (A - $0) * 100}|Acc]);
+conneg_weight(<< "0.", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 0}|Acc]);
+conneg_weight(<< "0", R/bits >>, Acc, T) -> conneg_list_sep(R, [{T, 0}|Acc]).
+
+conneg_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+conneg_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> conneg_list_sep(R, Acc);
+conneg_list_sep(<< $,, R/bits >>, Acc) -> conneg_list(R, Acc).
+
+-ifdef(TEST).
+accept_charset() ->
+ ?LET({C, W},
+ {token(), weight()},
+ {C, W, iolist_to_binary([C, case W of
+ undefined -> [];
+ _ -> [<<";q=">>, qvalue_to_iodata(W)]
+ end])}
+ ).
+
+prop_parse_accept_charset() ->
+ ?FORALL(L,
+ non_empty(list(accept_charset())),
+ begin
+ << _, AcceptCharset/binary >> = iolist_to_binary([[$,, A] || {_, _, A} <- L]),
+ ResL = parse_accept_charset(AcceptCharset),
+ CheckedL = [begin
+ ResC =:= ?LOWER(Ch)
+ andalso (ResW =:= W orelse (W =:= undefined andalso ResW =:= 1000))
+ end || {{Ch, W, _}, {ResC, ResW}} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_accept_charset_test_() ->
+ Tests = [
+ {<<"iso-8859-5, unicode-1-1;q=0.8">>, [
+ {<<"iso-8859-5">>, 1000},
+ {<<"unicode-1-1">>, 800}
+ ]},
+ %% Some user agents send this invalid value for the Accept-Charset header
+ {<<"ISO-8859-1;utf-8;q=0.7,*;q=0.7">>, [
+ {<<"iso-8859-1">>, 1000},
+ {<<"utf-8">>, 700},
+ {<<"*">>, 700}
+ ]}
+ ],
+ [{V, fun() -> R = parse_accept_charset(V) end} || {V, R} <- Tests].
+
+parse_accept_charset_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_accept_charset(V)) end} || V <- Tests].
+
+horse_parse_accept_charset() ->
+ horse:repeat(20000,
+ parse_accept_charset(<<"iso-8859-5, unicode-1-1;q=0.8">>)
+ ).
+-endif.
+
+%% Accept-Encoding header.
+
+-spec parse_accept_encoding(binary()) -> [{binary(), qvalue()}].
+parse_accept_encoding(Encoding) ->
+ conneg_list(Encoding, []).
+
+-ifdef(TEST).
+accept_encoding() ->
+ ?LET({E, W},
+ {token(), weight()},
+ {E, W, iolist_to_binary([E, case W of
+ undefined -> [];
+ _ -> [<<";q=">>, qvalue_to_iodata(W)]
+ end])}
+ ).
+
+%% @todo This property seems useless, see prop_accept_charset.
+prop_parse_accept_encoding() ->
+ ?FORALL(L,
+ non_empty(list(accept_encoding())),
+ begin
+ << _, AcceptEncoding/binary >> = iolist_to_binary([[$,, A] || {_, _, A} <- L]),
+ ResL = parse_accept_encoding(AcceptEncoding),
+ CheckedL = [begin
+ ResE =:= ?LOWER(E)
+ andalso (ResW =:= W orelse (W =:= undefined andalso ResW =:= 1000))
+ end || {{E, W, _}, {ResE, ResW}} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_accept_encoding_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<"*">>, [{<<"*">>, 1000}]},
+ {<<"compress, gzip">>, [
+ {<<"compress">>, 1000},
+ {<<"gzip">>, 1000}
+ ]},
+ {<<"compress;q=0.5, gzip;q=1.0">>, [
+ {<<"compress">>, 500},
+ {<<"gzip">>, 1000}
+ ]},
+ {<<"gzip;q=1.0, identity; q=0.5, *;q=0">>, [
+ {<<"gzip">>, 1000},
+ {<<"identity">>, 500},
+ {<<"*">>, 0}
+ ]}
+ ],
+ [{V, fun() -> R = parse_accept_encoding(V) end} || {V, R} <- Tests].
+
+horse_parse_accept_encoding() ->
+ horse:repeat(20000,
+ parse_accept_encoding(<<"gzip;q=1.0, identity; q=0.5, *;q=0">>)
+ ).
+-endif.
+
+%% Accept-Language header.
+
+-spec parse_accept_language(binary()) -> [{binary(), qvalue()}].
+parse_accept_language(LanguageRange) ->
+ nonempty(language_range_list(LanguageRange, [])).
+
+language_range_list(<<>>, Acc) -> lists:reverse(Acc);
+language_range_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> language_range_list(R, Acc);
+language_range_list(<< $*, R/bits >>, Acc) -> language_range_param_sep(R, Acc, <<"*">>);
+language_range_list(<< C, R/bits >>, Acc) when ?IS_ALPHA(C) ->
+ ?LOWER(language_range, R, Acc, 1, <<>>).
+
+language_range(<< $-, C, R/bits >>, Acc, _, T) when ?IS_ALPHANUM(C) ->
+ ?LOWER(language_range_sub, R, Acc, 1, << T/binary, $- >>);
+language_range(<< C, R/bits >>, Acc, N, T) when ?IS_ALPHA(C), N < 8 ->
+ ?LOWER(language_range, R, Acc, N + 1, T);
+language_range(R, Acc, _, T) -> language_range_param_sep(R, Acc, T).
+
+language_range_sub(<< $-, R/bits >>, Acc, _, T) -> language_range_sub(R, Acc, 0, << T/binary, $- >>);
+language_range_sub(<< C, R/bits >>, Acc, N, T) when ?IS_ALPHANUM(C), N < 8 ->
+ ?LOWER(language_range_sub, R, Acc, N + 1, T);
+language_range_sub(R, Acc, _, T) -> language_range_param_sep(R, Acc, T).
+
+language_range_param_sep(<<>>, Acc, T) -> lists:reverse([{T, 1000}|Acc]);
+language_range_param_sep(<< $,, R/bits >>, Acc, T) -> language_range_list(R, [{T, 1000}|Acc]);
+language_range_param_sep(<< $;, R/bits >>, Acc, T) -> language_range_before_weight(R, Acc, T);
+language_range_param_sep(<< C, R/bits >>, Acc, T) when ?IS_WS(C) -> language_range_param_sep(R, Acc, T).
+
+language_range_before_weight(<< C, R/bits >>, Acc, T) when ?IS_WS(C) -> language_range_before_weight(R, Acc, T);
+language_range_before_weight(<< $q, $=, R/bits >>, Acc, T) -> language_range_weight(R, Acc, T);
+%% Special clause for broken user agents that confuse ; and , separators.
+language_range_before_weight(<< C, R/bits >>, Acc, T) when ?IS_ALPHA(C) ->
+ ?LOWER(language_range, R, [{T, 1000}|Acc], 1, <<>>).
+
+language_range_weight(<< "1.000", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 1000}|Acc]);
+language_range_weight(<< "1.00", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 1000}|Acc]);
+language_range_weight(<< "1.0", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 1000}|Acc]);
+language_range_weight(<< "1.", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 1000}|Acc]);
+language_range_weight(<< "1", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 1000}|Acc]);
+language_range_weight(<< "0.", A, B, C, R/bits >>, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ language_range_list_sep(R, [{T, (A - $0) * 100 + (B - $0) * 10 + (C - $0)}|Acc]);
+language_range_weight(<< "0.", A, B, R/bits >>, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B) ->
+ language_range_list_sep(R, [{T, (A - $0) * 100 + (B - $0) * 10}|Acc]);
+language_range_weight(<< "0.", A, R/bits >>, Acc, T) when ?IS_DIGIT(A) ->
+ language_range_list_sep(R, [{T, (A - $0) * 100}|Acc]);
+language_range_weight(<< "0.", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 0}|Acc]);
+language_range_weight(<< "0", R/bits >>, Acc, T) -> language_range_list_sep(R, [{T, 0}|Acc]).
+
+language_range_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+language_range_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> language_range_list_sep(R, Acc);
+language_range_list_sep(<< $,, R/bits >>, Acc) -> language_range_list(R, Acc).
+
+-ifdef(TEST).
+language_range_tag() ->
+ vector(1, 8, alpha()).
+
+language_range_subtag() ->
+ [$-, vector(1, 8, alphanum())].
+
+language_range() ->
+ [language_range_tag(), small_list(language_range_subtag())].
+
+accept_language() ->
+ ?LET({R, W},
+ {language_range(), weight()},
+ {iolist_to_binary(R), W, iolist_to_binary([R, case W of
+ undefined -> [];
+ _ -> [<<";q=">>, qvalue_to_iodata(W)]
+ end])}
+ ).
+
+prop_parse_accept_language() ->
+ ?FORALL(L,
+ non_empty(list(accept_language())),
+ begin
+ << _, AcceptLanguage/binary >> = iolist_to_binary([[$,, A] || {_, _, A} <- L]),
+ ResL = parse_accept_language(AcceptLanguage),
+ CheckedL = [begin
+ ResR =:= ?LOWER(R)
+ andalso (ResW =:= W orelse (W =:= undefined andalso ResW =:= 1000))
+ end || {{R, W, _}, {ResR, ResW}} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_accept_language_test_() ->
+ Tests = [
+ {<<"da, en-gb;q=0.8, en;q=0.7">>, [
+ {<<"da">>, 1000},
+ {<<"en-gb">>, 800},
+ {<<"en">>, 700}
+ ]},
+ {<<"en, en-US, en-cockney, i-cherokee, x-pig-latin, es-419">>, [
+ {<<"en">>, 1000},
+ {<<"en-us">>, 1000},
+ {<<"en-cockney">>, 1000},
+ {<<"i-cherokee">>, 1000},
+ {<<"x-pig-latin">>, 1000},
+ {<<"es-419">>, 1000}
+ ]}
+ ],
+ [{V, fun() -> R = parse_accept_language(V) end} || {V, R} <- Tests].
+
+parse_accept_language_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"loooooong">>,
+ <<"en-us-loooooong">>,
+ <<"419-en-us">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_accept_language(V)) end} || V <- Tests].
+
+horse_parse_accept_language() ->
+ horse:repeat(20000,
+ parse_accept_language(<<"da, en-gb;q=0.8, en;q=0.7">>)
+ ).
+-endif.
+
+%% Accept-Ranges header.
+
+-spec parse_accept_ranges(binary()) -> [binary()].
+parse_accept_ranges(<<"none">>) -> [];
+parse_accept_ranges(<<"bytes">>) -> [<<"bytes">>];
+parse_accept_ranges(AcceptRanges) ->
+ nonempty(token_ci_list(AcceptRanges, [])).
+
+-ifdef(TEST).
+parse_accept_ranges_test_() ->
+ Tests = [
+ {<<"bytes">>, [<<"bytes">>]},
+ {<<"none">>, []},
+ {<<"bytes, pages, kilos">>, [<<"bytes">>, <<"pages">>, <<"kilos">>]}
+ ],
+ [{V, fun() -> R = parse_accept_ranges(V) end} || {V, R} <- Tests].
+
+parse_accept_ranges_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_accept_ranges(V)) end} || V <- Tests].
+
+horse_parse_accept_ranges_none() ->
+ horse:repeat(200000,
+ parse_accept_ranges(<<"none">>)
+ ).
+
+horse_parse_accept_ranges_bytes() ->
+ horse:repeat(200000,
+ parse_accept_ranges(<<"bytes">>)
+ ).
+
+horse_parse_accept_ranges_other() ->
+ horse:repeat(200000,
+ parse_accept_ranges(<<"bytes, pages, kilos">>)
+ ).
+-endif.
+
+%% Access-Control-Allow-Credentials header.
+
+-spec access_control_allow_credentials() -> iodata().
+access_control_allow_credentials() -> <<"true">>.
+
+%% Access-Control-Allow-Headers header.
+
+-spec access_control_allow_headers([binary()]) -> iodata().
+access_control_allow_headers(Headers) ->
+ join_token_list(nonempty(Headers)).
+
+-ifdef(TEST).
+access_control_allow_headers_test_() ->
+ Tests = [
+ {[<<"accept">>], <<"accept">>},
+ {[<<"accept">>, <<"authorization">>, <<"content-type">>], <<"accept, authorization, content-type">>}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> R = iolist_to_binary(access_control_allow_headers(V)) end} || {V, R} <- Tests].
+
+access_control_allow_headers_error_test_() ->
+ Tests = [
+ []
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> {'EXIT', _} = (catch access_control_allow_headers(V)) end} || V <- Tests].
+
+horse_access_control_allow_headers() ->
+ horse:repeat(200000,
+ access_control_allow_headers([<<"accept">>, <<"authorization">>, <<"content-type">>])
+ ).
+-endif.
+
+%% Access-Control-Allow-Methods header.
+
+-spec access_control_allow_methods([binary()]) -> iodata().
+access_control_allow_methods(Methods) ->
+ join_token_list(nonempty(Methods)).
+
+-ifdef(TEST).
+access_control_allow_methods_test_() ->
+ Tests = [
+ {[<<"GET">>], <<"GET">>},
+ {[<<"GET">>, <<"POST">>, <<"DELETE">>], <<"GET, POST, DELETE">>}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> R = iolist_to_binary(access_control_allow_methods(V)) end} || {V, R} <- Tests].
+
+access_control_allow_methods_error_test_() ->
+ Tests = [
+ []
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> {'EXIT', _} = (catch access_control_allow_methods(V)) end} || V <- Tests].
+
+horse_access_control_allow_methods() ->
+ horse:repeat(200000,
+ access_control_allow_methods([<<"GET">>, <<"POST">>, <<"DELETE">>])
+ ).
+-endif.
+
+%% Access-Control-Allow-Origin header.
+
+-spec access_control_allow_origin({binary(), binary(), 0..65535} | reference() | '*') -> iodata().
+access_control_allow_origin({Scheme, Host, Port}) ->
+ case default_port(Scheme) of
+ Port -> [Scheme, <<"://">>, Host];
+ _ -> [Scheme, <<"://">>, Host, <<":">>, integer_to_binary(Port)]
+ end;
+access_control_allow_origin('*') -> <<$*>>;
+access_control_allow_origin(Ref) when is_reference(Ref) -> <<"null">>.
+
+-ifdef(TEST).
+access_control_allow_origin_test_() ->
+ Tests = [
+ {{<<"http">>, <<"www.example.org">>, 8080}, <<"http://www.example.org:8080">>},
+ {{<<"http">>, <<"www.example.org">>, 80}, <<"http://www.example.org">>},
+ {{<<"http">>, <<"192.0.2.1">>, 8080}, <<"http://192.0.2.1:8080">>},
+ {{<<"http">>, <<"192.0.2.1">>, 80}, <<"http://192.0.2.1">>},
+ {{<<"http">>, <<"[2001:db8::1]">>, 8080}, <<"http://[2001:db8::1]:8080">>},
+ {{<<"http">>, <<"[2001:db8::1]">>, 80}, <<"http://[2001:db8::1]">>},
+ {{<<"http">>, <<"[::ffff:192.0.2.1]">>, 8080}, <<"http://[::ffff:192.0.2.1]:8080">>},
+ {{<<"http">>, <<"[::ffff:192.0.2.1]">>, 80}, <<"http://[::ffff:192.0.2.1]">>},
+ {make_ref(), <<"null">>},
+ {'*', <<$*>>}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> R = iolist_to_binary(access_control_allow_origin(V)) end} || {V, R} <- Tests].
+
+horse_access_control_allow_origin() ->
+ horse:repeat(200000,
+ access_control_allow_origin({<<"http">>, <<"example.org">>, 8080})
+ ).
+-endif.
+
+%% Access-Control-Expose-Headers header.
+
+-spec access_control_expose_headers([binary()]) -> iodata().
+access_control_expose_headers(Headers) ->
+ join_token_list(nonempty(Headers)).
+
+-ifdef(TEST).
+access_control_expose_headers_test_() ->
+ Tests = [
+ {[<<"accept">>], <<"accept">>},
+ {[<<"accept">>, <<"authorization">>, <<"content-type">>], <<"accept, authorization, content-type">>}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> R = iolist_to_binary(access_control_expose_headers(V)) end} || {V, R} <- Tests].
+
+access_control_expose_headers_error_test_() ->
+ Tests = [
+ []
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> {'EXIT', _} = (catch access_control_expose_headers(V)) end} || V <- Tests].
+
+horse_access_control_expose_headers() ->
+ horse:repeat(200000,
+ access_control_expose_headers([<<"accept">>, <<"authorization">>, <<"content-type">>])
+ ).
+-endif.
+
+%% Access-Control-Max-Age header.
+
+-spec access_control_max_age(non_neg_integer()) -> iodata().
+access_control_max_age(MaxAge) -> integer_to_binary(MaxAge).
+
+-ifdef(TEST).
+access_control_max_age_test_() ->
+ Tests = [
+ {0, <<"0">>},
+ {42, <<"42">>},
+ {69, <<"69">>},
+ {1337, <<"1337">>},
+ {3495, <<"3495">>},
+ {1234567890, <<"1234567890">>}
+ ],
+ [{V, fun() -> R = access_control_max_age(V) end} || {V, R} <- Tests].
+-endif.
+
+%% Access-Control-Request-Headers header.
+
+-spec parse_access_control_request_headers(binary()) -> [binary()].
+parse_access_control_request_headers(Headers) ->
+ token_ci_list(Headers, []).
+
+-ifdef(TEST).
+headers() ->
+ ?LET(L,
+ list({ows(), ows(), token()}),
+ case L of
+ [] -> {[], <<>>};
+ _ ->
+ << _, Headers/binary >> = iolist_to_binary([[OWS1, $,, OWS2, M] || {OWS1, OWS2, M} <- L]),
+ {[?LOWER(M) || {_, _, M} <- L], Headers}
+ end).
+
+prop_parse_access_control_request_headers() ->
+ ?FORALL({L, Headers},
+ headers(),
+ L =:= parse_access_control_request_headers(Headers)).
+
+parse_access_control_request_headers_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<"Content-Type">>, [<<"content-type">>]},
+ {<<"accept, authorization, content-type">>, [<<"accept">>, <<"authorization">>, <<"content-type">>]},
+ {<<"accept,, , authorization,content-type">>, [<<"accept">>, <<"authorization">>, <<"content-type">>]}
+ ],
+ [{V, fun() -> R = parse_access_control_request_headers(V) end} || {V, R} <- Tests].
+
+horse_parse_access_control_request_headers() ->
+ horse:repeat(200000,
+ parse_access_control_request_headers(<<"accept, authorization, content-type">>)
+ ).
+-endif.
+
+%% Access-Control-Request-Method header.
+
+-spec parse_access_control_request_method(binary()) -> binary().
+parse_access_control_request_method(Method) ->
+ true = <<>> =/= Method,
+ ok = validate_token(Method),
+ Method.
+
+validate_token(<< C, R/bits >>) when ?IS_TOKEN(C) -> validate_token(R);
+validate_token(<<>>) -> ok.
+
+-ifdef(TEST).
+parse_access_control_request_method_test_() ->
+ Tests = [
+ <<"GET">>,
+ <<"HEAD">>,
+ <<"POST">>,
+ <<"PUT">>,
+ <<"DELETE">>,
+ <<"TRACE">>,
+ <<"CONNECT">>,
+ <<"whatever">>
+ ],
+ [{V, fun() -> R = parse_access_control_request_method(V) end} || {V, R} <- Tests].
+
+parse_access_control_request_method_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_access_control_request_method(V)) end} || V <- Tests].
+
+horse_parse_access_control_request_method() ->
+ horse:repeat(200000,
+ parse_access_control_request_method(<<"POST">>)
+ ).
+-endif.
+
+%% Age header.
+
+-spec parse_age(binary()) -> non_neg_integer().
+parse_age(Age) ->
+ I = binary_to_integer(Age),
+ true = I >= 0,
+ I.
+
+-ifdef(TEST).
+parse_age_test_() ->
+ Tests = [
+ {<<"0">>, 0},
+ {<<"42">>, 42},
+ {<<"69">>, 69},
+ {<<"1337">>, 1337},
+ {<<"3495">>, 3495},
+ {<<"1234567890">>, 1234567890}
+ ],
+ [{V, fun() -> R = parse_age(V) end} || {V, R} <- Tests].
+
+parse_age_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"123, 123">>,
+ <<"4.17">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_age(V)) end} || V <- Tests].
+-endif.
+
+%% Allow header.
+
+-spec parse_allow(binary()) -> [binary()].
+parse_allow(Allow) ->
+ token_list(Allow, []).
+
+-ifdef(TEST).
+allow() ->
+ ?LET(L,
+ list({ows(), ows(), token()}),
+ case L of
+ [] -> {[], <<>>};
+ _ ->
+ << _, Allow/binary >> = iolist_to_binary([[OWS1, $,, OWS2, M] || {OWS1, OWS2, M} <- L]),
+ {[M || {_, _, M} <- L], Allow}
+ end).
+
+prop_parse_allow() ->
+ ?FORALL({L, Allow},
+ allow(),
+ L =:= parse_allow(Allow)).
+
+parse_allow_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<"GET, HEAD, PUT">>, [<<"GET">>, <<"HEAD">>, <<"PUT">>]}
+ ],
+ [{V, fun() -> R = parse_allow(V) end} || {V, R} <- Tests].
+
+horse_parse_allow() ->
+ horse:repeat(200000,
+ parse_allow(<<"GET, HEAD, PUT">>)
+ ).
+-endif.
+
+%% Authorization header.
+%%
+%% We support Basic, Digest and Bearer schemes only.
+%%
+%% In the Digest case we do not validate that the mandatory
+%% fields are present. When parsing auth-params, we do not
+%% accept BWS characters around the "=".
+
+-spec parse_authorization(binary())
+ -> {basic, binary(), binary()}
+ | {bearer, binary()}
+ | {digest, [{binary(), binary()}]}.
+parse_authorization(<<B, A, S, I, C, " ", R/bits >>)
+ when ((B =:= $B) or (B =:= $b)), ((A =:= $A) or (A =:= $a)),
+ ((S =:= $S) or (S =:= $s)), ((I =:= $I) or (I =:= $i)),
+ ((C =:= $C) or (C =:= $c)) ->
+ auth_basic(base64:decode(R), <<>>);
+parse_authorization(<<B, E1, A, R1, E2, R2, " ", R/bits >>)
+ when (R =/= <<>>), ((B =:= $B) or (B =:= $b)),
+ ((E1 =:= $E) or (E1 =:= $e)), ((A =:= $A) or (A =:= $a)),
+ ((R1 =:= $R) or (R1 =:= $r)), ((E2 =:= $E) or (E2 =:= $e)),
+ ((R2 =:= $R) or (R2 =:= $r)) ->
+ validate_auth_bearer(R),
+ {bearer, R};
+parse_authorization(<<D, I, G, E, S, T, " ", R/bits >>)
+ when ((D =:= $D) or (D =:= $d)), ((I =:= $I) or (I =:= $i)),
+ ((G =:= $G) or (G =:= $g)), ((E =:= $E) or (E =:= $e)),
+ ((S =:= $S) or (S =:= $s)), ((T =:= $T) or (T =:= $t)) ->
+ {digest, nonempty(auth_digest_list(R, []))}.
+
+auth_basic(<< $:, Password/bits >>, UserID) -> {basic, UserID, Password};
+auth_basic(<< C, R/bits >>, UserID) -> auth_basic(R, << UserID/binary, C >>).
+
+validate_auth_bearer(<< C, R/bits >>) when ?IS_TOKEN68(C) -> validate_auth_bearer(R);
+validate_auth_bearer(<< $=, R/bits >>) -> validate_auth_bearer_eq(R);
+validate_auth_bearer(<<>>) -> ok.
+
+validate_auth_bearer_eq(<< $=, R/bits >>) -> validate_auth_bearer_eq(R);
+validate_auth_bearer_eq(<<>>) -> ok.
+
+auth_digest_list(<<>>, Acc) -> lists:reverse(Acc);
+auth_digest_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> auth_digest_list(R, Acc);
+auth_digest_list(<< "algorithm=", C, R/bits >>, Acc) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, <<"algorithm">>, << C >>);
+auth_digest_list(<< "cnonce=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"cnonce">>, <<>>);
+auth_digest_list(<< "nc=", A, B, C, D, E, F, G, H, R/bits >>, Acc)
+ when ?IS_LHEX(A), ?IS_LHEX(B), ?IS_LHEX(C), ?IS_LHEX(D),
+ ?IS_LHEX(E), ?IS_LHEX(F), ?IS_LHEX(G), ?IS_LHEX(H) ->
+ auth_digest_list_sep(R, [{<<"nc">>, << A, B, C, D, E, F, G, H >>}|Acc]);
+auth_digest_list(<< "nonce=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"nonce">>, <<>>);
+auth_digest_list(<< "opaque=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"opaque">>, <<>>);
+auth_digest_list(<< "qop=", C, R/bits >>, Acc) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, <<"qop">>, << C >>);
+auth_digest_list(<< "realm=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"realm">>, <<>>);
+auth_digest_list(<< "response=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"response">>, <<>>);
+auth_digest_list(<< "uri=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"uri">>, <<>>);
+auth_digest_list(<< "username=\"", R/bits >>, Acc) -> auth_digest_quoted(R, Acc, <<"username">>, <<>>);
+auth_digest_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
+ ?LOWER(auth_digest_param, R, Acc, <<>>).
+
+auth_digest_param(<< $=, $", R/bits >>, Acc, K) -> auth_digest_quoted(R, Acc, K, <<>>);
+auth_digest_param(<< $=, C, R/bits >>, Acc, K) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, K, << C >>);
+auth_digest_param(<< C, R/bits >>, Acc, K) when ?IS_TOKEN(C) ->
+ ?LOWER(auth_digest_param, R, Acc, K).
+
+auth_digest_token(<< C, R/bits >>, Acc, K, V) when ?IS_TOKEN(C) -> auth_digest_token(R, Acc, K, << V/binary, C >>);
+auth_digest_token(R, Acc, K, V) -> auth_digest_list_sep(R, [{K, V}|Acc]).
+
+auth_digest_quoted(<< $", R/bits >>, Acc, K, V) -> auth_digest_list_sep(R, [{K, V}|Acc]);
+auth_digest_quoted(<< $\\, C, R/bits >>, Acc, K, V) when ?IS_VCHAR_OBS(C) -> auth_digest_quoted(R, Acc, K, << V/binary, C >>);
+auth_digest_quoted(<< C, R/bits >>, Acc, K, V) when ?IS_VCHAR_OBS(C) -> auth_digest_quoted(R, Acc, K, << V/binary, C >>).
+
+auth_digest_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+auth_digest_list_sep(<< $,, R/bits >>, Acc) -> auth_digest_list(R, Acc);
+auth_digest_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> auth_digest_list_sep(R, Acc).
+
+-ifdef(TEST).
+parse_authorization_test_() ->
+ Tests = [
+ {<<"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>, {basic, <<"Aladdin">>, <<"open sesame">>}},
+ {<<"bAsIc QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>, {basic, <<"Aladdin">>, <<"open sesame">>}},
+ {<<"Bearer mF_9.B5f-4.1JqM">>, {bearer, <<"mF_9.B5f-4.1JqM">>}},
+ {<<"bEaRer mF_9.B5f-4.1JqM">>, {bearer, <<"mF_9.B5f-4.1JqM">>}},
+ {<<"Digest username=\"Mufasa\","
+ "realm=\"testrealm@host.com\","
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
+ "uri=\"/dir/index.html\","
+ "qop=auth,"
+ "nc=00000001,"
+ "cnonce=\"0a4f113b\","
+ "response=\"6629fae49393a05397450978507c4ef1\","
+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"">>,
+ {digest, [
+ {<<"username">>, <<"Mufasa">>},
+ {<<"realm">>, <<"testrealm@host.com">>},
+ {<<"nonce">>, <<"dcd98b7102dd2f0e8b11d0f600bfb0c093">>},
+ {<<"uri">>, <<"/dir/index.html">>},
+ {<<"qop">>, <<"auth">>},
+ {<<"nc">>, <<"00000001">>},
+ {<<"cnonce">>, <<"0a4f113b">>},
+ {<<"response">>, <<"6629fae49393a05397450978507c4ef1">>},
+ {<<"opaque">>, <<"5ccc069c403ebaf9f0171e9517f40e41">>}]}}
+ ],
+ [{V, fun() -> R = parse_authorization(V) end} || {V, R} <- Tests].
+
+horse_parse_authorization_basic() ->
+ horse:repeat(20000,
+ parse_authorization(<<"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==">>)
+ ).
+
+horse_parse_authorization_bearer() ->
+ horse:repeat(20000,
+ parse_authorization(<<"Bearer mF_9.B5f-4.1JqM">>)
+ ).
+
+horse_parse_authorization_digest() ->
+ horse:repeat(20000,
+ parse_authorization(
+ <<"Digest username=\"Mufasa\","
+ "realm=\"testrealm@host.com\","
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\","
+ "uri=\"/dir/index.html\","
+ "qop=auth,"
+ "nc=00000001,"
+ "cnonce=\"0a4f113b\","
+ "response=\"6629fae49393a05397450978507c4ef1\","
+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"">>)
+ ).
+-endif.
+
+%% Cache-Control header.
+%%
+%% In the fields list case, we do not support escaping, which shouldn't be needed anyway.
+
+-spec parse_cache_control(binary())
+ -> [binary() | {binary(), binary()} | {binary(), non_neg_integer()} | {binary(), [binary()]}].
+parse_cache_control(<<"no-cache">>) ->
+ [<<"no-cache">>];
+parse_cache_control(<<"max-age=0">>) ->
+ [{<<"max-age">>, 0}];
+parse_cache_control(CacheControl) ->
+ nonempty(cache_directive_list(CacheControl, [])).
+
+cache_directive_list(<<>>, Acc) -> lists:reverse(Acc);
+cache_directive_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C)-> cache_directive_list(R, Acc);
+cache_directive_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
+ ?LOWER(cache_directive, R, Acc, <<>>).
+
+cache_directive(<< $=, $", R/bits >>, Acc, T)
+ when (T =:= <<"no-cache">>) or (T =:= <<"private">>) ->
+ cache_directive_fields_list(R, Acc, T, []);
+cache_directive(<< $=, C, R/bits >>, Acc, T)
+ when ?IS_DIGIT(C), (T =:= <<"max-age">>) or (T =:= <<"max-stale">>)
+ or (T =:= <<"min-fresh">>) or (T =:= <<"s-maxage">>)
+ or (T =:= <<"stale-while-revalidate">>) or (T =:= <<"stale-if-error">>) ->
+ cache_directive_delta(R, Acc, T, (C - $0));
+cache_directive(<< $=, $", R/bits >>, Acc, T) -> cache_directive_quoted_string(R, Acc, T, <<>>);
+cache_directive(<< $=, C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> cache_directive_token(R, Acc, T, << C >>);
+cache_directive(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) ->
+ ?LOWER(cache_directive, R, Acc, T);
+cache_directive(R, Acc, T) -> cache_directive_list_sep(R, [T|Acc]).
+
+cache_directive_delta(<< C, R/bits >>, Acc, K, V) when ?IS_DIGIT(C) -> cache_directive_delta(R, Acc, K, V * 10 + (C - $0));
+cache_directive_delta(R, Acc, K, V) -> cache_directive_list_sep(R, [{K, V}|Acc]).
+
+cache_directive_fields_list(<< C, R/bits >>, Acc, K, L) when ?IS_WS_COMMA(C) -> cache_directive_fields_list(R, Acc, K, L);
+cache_directive_fields_list(<< $", R/bits >>, Acc, K, L) -> cache_directive_list_sep(R, [{K, lists:reverse(L)}|Acc]);
+cache_directive_fields_list(<< C, R/bits >>, Acc, K, L) when ?IS_TOKEN(C) ->
+ ?LOWER(cache_directive_field, R, Acc, K, L, <<>>).
+
+cache_directive_field(<< C, R/bits >>, Acc, K, L, F) when ?IS_TOKEN(C) ->
+ ?LOWER(cache_directive_field, R, Acc, K, L, F);
+cache_directive_field(R, Acc, K, L, F) -> cache_directive_fields_list_sep(R, Acc, K, [F|L]).
+
+cache_directive_fields_list_sep(<< C, R/bits >>, Acc, K, L) when ?IS_WS(C) -> cache_directive_fields_list_sep(R, Acc, K, L);
+cache_directive_fields_list_sep(<< $,, R/bits >>, Acc, K, L) -> cache_directive_fields_list(R, Acc, K, L);
+cache_directive_fields_list_sep(<< $", R/bits >>, Acc, K, L) -> cache_directive_list_sep(R, [{K, lists:reverse(L)}|Acc]).
+
+cache_directive_token(<< C, R/bits >>, Acc, K, V) when ?IS_TOKEN(C) -> cache_directive_token(R, Acc, K, << V/binary, C >>);
+cache_directive_token(R, Acc, K, V) -> cache_directive_list_sep(R, [{K, V}|Acc]).
+
+cache_directive_quoted_string(<< $", R/bits >>, Acc, K, V) -> cache_directive_list_sep(R, [{K, V}|Acc]);
+cache_directive_quoted_string(<< $\\, C, R/bits >>, Acc, K, V) when ?IS_VCHAR_OBS(C) ->
+ cache_directive_quoted_string(R, Acc, K, << V/binary, C >>);
+cache_directive_quoted_string(<< C, R/bits >>, Acc, K, V) when ?IS_VCHAR_OBS(C) ->
+ cache_directive_quoted_string(R, Acc, K, << V/binary, C >>).
+
+cache_directive_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+cache_directive_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> cache_directive_list_sep(R, Acc);
+cache_directive_list_sep(<< $,, R/bits >>, Acc) -> cache_directive_list(R, Acc).
+
+-ifdef(TEST).
+cache_directive_unreserved_token() ->
+ ?SUCHTHAT(T,
+ token(),
+ T =/= <<"max-age">> andalso T =/= <<"max-stale">> andalso T =/= <<"min-fresh">>
+ andalso T =/= <<"s-maxage">> andalso T =/= <<"no-cache">> andalso T =/= <<"private">>
+ andalso T =/= <<"stale-while-revalidate">> andalso T =/= <<"stale-if-error">>).
+
+cache_directive() ->
+ oneof([
+ token(),
+ {cache_directive_unreserved_token(), token()},
+ {cache_directive_unreserved_token(), quoted_string()},
+ {elements([
+ <<"max-age">>, <<"max-stale">>, <<"min-fresh">>, <<"s-maxage">>,
+ <<"stale-while-revalidate">>, <<"stale-if-error">>
+ ]), non_neg_integer()},
+ {fields, elements([<<"no-cache">>, <<"private">>]), small_list(token())}
+ ]).
+
+cache_control() ->
+ ?LET(L,
+ non_empty(list(cache_directive())),
+ begin
+ << _, CacheControl/binary >> = iolist_to_binary([[$,,
+ case C of
+ {fields, K, V} -> [K, $=, $", [[F, $,] || F <- V], $"];
+ {K, V} when is_integer(V) -> [K, $=, integer_to_binary(V)];
+ {K, V} -> [K, $=, V];
+ K -> K
+ end] || C <- L]),
+ {L, CacheControl}
+ end).
+
+prop_parse_cache_control() ->
+ ?FORALL({L, CacheControl},
+ cache_control(),
+ begin
+ ResL = parse_cache_control(CacheControl),
+ CheckedL = [begin
+ ExpectedCc = case Cc of
+ {fields, K, V} -> {?LOWER(K), [?LOWER(F) || F <- V]};
+ {K, V} -> {?LOWER(K), unquote(V)};
+ K -> ?LOWER(K)
+ end,
+ ExpectedCc =:= ResCc
+ end || {Cc, ResCc} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_cache_control_test_() ->
+ Tests = [
+ {<<"no-cache">>, [<<"no-cache">>]},
+ {<<"no-store">>, [<<"no-store">>]},
+ {<<"max-age=0">>, [{<<"max-age">>, 0}]},
+ {<<"max-age=30">>, [{<<"max-age">>, 30}]},
+ {<<"private, community=\"UCI\"">>, [<<"private">>, {<<"community">>, <<"UCI">>}]},
+ {<<"private=\"Content-Type, Content-Encoding, Content-Language\"">>,
+ [{<<"private">>, [<<"content-type">>, <<"content-encoding">>, <<"content-language">>]}]},
+ %% RFC5861 3.1.
+ {<<"max-age=600, stale-while-revalidate=30">>,
+ [{<<"max-age">>, 600}, {<<"stale-while-revalidate">>, 30}]},
+ %% RFC5861 4.1.
+ {<<"max-age=600, stale-if-error=1200">>,
+ [{<<"max-age">>, 600}, {<<"stale-if-error">>, 1200}]}
+ ],
+ [{V, fun() -> R = parse_cache_control(V) end} || {V, R} <- Tests].
+
+parse_cache_control_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_cache_control(V)) end} || V <- Tests].
+
+horse_parse_cache_control_no_cache() ->
+ horse:repeat(200000,
+ parse_cache_control(<<"no-cache">>)
+ ).
+
+horse_parse_cache_control_max_age_0() ->
+ horse:repeat(200000,
+ parse_cache_control(<<"max-age=0">>)
+ ).
+
+horse_parse_cache_control_max_age_30() ->
+ horse:repeat(200000,
+ parse_cache_control(<<"max-age=30">>)
+ ).
+
+horse_parse_cache_control_custom() ->
+ horse:repeat(200000,
+ parse_cache_control(<<"private, community=\"UCI\"">>)
+ ).
+
+horse_parse_cache_control_fields() ->
+ horse:repeat(200000,
+ parse_cache_control(<<"private=\"Content-Type, Content-Encoding, Content-Language\"">>)
+ ).
+-endif.
+
+%% Connection header.
+
+-spec parse_connection(binary()) -> [binary()].
+parse_connection(<<"close">>) ->
+ [<<"close">>];
+parse_connection(<<"keep-alive">>) ->
+ [<<"keep-alive">>];
+parse_connection(Connection) ->
+ nonempty(token_ci_list(Connection, [])).
+
+-ifdef(TEST).
+prop_parse_connection() ->
+ ?FORALL(L,
+ non_empty(list(token())),
+ begin
+ << _, Connection/binary >> = iolist_to_binary([[$,, C] || C <- L]),
+ ResL = parse_connection(Connection),
+ CheckedL = [?LOWER(Co) =:= ResC || {Co, ResC} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_connection_test_() ->
+ Tests = [
+ {<<"close">>, [<<"close">>]},
+ {<<"ClOsE">>, [<<"close">>]},
+ {<<"Keep-Alive">>, [<<"keep-alive">>]},
+ {<<"keep-alive, Upgrade">>, [<<"keep-alive">>, <<"upgrade">>]}
+ ],
+ [{V, fun() -> R = parse_connection(V) end} || {V, R} <- Tests].
+
+parse_connection_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_connection(V)) end} || V <- Tests].
+
+horse_parse_connection_close() ->
+ horse:repeat(200000,
+ parse_connection(<<"close">>)
+ ).
+
+horse_parse_connection_keepalive() ->
+ horse:repeat(200000,
+ parse_connection(<<"keep-alive">>)
+ ).
+
+horse_parse_connection_keepalive_upgrade() ->
+ horse:repeat(200000,
+ parse_connection(<<"keep-alive, upgrade">>)
+ ).
+-endif.
+
+%% Content-Encoding header.
+
+-spec parse_content_encoding(binary()) -> [binary()].
+parse_content_encoding(ContentEncoding) ->
+ nonempty(token_ci_list(ContentEncoding, [])).
+
+-ifdef(TEST).
+parse_content_encoding_test_() ->
+ Tests = [
+ {<<"gzip">>, [<<"gzip">>]}
+ ],
+ [{V, fun() -> R = parse_content_encoding(V) end} || {V, R} <- Tests].
+
+parse_content_encoding_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_content_encoding(V)) end} || V <- Tests].
+
+horse_parse_content_encoding() ->
+ horse:repeat(200000,
+ parse_content_encoding(<<"gzip">>)
+ ).
+-endif.
+
+%% Content-Language header.
+%%
+%% We do not support irregular deprecated tags that do not match the ABNF.
+
+-spec parse_content_language(binary()) -> [binary()].
+parse_content_language(ContentLanguage) ->
+ nonempty(langtag_list(ContentLanguage, [])).
+
+langtag_list(<<>>, Acc) -> lists:reverse(Acc);
+langtag_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> langtag_list(R, Acc);
+langtag_list(<< A, B, C, R/bits >>, Acc) when ?IS_ALPHA(A), ?IS_ALPHA(B), ?IS_ALPHA(C) ->
+ langtag_extlang(R, Acc, << ?LC(A), ?LC(B), ?LC(C) >>, 0);
+langtag_list(<< A, B, R/bits >>, Acc) when ?IS_ALPHA(A), ?IS_ALPHA(B) ->
+ langtag_extlang(R, Acc, << ?LC(A), ?LC(B) >>, 0);
+langtag_list(<< X, R/bits >>, Acc) when X =:= $x; X =:= $X -> langtag_privateuse_sub(R, Acc, << $x >>, 0).
+
+langtag_extlang(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T, _)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>);
+langtag_extlang(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T, _)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>);
+langtag_extlang(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T, _)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>);
+langtag_extlang(<< $-, A, B, C, D, E, R/bits >>, Acc, T, _)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>);
+langtag_extlang(<< $-, A, B, C, D, R/bits >>, Acc, T, _)
+ when ?IS_ALPHA(A), ?IS_ALPHA(B), ?IS_ALPHA(C), ?IS_ALPHA(D) ->
+ langtag_region(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D) >>);
+langtag_extlang(<< $-, A, B, C, R/bits >>, Acc, T, N)
+ when ?IS_ALPHA(A), ?IS_ALPHA(B), ?IS_ALPHA(C) ->
+ case N of
+ 2 -> langtag_script(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C) >>);
+ _ -> langtag_extlang(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C) >>, N + 1)
+ end;
+langtag_extlang(R, Acc, T, _) -> langtag_region(R, Acc, T).
+
+langtag_script(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>);
+langtag_script(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>);
+langtag_script(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>);
+langtag_script(<< $-, A, B, C, D, E, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>);
+langtag_script(<< $-, A, B, C, D, R/bits >>, Acc, T)
+ when ?IS_ALPHA(A), ?IS_ALPHA(B), ?IS_ALPHA(C), ?IS_ALPHA(D) ->
+ langtag_region(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D) >>);
+langtag_script(R, Acc, T) ->
+ langtag_region(R, Acc, T).
+
+langtag_region(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>);
+langtag_region(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>);
+langtag_region(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>);
+langtag_region(<< $-, A, B, C, D, E, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>);
+langtag_region(<< $-, A, B, C, D, R/bits >>, Acc, T)
+ when ?IS_DIGIT(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D) ->
+ langtag_variant(R, Acc, << T/binary, $-, A, ?LC(B), ?LC(C), ?LC(D) >>);
+langtag_region(<< $-, A, B, R/bits >>, Acc, T) when ?IS_ALPHA(A), ?IS_ALPHA(B) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B) >>);
+langtag_region(<< $-, A, B, C, R/bits >>, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ langtag_variant(R, Acc, << T/binary, $-, A, B, C >>);
+langtag_region(R, Acc, T) ->
+ langtag_variant(R, Acc, T).
+
+langtag_variant(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>);
+langtag_variant(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>);
+langtag_variant(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>);
+langtag_variant(<< $-, A, B, C, D, E, R/bits >>, Acc, T)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_variant(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>);
+langtag_variant(<< $-, A, B, C, D, R/bits >>, Acc, T)
+ when ?IS_DIGIT(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D) ->
+ langtag_variant(R, Acc, << T/binary, $-, A, ?LC(B), ?LC(C), ?LC(D) >>);
+langtag_variant(R, Acc, T) ->
+ langtag_extension(R, Acc, T).
+
+langtag_extension(<< $-, X, R/bits >>, Acc, T) when X =:= $x; X =:= $X -> langtag_privateuse_sub(R, Acc, << T/binary, $-, $x >>, 0);
+langtag_extension(<< $-, S, R/bits >>, Acc, T) when ?IS_ALPHANUM(S) -> langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(S) >>, 0);
+langtag_extension(R, Acc, T) -> langtag_list_sep(R, [T|Acc]).
+
+langtag_extension_sub(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, C, D, E, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, C, D, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, C, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C) >>, N + 1);
+langtag_extension_sub(<< $-, A, B, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B) ->
+ langtag_extension_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B) >>, N + 1);
+langtag_extension_sub(R, Acc, T, N) when N > 0 ->
+ langtag_extension(R, Acc, T).
+
+langtag_privateuse_sub(<< $-, A, B, C, D, E, F, G, H, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G), ?IS_ALPHANUM(H) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G), ?LC(H) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, C, D, E, F, G, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F), ?IS_ALPHANUM(G) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F), ?LC(G) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, C, D, E, F, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D),
+ ?IS_ALPHANUM(E), ?IS_ALPHANUM(F) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E), ?LC(F) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, C, D, E, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D), ?IS_ALPHANUM(E) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D), ?LC(E) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, C, D, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C), ?IS_ALPHANUM(D) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C), ?LC(D) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, C, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B), ?IS_ALPHANUM(C) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B), ?LC(C) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, B, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A), ?IS_ALPHANUM(B) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A), ?LC(B) >>, N + 1);
+langtag_privateuse_sub(<< $-, A, R/bits >>, Acc, T, N)
+ when ?IS_ALPHANUM(A) ->
+ langtag_privateuse_sub(R, Acc, << T/binary, $-, ?LC(A) >>, N + 1);
+langtag_privateuse_sub(R, Acc, T, N) when N > 0 -> langtag_list_sep(R, [T|Acc]).
+
+langtag_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+langtag_list_sep(<< $,, R/bits >>, Acc) -> langtag_list(R, Acc);
+langtag_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> langtag_list_sep(R, Acc).
+
+-ifdef(TEST).
+langtag_language() -> vector(2, 3, alpha()).
+langtag_extlang() -> vector(0, 3, [$-, alpha(), alpha(), alpha()]).
+langtag_script() -> oneof([[], [$-, alpha(), alpha(), alpha(), alpha()]]).
+langtag_region() -> oneof([[], [$-, alpha(), alpha()], [$-, digit(), digit(), digit()]]).
+
+langtag_variant() ->
+ small_list(frequency([
+ {4, [$-, vector(5, 8, alphanum())]},
+ {1, [$-, digit(), alphanum(), alphanum(), alphanum()]}
+ ])).
+
+langtag_extension() ->
+ small_list([$-, ?SUCHTHAT(S, alphanum(), S =/= $x andalso S =/= $X),
+ small_non_empty_list([$-, vector(2, 8, alphanum())])
+ ]).
+
+langtag_privateuse() -> oneof([[], [$-, langtag_privateuse_nodash()]]).
+langtag_privateuse_nodash() -> [elements([$x, $X]), small_non_empty_list([$-, vector(1, 8, alphanum())])].
+private_language_tag() -> ?LET(T, langtag_privateuse_nodash(), iolist_to_binary(T)).
+
+language_tag() ->
+ ?LET(IoList,
+ [langtag_language(), langtag_extlang(), langtag_script(), langtag_region(),
+ langtag_variant(), langtag_extension(), langtag_privateuse()],
+ iolist_to_binary(IoList)).
+
+content_language() ->
+ ?LET(L,
+ non_empty(list(frequency([
+ {90, language_tag()},
+ {10, private_language_tag()}
+ ]))),
+ begin
+ << _, ContentLanguage/binary >> = iolist_to_binary([[$,, T] || T <- L]),
+ {L, ContentLanguage}
+ end).
+
+prop_parse_content_language() ->
+ ?FORALL({L, ContentLanguage},
+ content_language(),
+ begin
+ ResL = parse_content_language(ContentLanguage),
+ CheckedL = [?LOWER(T) =:= ResT || {T, ResT} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_content_language_test_() ->
+ Tests = [
+ {<<"de">>, [<<"de">>]},
+ {<<"fr">>, [<<"fr">>]},
+ {<<"ja">>, [<<"ja">>]},
+ {<<"zh-Hant">>, [<<"zh-hant">>]},
+ {<<"zh-Hans">>, [<<"zh-hans">>]},
+ {<<"sr-Cyrl">>, [<<"sr-cyrl">>]},
+ {<<"sr-Latn">>, [<<"sr-latn">>]},
+ {<<"zh-cmn-Hans-CN">>, [<<"zh-cmn-hans-cn">>]},
+ {<<"cmn-Hans-CN">>, [<<"cmn-hans-cn">>]},
+ {<<"zh-yue-HK">>, [<<"zh-yue-hk">>]},
+ {<<"yue-HK">>, [<<"yue-hk">>]},
+ {<<"zh-Hans-CN">>, [<<"zh-hans-cn">>]},
+ {<<"sr-Latn-RS">>, [<<"sr-latn-rs">>]},
+ {<<"sl-rozaj">>, [<<"sl-rozaj">>]},
+ {<<"sl-rozaj-biske">>, [<<"sl-rozaj-biske">>]},
+ {<<"sl-nedis">>, [<<"sl-nedis">>]},
+ {<<"de-CH-1901">>, [<<"de-ch-1901">>]},
+ {<<"sl-IT-nedis">>, [<<"sl-it-nedis">>]},
+ {<<"hy-Latn-IT-arevela">>, [<<"hy-latn-it-arevela">>]},
+ {<<"de-DE">>, [<<"de-de">>]},
+ {<<"en-US">>, [<<"en-us">>]},
+ {<<"es-419">>, [<<"es-419">>]},
+ {<<"de-CH-x-phonebk">>, [<<"de-ch-x-phonebk">>]},
+ {<<"az-Arab-x-AZE-derbend">>, [<<"az-arab-x-aze-derbend">>]},
+ {<<"x-whatever">>, [<<"x-whatever">>]},
+ {<<"qaa-Qaaa-QM-x-southern">>, [<<"qaa-qaaa-qm-x-southern">>]},
+ {<<"de-Qaaa">>, [<<"de-qaaa">>]},
+ {<<"sr-Latn-QM">>, [<<"sr-latn-qm">>]},
+ {<<"sr-Qaaa-RS">>, [<<"sr-qaaa-rs">>]},
+ {<<"en-US-u-islamcal">>, [<<"en-us-u-islamcal">>]},
+ {<<"zh-CN-a-myext-x-private">>, [<<"zh-cn-a-myext-x-private">>]},
+ {<<"en-a-myext-b-another">>, [<<"en-a-myext-b-another">>]},
+ {<<"mn-Cyrl-MN">>, [<<"mn-cyrl-mn">>]},
+ {<<"MN-cYRL-mn">>, [<<"mn-cyrl-mn">>]},
+ {<<"mN-cYrL-Mn">>, [<<"mn-cyrl-mn">>]},
+ {<<"az-Arab-IR">>, [<<"az-arab-ir">>]},
+ {<<"zh-gan">>, [<<"zh-gan">>]},
+ {<<"zh-yue">>, [<<"zh-yue">>]},
+ {<<"zh-cmn">>, [<<"zh-cmn">>]},
+ {<<"de-AT">>, [<<"de-at">>]},
+ {<<"de-CH-1996">>, [<<"de-ch-1996">>]},
+ {<<"en-Latn-GB-boont-r-extended-sequence-x-private">>,
+ [<<"en-latn-gb-boont-r-extended-sequence-x-private">>]},
+ {<<"el-x-koine">>, [<<"el-x-koine">>]},
+ {<<"el-x-attic">>, [<<"el-x-attic">>]},
+ {<<"fr, en-US, es-419, az-Arab, x-pig-latin, man-Nkoo-GN">>,
+ [<<"fr">>, <<"en-us">>, <<"es-419">>, <<"az-arab">>, <<"x-pig-latin">>, <<"man-nkoo-gn">>]},
+ {<<"da">>, [<<"da">>]},
+ {<<"mi, en">>, [<<"mi">>, <<"en">>]}
+ ],
+ [{V, fun() -> R = parse_content_language(V) end} || {V, R} <- Tests].
+
+parse_content_language_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_content_language(V)) end} || V <- Tests].
+
+horse_parse_content_language() ->
+ horse:repeat(100000,
+ parse_content_language(<<"fr, en-US, es-419, az-Arab, x-pig-latin, man-Nkoo-GN">>)
+ ).
+-endif.
+
+%% Content-Length header.
+
+-spec parse_content_length(binary()) -> non_neg_integer().
+parse_content_length(ContentLength) ->
+ I = binary_to_integer(ContentLength),
+ true = I >= 0,
+ I.
+
+-ifdef(TEST).
+prop_parse_content_length() ->
+ ?FORALL(
+ X,
+ non_neg_integer(),
+ X =:= parse_content_length(integer_to_binary(X))
+ ).
+
+parse_content_length_test_() ->
+ Tests = [
+ {<<"0">>, 0},
+ {<<"42">>, 42},
+ {<<"69">>, 69},
+ {<<"1337">>, 1337},
+ {<<"3495">>, 3495},
+ {<<"1234567890">>, 1234567890}
+ ],
+ [{V, fun() -> R = parse_content_length(V) end} || {V, R} <- Tests].
+
+parse_content_length_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"-1">>,
+ <<"123, 123">>,
+ <<"4.17">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_content_length(V)) end} || V <- Tests].
+
+horse_parse_content_length_zero() ->
+ horse:repeat(100000,
+ parse_content_length(<<"0">>)
+ ).
+
+horse_parse_content_length_giga() ->
+ horse:repeat(100000,
+ parse_content_length(<<"1234567890">>)
+ ).
+-endif.
+
+%% Content-Range header.
+
+-spec parse_content_range(binary())
+ -> {bytes, non_neg_integer(), non_neg_integer(), non_neg_integer() | '*'}
+ | {bytes, '*', non_neg_integer()} | {binary(), binary()}.
+parse_content_range(<<"bytes */", C, R/bits >>) when ?IS_DIGIT(C) -> unsatisfied_range(R, C - $0);
+parse_content_range(<<"bytes ", C, R/bits >>) when ?IS_DIGIT(C) -> byte_range_first(R, C - $0);
+parse_content_range(<< C, R/bits >>) when ?IS_TOKEN(C) ->
+ ?LOWER(other_content_range_unit, R, <<>>).
+
+byte_range_first(<< $-, C, R/bits >>, First) when ?IS_DIGIT(C) -> byte_range_last(R, First, C - $0);
+byte_range_first(<< C, R/bits >>, First) when ?IS_DIGIT(C) -> byte_range_first(R, First * 10 + C - $0).
+
+byte_range_last(<<"/*">>, First, Last) -> {bytes, First, Last, '*'};
+byte_range_last(<< $/, C, R/bits >>, First, Last) when ?IS_DIGIT(C) -> byte_range_complete(R, First, Last, C - $0);
+byte_range_last(<< C, R/bits >>, First, Last) when ?IS_DIGIT(C) -> byte_range_last(R, First, Last * 10 + C - $0).
+
+byte_range_complete(<<>>, First, Last, Complete) -> {bytes, First, Last, Complete};
+byte_range_complete(<< C, R/bits >>, First, Last, Complete) when ?IS_DIGIT(C) ->
+ byte_range_complete(R, First, Last, Complete * 10 + C - $0).
+
+unsatisfied_range(<<>>, Complete) -> {bytes, '*', Complete};
+unsatisfied_range(<< C, R/bits >>, Complete) when ?IS_DIGIT(C) -> unsatisfied_range(R, Complete * 10 + C - $0).
+
+other_content_range_unit(<< $\s, R/bits >>, Unit) -> other_content_range_resp(R, Unit, <<>>);
+other_content_range_unit(<< C, R/bits >>, Unit) when ?IS_TOKEN(C) ->
+ ?LOWER(other_content_range_unit, R, Unit).
+
+other_content_range_resp(<<>>, Unit, Resp) -> {Unit, Resp};
+other_content_range_resp(<< C, R/bits >>, Unit, Resp) when ?IS_CHAR(C) -> other_content_range_resp(R, Unit, << Resp/binary, C >>).
+
+-ifdef(TEST).
+content_range() ->
+ ?LET(ContentRange,
+ oneof([
+ ?SUCHTHAT({bytes, First, Last, Complete},
+ {bytes, non_neg_integer(), non_neg_integer(), non_neg_integer()},
+ First =< Last andalso Last < Complete),
+ ?SUCHTHAT({bytes, First, Last, '*'},
+ {bytes, non_neg_integer(), non_neg_integer(), '*'},
+ First =< Last),
+ {bytes, '*', non_neg_integer()},
+ {token(), ?LET(L, list(abnf_char()), list_to_binary(L))}
+ ]),
+ {case ContentRange of
+ {Unit, Resp} when is_binary(Unit) -> {?LOWER(Unit), Resp};
+ _ -> ContentRange
+ end, case ContentRange of
+ {bytes, First, Last, '*'} ->
+ << "bytes ", (integer_to_binary(First))/binary, "-",
+ (integer_to_binary(Last))/binary, "/*">>;
+ {bytes, First, Last, Complete} ->
+ << "bytes ", (integer_to_binary(First))/binary, "-",
+ (integer_to_binary(Last))/binary, "/", (integer_to_binary(Complete))/binary >>;
+ {bytes, '*', Complete} ->
+ << "bytes */", (integer_to_binary(Complete))/binary >>;
+ {Unit, Resp} ->
+ << Unit/binary, $\s, Resp/binary >>
+ end}).
+
+prop_parse_content_range() ->
+ ?FORALL({Res, ContentRange},
+ content_range(),
+ Res =:= parse_content_range(ContentRange)).
+
+parse_content_range_test_() ->
+ Tests = [
+ {<<"bytes 21010-47021/47022">>, {bytes, 21010, 47021, 47022}},
+ {<<"bytes 500-999/8000">>, {bytes, 500, 999, 8000}},
+ {<<"bytes 7000-7999/8000">>, {bytes, 7000, 7999, 8000}},
+ {<<"bytes 42-1233/1234">>, {bytes, 42, 1233, 1234}},
+ {<<"bytes 42-1233/*">>, {bytes, 42, 1233, '*'}},
+ {<<"bytes */1234">>, {bytes, '*', 1234}},
+ {<<"bytes 0-499/1234">>, {bytes, 0, 499, 1234}},
+ {<<"bytes 500-999/1234">>, {bytes, 500, 999, 1234}},
+ {<<"bytes 500-1233/1234">>, {bytes, 500, 1233, 1234}},
+ {<<"bytes 734-1233/1234">>, {bytes, 734, 1233, 1234}},
+ {<<"bytes */47022">>, {bytes, '*', 47022}},
+ {<<"exampleunit 1.2-4.3/25">>, {<<"exampleunit">>, <<"1.2-4.3/25">>}},
+ {<<"exampleunit 11.2-14.3/25">>, {<<"exampleunit">>, <<"11.2-14.3/25">>}}
+ ],
+ [{V, fun() -> R = parse_content_range(V) end} || {V, R} <- Tests].
+
+parse_content_range_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_content_range(V)) end} || V <- Tests].
+
+horse_parse_content_range_bytes() ->
+ horse:repeat(200000,
+ parse_content_range(<<"bytes 21010-47021/47022">>)
+ ).
+
+horse_parse_content_range_other() ->
+ horse:repeat(200000,
+ parse_content_range(<<"exampleunit 11.2-14.3/25">>)
+ ).
+-endif.
+
+%% Content-Type header.
+
+-spec parse_content_type(binary()) -> media_type().
+parse_content_type(<< C, R/bits >>) when ?IS_TOKEN(C) ->
+ ?LOWER(media_type, R, <<>>).
+
+media_type(<< $/, C, R/bits >>, T) when ?IS_TOKEN(C) ->
+ ?LOWER(media_subtype, R, T, <<>>);
+media_type(<< C, R/bits >>, T) when ?IS_TOKEN(C) ->
+ ?LOWER(media_type, R, T).
+
+media_subtype(<< C, R/bits >>, T, S) when ?IS_TOKEN(C) ->
+ ?LOWER(media_subtype, R, T, S);
+media_subtype(R, T, S) -> media_param_sep(R, T, S, []).
+
+media_param_sep(<<>>, T, S, P) -> {T, S, lists:reverse(P)};
+media_param_sep(<< $;, R/bits >>, T, S, P) -> media_before_param(R, T, S, P);
+media_param_sep(<< C, R/bits >>, T, S, P) when ?IS_WS(C) -> media_param_sep(R, T, S, P).
+
+media_before_param(<< C, R/bits >>, T, S, P) when ?IS_WS(C)-> media_before_param(R, T, S, P);
+media_before_param(<< "charset=", $", R/bits >>, T, S, P) -> media_charset_quoted(R, T, S, P, <<>>);
+media_before_param(<< "charset=", R/bits >>, T, S, P) -> media_charset(R, T, S, P, <<>>);
+media_before_param(<< C, R/bits >>, T, S, P) when ?IS_TOKEN(C) ->
+ ?LOWER(media_param, R, T, S, P, <<>>).
+
+media_charset_quoted(<< $", R/bits >>, T, S, P, V) ->
+ media_param_sep(R, T, S, [{<<"charset">>, V}|P]);
+media_charset_quoted(<< $\\, C, R/bits >>, T, S, P, V) when ?IS_VCHAR_OBS(C) ->
+ ?LOWER(media_charset_quoted, R, T, S, P, V);
+media_charset_quoted(<< C, R/bits >>, T, S, P, V) when ?IS_VCHAR_OBS(C) ->
+ ?LOWER(media_charset_quoted, R, T, S, P, V).
+
+media_charset(<< C, R/bits >>, T, S, P, V) when ?IS_TOKEN(C) ->
+ ?LOWER(media_charset, R, T, S, P, V);
+media_charset(R, T, S, P, V) -> media_param_sep(R, T, S, [{<<"charset">>, V}|P]).
+
+media_param(<< $=, $", R/bits >>, T, S, P, K) -> media_quoted(R, T, S, P, K, <<>>);
+media_param(<< $=, C, R/bits >>, T, S, P, K) when ?IS_TOKEN(C) -> media_value(R, T, S, P, K, << C >>);
+media_param(<< C, R/bits >>, T, S, P, K) when ?IS_TOKEN(C) ->
+ ?LOWER(media_param, R, T, S, P, K).
+
+media_quoted(<< $", R/bits >>, T, S, P, K, V) -> media_param_sep(R, T, S, [{K, V}|P]);
+media_quoted(<< $\\, C, R/bits >>, T, S, P, K, V) when ?IS_VCHAR_OBS(C) -> media_quoted(R, T, S, P, K, << V/binary, C >>);
+media_quoted(<< C, R/bits >>, T, S, P, K, V) when ?IS_VCHAR_OBS(C) -> media_quoted(R, T, S, P, K, << V/binary, C >>).
+
+media_value(<< C, R/bits >>, T, S, P, K, V) when ?IS_TOKEN(C) -> media_value(R, T, S, P, K, << V/binary, C >>);
+media_value(R, T, S, P, K, V) -> media_param_sep(R, T, S, [{K, V}|P]).
+
+-ifdef(TEST).
+media_type_parameter() ->
+ frequency([
+ {90, parameter()},
+ {10, {<<"charset">>, oneof([token(), quoted_string()]), <<>>, <<>>}}
+ ]).
+
+media_type() ->
+ ?LET({T, S, P},
+ {token(), token(), small_list(media_type_parameter())},
+ {T, S, P, iolist_to_binary([T, $/, S, [[OWS1, $;, OWS2, K, $=, V] || {K, V, OWS1, OWS2} <- P]])}
+ ).
+
+prop_parse_content_type() ->
+ ?FORALL({T, S, P, MediaType},
+ media_type(),
+ begin
+ {ResT, ResS, ResP} = parse_content_type(MediaType),
+ ExpectedP = [case ?LOWER(K) of
+ <<"charset">> -> {<<"charset">>, ?LOWER(unquote(V))};
+ LowK -> {LowK, unquote(V)}
+ end || {K, V, _, _} <- P],
+ ResT =:= ?LOWER(T)
+ andalso ResS =:= ?LOWER(S)
+ andalso ResP =:= ExpectedP
+ end
+ ).
+
+parse_content_type_test_() ->
+ Tests = [
+ {<<"text/html;charset=utf-8">>,
+ {<<"text">>, <<"html">>, [{<<"charset">>, <<"utf-8">>}]}},
+ {<<"text/html;charset=UTF-8">>,
+ {<<"text">>, <<"html">>, [{<<"charset">>, <<"utf-8">>}]}},
+ {<<"Text/HTML;Charset=\"utf-8\"">>,
+ {<<"text">>, <<"html">>, [{<<"charset">>, <<"utf-8">>}]}},
+ {<<"text/html; charset=\"utf-8\"">>,
+ {<<"text">>, <<"html">>, [{<<"charset">>, <<"utf-8">>}]}},
+ {<<"text/html; charset=ISO-8859-4">>,
+ {<<"text">>, <<"html">>, [{<<"charset">>, <<"iso-8859-4">>}]}},
+ {<<"text/plain; charset=iso-8859-4">>,
+ {<<"text">>, <<"plain">>, [{<<"charset">>, <<"iso-8859-4">>}]}},
+ {<<"multipart/form-data \t;Boundary=\"MultipartIsUgly\"">>,
+ {<<"multipart">>, <<"form-data">>, [
+ {<<"boundary">>, <<"MultipartIsUgly">>}
+ ]}},
+ {<<"foo/bar; one=FirstParam; two=SecondParam">>,
+ {<<"foo">>, <<"bar">>, [
+ {<<"one">>, <<"FirstParam">>},
+ {<<"two">>, <<"SecondParam">>}
+ ]}}
+ ],
+ [{V, fun() -> R = parse_content_type(V) end} || {V, R} <- Tests].
+
+horse_parse_content_type() ->
+ horse:repeat(200000,
+ parse_content_type(<<"text/html;charset=utf-8">>)
+ ).
+-endif.
+
+%% Cookie header.
+
+-spec parse_cookie(binary()) -> [{binary(), binary()}].
+parse_cookie(Cookie) ->
+ cow_cookie:parse_cookie(Cookie).
+
+%% Date header.
+
+-spec parse_date(binary()) -> calendar:datetime().
+parse_date(Date) ->
+ cow_date:parse_date(Date).
+
+-ifdef(TEST).
+parse_date_test_() ->
+ Tests = [
+ {<<"Tue, 15 Nov 1994 08:12:31 GMT">>, {{1994, 11, 15}, {8, 12, 31}}}
+ ],
+ [{V, fun() -> R = parse_date(V) end} || {V, R} <- Tests].
+-endif.
+
+%% ETag header.
+
+-spec parse_etag(binary()) -> etag().
+parse_etag(<< $W, $/, $", R/bits >>) ->
+ etag(R, weak, <<>>);
+parse_etag(<< $", R/bits >>) ->
+ etag(R, strong, <<>>).
+
+etag(<< $" >>, Strength, Tag) ->
+ {Strength, Tag};
+etag(<< C, R/bits >>, Strength, Tag) when ?IS_ETAGC(C) ->
+ etag(R, Strength, << Tag/binary, C >>).
+
+-ifdef(TEST).
+etagc() ->
+ ?SUCHTHAT(C, integer(16#21, 16#ff), C =/= 16#22 andalso C =/= 16#7f).
+
+etag() ->
+ ?LET({Strength, Tag},
+ {elements([weak, strong]), list(etagc())},
+ begin
+ TagBin = list_to_binary(Tag),
+ {{Strength, TagBin},
+ case Strength of
+ weak -> << $W, $/, $", TagBin/binary, $" >>;
+ strong -> << $", TagBin/binary, $" >>
+ end}
+ end).
+
+prop_parse_etag() ->
+ ?FORALL({Tag, TagBin},
+ etag(),
+ Tag =:= parse_etag(TagBin)).
+
+parse_etag_test_() ->
+ Tests = [
+ {<<"\"xyzzy\"">>, {strong, <<"xyzzy">>}},
+ {<<"W/\"xyzzy\"">>, {weak, <<"xyzzy">>}},
+ {<<"\"\"">>, {strong, <<>>}}
+ ],
+ [{V, fun() -> R = parse_etag(V) end} || {V, R} <- Tests].
+
+parse_etag_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"\"">>,
+ <<"W">>,
+ <<"W/">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_etag(V)) end} || V <- Tests].
+
+horse_parse_etag() ->
+ horse:repeat(200000,
+ parse_etag(<<"W/\"xyzzy\"">>)
+ ).
+-endif.
+
+%% Expect header.
+
+-spec parse_expect(binary()) -> continue.
+parse_expect(<<"100-continue">>) ->
+ continue;
+parse_expect(<<"100-", C, O, N, T, I, M, U, E >>)
+ when (C =:= $C) or (C =:= $c), (O =:= $O) or (O =:= $o),
+ (N =:= $N) or (N =:= $n), (T =:= $T) or (T =:= $t),
+ (I =:= $I) or (I =:= $i), (M =:= $N) or (M =:= $n),
+ (U =:= $U) or (U =:= $u), (E =:= $E) or (E =:= $e) ->
+ continue.
+
+-ifdef(TEST).
+expect() ->
+ ?LET(E,
+ [$1, $0, $0, $-,
+ elements([$c, $C]), elements([$o, $O]), elements([$n, $N]),
+ elements([$t, $T]), elements([$i, $I]), elements([$n, $N]),
+ elements([$u, $U]), elements([$e, $E])],
+ list_to_binary(E)).
+
+prop_parse_expect() ->
+ ?FORALL(E, expect(), continue =:= parse_expect(E)).
+
+parse_expect_test_() ->
+ Tests = [
+ <<"100-continue">>,
+ <<"100-CONTINUE">>,
+ <<"100-Continue">>,
+ <<"100-CoNtInUe">>
+ ],
+ [{V, fun() -> continue = parse_expect(V) end} || V <- Tests].
+
+parse_expect_error_test_() ->
+ Tests = [
+ <<>>,
+ <<" ">>,
+ <<"200-OK">>,
+ <<"Cookies">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_expect(V)) end} || V <- Tests].
+
+horse_parse_expect() ->
+ horse:repeat(200000,
+ parse_expect(<<"100-continue">>)
+ ).
+-endif.
+
+%% Expires header.
+%%
+%% Recipients must interpret invalid date formats as a date
+%% in the past. The value "0" is commonly used.
+
+-spec parse_expires(binary()) -> calendar:datetime().
+parse_expires(<<"0">>) ->
+ {{1, 1, 1}, {0, 0, 0}};
+parse_expires(Expires) ->
+ try
+ cow_date:parse_date(Expires)
+ catch _:_ ->
+ {{1, 1, 1}, {0, 0, 0}}
+ end.
+
+-ifdef(TEST).
+parse_expires_test_() ->
+ Tests = [
+ {<<"0">>, {{1, 1, 1}, {0, 0, 0}}},
+ {<<"Thu, 01 Dec 1994 nope invalid">>, {{1, 1, 1}, {0, 0, 0}}},
+ {<<"Thu, 01 Dec 1994 16:00:00 GMT">>, {{1994, 12, 1}, {16, 0, 0}}}
+ ],
+ [{V, fun() -> R = parse_expires(V) end} || {V, R} <- Tests].
+
+horse_parse_expires_0() ->
+ horse:repeat(200000,
+ parse_expires(<<"0">>)
+ ).
+
+horse_parse_expires_invalid() ->
+ horse:repeat(200000,
+ parse_expires(<<"Thu, 01 Dec 1994 nope invalid">>)
+ ).
+-endif.
+
+%% Host header.
+%%
+%% We only seek to have legal characters and separate the
+%% host and port values. The number of segments in the host
+%% or the size of each segment is not checked.
+%%
+%% There is no way to distinguish IPv4 addresses from regular
+%% names until the last segment is reached therefore we do not
+%% differentiate them.
+%%
+%% The following valid hosts are currently rejected: IPv6
+%% addresses with a zone identifier; IPvFuture addresses;
+%% and percent-encoded addresses.
+
+-spec parse_host(binary()) -> {binary(), 0..65535 | undefined}.
+parse_host(<< $[, R/bits >>) ->
+ ipv6_address(R, << $[ >>);
+parse_host(Host) ->
+ reg_name(Host, <<>>).
+
+ipv6_address(<< $] >>, IP) -> {<< IP/binary, $] >>, undefined};
+ipv6_address(<< $], $:, Port/bits >>, IP) -> {<< IP/binary, $] >>, binary_to_integer(Port)};
+ipv6_address(<< C, R/bits >>, IP) when ?IS_HEX(C) or (C =:= $:) or (C =:= $.) ->
+ ?LOWER(ipv6_address, R, IP).
+
+reg_name(<<>>, Name) -> {Name, undefined};
+reg_name(<< $:, Port/bits >>, Name) -> {Name, binary_to_integer(Port)};
+reg_name(<< C, R/bits >>, Name) when ?IS_URI_UNRESERVED(C) or ?IS_URI_SUB_DELIMS(C) ->
+ ?LOWER(reg_name, R, Name).
+
+-ifdef(TEST).
+host_chars() -> "!$&'()*+,-.0123456789;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~".
+host() -> vector(1, 255, elements(host_chars())).
+
+host_port() ->
+ ?LET({Host, Port},
+ {host(), oneof([undefined, integer(1, 65535)])},
+ begin
+ HostBin = list_to_binary(Host),
+ {{?LOWER(HostBin), Port},
+ case Port of
+ undefined -> HostBin;
+ _ -> << HostBin/binary, $:, (integer_to_binary(Port))/binary >>
+ end}
+ end).
+
+prop_parse_host() ->
+ ?FORALL({Res, Host}, host_port(), Res =:= parse_host(Host)).
+
+parse_host_test_() ->
+ Tests = [
+ {<<>>, {<<>>, undefined}},
+ {<<"www.example.org:8080">>, {<<"www.example.org">>, 8080}},
+ {<<"www.example.org">>, {<<"www.example.org">>, undefined}},
+ {<<"192.0.2.1:8080">>, {<<"192.0.2.1">>, 8080}},
+ {<<"192.0.2.1">>, {<<"192.0.2.1">>, undefined}},
+ {<<"[2001:db8::1]:8080">>, {<<"[2001:db8::1]">>, 8080}},
+ {<<"[2001:db8::1]">>, {<<"[2001:db8::1]">>, undefined}},
+ {<<"[::ffff:192.0.2.1]:8080">>, {<<"[::ffff:192.0.2.1]">>, 8080}},
+ {<<"[::ffff:192.0.2.1]">>, {<<"[::ffff:192.0.2.1]">>, undefined}}
+ ],
+ [{V, fun() -> R = parse_host(V) end} || {V, R} <- Tests].
+
+horse_parse_host_blue_example_org() ->
+ horse:repeat(200000,
+ parse_host(<<"blue.example.org:8080">>)
+ ).
+
+horse_parse_host_ipv4() ->
+ horse:repeat(200000,
+ parse_host(<<"192.0.2.1:8080">>)
+ ).
+
+horse_parse_host_ipv6() ->
+ horse:repeat(200000,
+ parse_host(<<"[2001:db8::1]:8080">>)
+ ).
+
+horse_parse_host_ipv6_v4() ->
+ horse:repeat(200000,
+ parse_host(<<"[::ffff:192.0.2.1]:8080">>)
+ ).
+-endif.
+
+%% HTTP2-Settings header.
+
+-spec parse_http2_settings(binary()) -> map().
+parse_http2_settings(HTTP2Settings) ->
+ cow_http2:parse_settings_payload(base64:decode(HTTP2Settings)).
+
+%% If-Match header.
+
+-spec parse_if_match(binary()) -> '*' | [etag()].
+parse_if_match(<<"*">>) ->
+ '*';
+parse_if_match(IfMatch) ->
+ nonempty(etag_list(IfMatch, [])).
+
+etag_list(<<>>, Acc) -> lists:reverse(Acc);
+etag_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> etag_list(R, Acc);
+etag_list(<< $W, $/, $", R/bits >>, Acc) -> etag(R, Acc, weak, <<>>);
+etag_list(<< $", R/bits >>, Acc) -> etag(R, Acc, strong, <<>>).
+
+etag(<< $", R/bits >>, Acc, Strength, Tag) -> etag_list_sep(R, [{Strength, Tag}|Acc]);
+etag(<< C, R/bits >>, Acc, Strength, Tag) when ?IS_ETAGC(C) -> etag(R, Acc, Strength, << Tag/binary, C >>).
+
+etag_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+etag_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> etag_list_sep(R, Acc);
+etag_list_sep(<< $,, R/bits >>, Acc) -> etag_list(R, Acc).
+
+-ifdef(TEST).
+prop_parse_if_match() ->
+ ?FORALL(L,
+ non_empty(list(etag())),
+ begin
+ << _, IfMatch/binary >> = iolist_to_binary([[$,, T] || {_, T} <- L]),
+ ResL = parse_if_match(IfMatch),
+ CheckedL = [T =:= ResT || {{T, _}, ResT} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_if_match_test_() ->
+ Tests = [
+ {<<"\"xyzzy\"">>, [{strong, <<"xyzzy">>}]},
+ {<<"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"">>,
+ [{strong, <<"xyzzy">>}, {strong, <<"r2d2xxxx">>}, {strong, <<"c3piozzzz">>}]},
+ {<<"*">>, '*'}
+ ],
+ [{V, fun() -> R = parse_if_match(V) end} || {V, R} <- Tests].
+
+parse_if_match_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_if_match(V)) end} || V <- Tests].
+
+horse_parse_if_match() ->
+ horse:repeat(200000,
+ parse_if_match(<<"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"">>)
+ ).
+-endif.
+
+%% If-Modified-Since header.
+
+-spec parse_if_modified_since(binary()) -> calendar:datetime().
+parse_if_modified_since(IfModifiedSince) ->
+ cow_date:parse_date(IfModifiedSince).
+
+-ifdef(TEST).
+parse_if_modified_since_test_() ->
+ Tests = [
+ {<<"Sat, 29 Oct 1994 19:43:31 GMT">>, {{1994, 10, 29}, {19, 43, 31}}}
+ ],
+ [{V, fun() -> R = parse_if_modified_since(V) end} || {V, R} <- Tests].
+-endif.
+
+%% If-None-Match header.
+
+-spec parse_if_none_match(binary()) -> '*' | [etag()].
+parse_if_none_match(<<"*">>) ->
+ '*';
+parse_if_none_match(IfNoneMatch) ->
+ nonempty(etag_list(IfNoneMatch, [])).
+
+-ifdef(TEST).
+parse_if_none_match_test_() ->
+ Tests = [
+ {<<"\"xyzzy\"">>, [{strong, <<"xyzzy">>}]},
+ {<<"W/\"xyzzy\"">>, [{weak, <<"xyzzy">>}]},
+ {<<"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"">>,
+ [{strong, <<"xyzzy">>}, {strong, <<"r2d2xxxx">>}, {strong, <<"c3piozzzz">>}]},
+ {<<"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\"">>,
+ [{weak, <<"xyzzy">>}, {weak, <<"r2d2xxxx">>}, {weak, <<"c3piozzzz">>}]},
+ {<<"*">>, '*'}
+ ],
+ [{V, fun() -> R = parse_if_none_match(V) end} || {V, R} <- Tests].
+
+parse_if_none_match_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_if_none_match(V)) end} || V <- Tests].
+
+horse_parse_if_none_match() ->
+ horse:repeat(200000,
+ parse_if_none_match(<<"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\"">>)
+ ).
+-endif.
+
+%% If-Range header.
+
+-spec parse_if_range(binary()) -> etag() | calendar:datetime().
+parse_if_range(<< $W, $/, $", R/bits >>) ->
+ etag(R, weak, <<>>);
+parse_if_range(<< $", R/bits >>) ->
+ etag(R, strong, <<>>);
+parse_if_range(IfRange) ->
+ cow_date:parse_date(IfRange).
+
+-ifdef(TEST).
+parse_if_range_test_() ->
+ Tests = [
+ {<<"W/\"xyzzy\"">>, {weak, <<"xyzzy">>}},
+ {<<"\"xyzzy\"">>, {strong, <<"xyzzy">>}},
+ {<<"Sat, 29 Oct 1994 19:43:31 GMT">>, {{1994, 10, 29}, {19, 43, 31}}}
+ ],
+ [{V, fun() -> R = parse_if_range(V) end} || {V, R} <- Tests].
+
+parse_if_range_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_if_range(V)) end} || V <- Tests].
+
+horse_parse_if_range_etag() ->
+ horse:repeat(200000,
+ parse_if_range(<<"\"xyzzy\"">>)
+ ).
+
+horse_parse_if_range_date() ->
+ horse:repeat(200000,
+ parse_if_range(<<"Sat, 29 Oct 1994 19:43:31 GMT">>)
+ ).
+-endif.
+
+%% If-Unmodified-Since header.
+
+-spec parse_if_unmodified_since(binary()) -> calendar:datetime().
+parse_if_unmodified_since(IfModifiedSince) ->
+ cow_date:parse_date(IfModifiedSince).
+
+-ifdef(TEST).
+parse_if_unmodified_since_test_() ->
+ Tests = [
+ {<<"Sat, 29 Oct 1994 19:43:31 GMT">>, {{1994, 10, 29}, {19, 43, 31}}}
+ ],
+ [{V, fun() -> R = parse_if_unmodified_since(V) end} || {V, R} <- Tests].
+-endif.
+
+%% Last-Modified header.
+
+-spec parse_last_modified(binary()) -> calendar:datetime().
+parse_last_modified(LastModified) ->
+ cow_date:parse_date(LastModified).
+
+-ifdef(TEST).
+parse_last_modified_test_() ->
+ Tests = [
+ {<<"Tue, 15 Nov 1994 12:45:26 GMT">>, {{1994, 11, 15}, {12, 45, 26}}}
+ ],
+ [{V, fun() -> R = parse_last_modified(V) end} || {V, R} <- Tests].
+-endif.
+
+%% Link header.
+
+-spec parse_link(binary()) -> [cow_link:link()].
+parse_link(Link) ->
+ cow_link:parse_link(Link).
+
+%% Max-Forwards header.
+
+-spec parse_max_forwards(binary()) -> non_neg_integer().
+parse_max_forwards(MaxForwards) ->
+ I = binary_to_integer(MaxForwards),
+ true = I >= 0,
+ I.
+
+-ifdef(TEST).
+prop_parse_max_forwards() ->
+ ?FORALL(
+ X,
+ non_neg_integer(),
+ X =:= parse_max_forwards(integer_to_binary(X))
+ ).
+
+parse_max_forwards_test_() ->
+ Tests = [
+ {<<"0">>, 0},
+ {<<"42">>, 42},
+ {<<"69">>, 69},
+ {<<"1337">>, 1337},
+ {<<"1234567890">>, 1234567890}
+ ],
+ [{V, fun() -> R = parse_max_forwards(V) end} || {V, R} <- Tests].
+
+parse_max_forwards_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"123, 123">>,
+ <<"4.17">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_max_forwards(V)) end} || V <- Tests].
+-endif.
+
+%% Origin header.
+
+%% According to the RFC6454 we should generate
+%% a fresh globally unique identifier and return that value if:
+%% - URI does not use a hierarchical element as a naming authority
+%% or the URI is not an absolute URI
+%% - the implementation doesn't support the protocol given by uri-scheme
+%% Thus, erlang reference represents a GUID here.
+%%
+%% We only seek to have legal characters and separate the
+%% host and port values. The number of segments in the host
+%% or the size of each segment is not checked.
+%%
+%% There is no way to distinguish IPv4 addresses from regular
+%% names until the last segment is reached therefore we do not
+%% differentiate them.
+%%
+%% @todo The following valid hosts are currently rejected: IPv6
+%% addresses with a zone identifier; IPvFuture addresses;
+%% and percent-encoded addresses.
+
+-spec parse_origin(binary()) -> [{binary(), binary(), 0..65535} | reference()].
+parse_origin(Origins) ->
+ nonempty(origin_scheme(Origins, [])).
+
+origin_scheme(<<>>, Acc) -> Acc;
+origin_scheme(<< "http://", R/bits >>, Acc) -> origin_host(R, Acc, <<"http">>);
+origin_scheme(<< "https://", R/bits >>, Acc) -> origin_host(R, Acc, <<"https">>);
+origin_scheme(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> origin_scheme(next_origin(R), [make_ref()|Acc]).
+
+origin_host(<< $[, R/bits >>, Acc, Scheme) -> origin_ipv6_address(R, Acc, Scheme, << $[ >>);
+origin_host(Host, Acc, Scheme) -> origin_reg_name(Host, Acc, Scheme, <<>>).
+
+origin_ipv6_address(<< $] >>, Acc, Scheme, IP) ->
+ lists:reverse([{Scheme, << IP/binary, $] >>, default_port(Scheme)}|Acc]);
+origin_ipv6_address(<< $], $\s, R/bits >>, Acc, Scheme, IP) ->
+ origin_scheme(R, [{Scheme, << IP/binary, $] >>, default_port(Scheme)}|Acc]);
+origin_ipv6_address(<< $], $:, Port/bits >>, Acc, Scheme, IP) ->
+ origin_port(Port, Acc, Scheme, << IP/binary, $] >>, <<>>);
+origin_ipv6_address(<< C, R/bits >>, Acc, Scheme, IP) when ?IS_HEX(C) or (C =:= $:) or (C =:= $.) ->
+ ?LOWER(origin_ipv6_address, R, Acc, Scheme, IP).
+
+origin_reg_name(<<>>, Acc, Scheme, Name) ->
+ lists:reverse([{Scheme, Name, default_port(Scheme)}|Acc]);
+origin_reg_name(<< $\s, R/bits >>, Acc, Scheme, Name) ->
+ origin_scheme(R, [{Scheme, Name, default_port(Scheme)}|Acc]);
+origin_reg_name(<< $:, Port/bits >>, Acc, Scheme, Name) ->
+ origin_port(Port, Acc, Scheme, Name, <<>>);
+origin_reg_name(<< C, R/bits >>, Acc, Scheme, Name) when ?IS_URI_UNRESERVED(C) or ?IS_URI_SUB_DELIMS(C) ->
+ ?LOWER(origin_reg_name, R, Acc, Scheme, Name).
+
+origin_port(<<>>, Acc, Scheme, Host, Port) ->
+ lists:reverse([{Scheme, Host, binary_to_integer(Port)}|Acc]);
+origin_port(<< $\s, R/bits >>, Acc, Scheme, Host, Port) ->
+ origin_scheme(R, [{Scheme, Host, binary_to_integer(Port)}|Acc]);
+origin_port(<< C, R/bits >>, Acc, Scheme, Host, Port) when ?IS_DIGIT(C) ->
+ origin_port(R, Acc, Scheme, Host, << Port/binary, C >>).
+
+next_origin(<<>>) -> <<>>;
+next_origin(<< $\s, C, R/bits >>) when ?IS_TOKEN(C) -> << C, R/bits >>;
+next_origin(<< C, R/bits >>) when ?IS_TOKEN(C) or (C =:= $:) or (C =:= $/) -> next_origin(R).
+
+default_port(<< "http" >>) -> 80;
+default_port(<< "https" >>) -> 443.
+
+-ifdef(TEST).
+scheme() -> oneof([<<"http">>, <<"https">>]).
+
+scheme_host_port() ->
+ ?LET({Scheme, Host, Port},
+ {scheme(), host(), integer(1, 65535)},
+ begin
+ HostBin = list_to_binary(Host),
+ {[{Scheme, ?LOWER(HostBin), Port}],
+ case default_port(Scheme) of
+ Port -> << Scheme/binary, "://", HostBin/binary>>;
+ _ -> << Scheme/binary, "://", HostBin/binary, $:, (integer_to_binary(Port))/binary >>
+ end}
+ end).
+
+prop_parse_origin() ->
+ ?FORALL({Res, Origin}, scheme_host_port(), Res =:= parse_origin(Origin)).
+
+parse_origin_test_() ->
+ Tests = [
+ {<<"http://www.example.org:8080">>, [{<<"http">>, <<"www.example.org">>, 8080}]},
+ {<<"http://www.example.org">>, [{<<"http">>, <<"www.example.org">>, 80}]},
+ {<<"http://192.0.2.1:8080">>, [{<<"http">>, <<"192.0.2.1">>, 8080}]},
+ {<<"http://192.0.2.1">>, [{<<"http">>, <<"192.0.2.1">>, 80}]},
+ {<<"http://[2001:db8::1]:8080">>, [{<<"http">>, <<"[2001:db8::1]">>, 8080}]},
+ {<<"http://[2001:db8::1]">>, [{<<"http">>, <<"[2001:db8::1]">>, 80}]},
+ {<<"http://[::ffff:192.0.2.1]:8080">>, [{<<"http">>, <<"[::ffff:192.0.2.1]">>, 8080}]},
+ {<<"http://[::ffff:192.0.2.1]">>, [{<<"http">>, <<"[::ffff:192.0.2.1]">>, 80}]},
+ {<<"http://example.org https://blue.example.com:8080">>,
+ [{<<"http">>, <<"example.org">>, 80},
+ {<<"https">>, <<"blue.example.com">>, 8080}]}
+ ],
+ [{V, fun() -> R = parse_origin(V) end} || {V, R} <- Tests].
+
+parse_origin_reference_test_() ->
+ Tests = [
+ <<"null">>,
+ <<"httpx://example.org:80">>,
+ <<"httpx://example.org:80 null">>,
+ <<"null null">>
+ ],
+ [{V, fun() -> [true = is_reference(Ref) || Ref <- parse_origin(V)] end} || V <- Tests].
+
+parse_origin_error_test_() ->
+ Tests = [
+ <<>>,
+ <<"null", $\t, "null">>,
+ <<"null", $\s, $\s, "null">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_origin(V)) end} || V <- Tests].
+
+horse_parse_origin_blue_example_org() ->
+ horse:repeat(200000,
+ parse_origin(<<"http://blue.example.org:8080">>)
+ ).
+
+horse_parse_origin_ipv4() ->
+ horse:repeat(200000,
+ parse_origin(<<"http://192.0.2.1:8080">>)
+ ).
+
+horse_parse_origin_ipv6() ->
+ horse:repeat(200000,
+ parse_origin(<<"http://[2001:db8::1]:8080">>)
+ ).
+
+horse_parse_origin_ipv6_v4() ->
+ horse:repeat(200000,
+ parse_origin(<<"http://[::ffff:192.0.2.1]:8080">>)
+ ).
+
+horse_parse_origin_null() ->
+ horse:repeat(200000,
+ parse_origin(<<"null">>)
+ ).
+-endif.
+
+%% Pragma header.
+%%
+%% Legacy header kept for backward compatibility with HTTP/1.0 caches.
+%% Only the "no-cache" directive was ever specified, and only for
+%% request messages.
+%%
+%% We take a large shortcut in the parsing of this header, expecting
+%% an exact match of "no-cache".
+
+-spec parse_pragma(binary()) -> cache | no_cache.
+parse_pragma(<<"no-cache">>) -> no_cache;
+parse_pragma(_) -> cache.
+
+%% Proxy-Authenticate header.
+%%
+%% Alias of parse_www_authenticate/1 due to identical syntax.
+
+-spec parse_proxy_authenticate(binary()) -> [{basic, binary()}
+ | {bearer | digest | binary(), [{binary(), binary()}]}].
+parse_proxy_authenticate(ProxyAuthenticate) ->
+ parse_www_authenticate(ProxyAuthenticate).
+
+%% Proxy-Authorization header.
+%%
+%% Alias of parse_authorization/1 due to identical syntax.
+
+-spec parse_proxy_authorization(binary())
+ -> {basic, binary(), binary()}
+ | {bearer, binary()}
+ | {digest, [{binary(), binary()}]}.
+parse_proxy_authorization(ProxyAuthorization) ->
+ parse_authorization(ProxyAuthorization).
+
+%% Range header.
+
+-spec parse_range(binary())
+ -> {bytes, [{non_neg_integer(), non_neg_integer() | infinity} | neg_integer()]}
+ | {binary(), binary()}.
+parse_range(<<"bytes=", R/bits >>) ->
+ bytes_range_set(R, []);
+parse_range(<< C, R/bits >>) when ?IS_TOKEN(C) ->
+ ?LOWER(other_range_unit, R, <<>>).
+
+bytes_range_set(<<>>, Acc) -> {bytes, lists:reverse(Acc)};
+bytes_range_set(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> bytes_range_set(R, Acc);
+bytes_range_set(<< $-, C, R/bits >>, Acc) when ?IS_DIGIT(C) -> bytes_range_suffix_spec(R, Acc, C - $0);
+bytes_range_set(<< C, R/bits >>, Acc) when ?IS_DIGIT(C) -> bytes_range_spec(R, Acc, C - $0).
+
+bytes_range_spec(<< $-, C, R/bits >>, Acc, First) when ?IS_DIGIT(C) -> bytes_range_spec_last(R, Acc, First, C - $0);
+bytes_range_spec(<< $-, R/bits >>, Acc, First) -> bytes_range_set_sep(R, [{First, infinity}|Acc]);
+bytes_range_spec(<< C, R/bits >>, Acc, First) when ?IS_DIGIT(C) -> bytes_range_spec(R, Acc, First * 10 + C - $0).
+
+bytes_range_spec_last(<< C, R/bits >>, Acc, First, Last) when ?IS_DIGIT(C) -> bytes_range_spec_last(R, Acc, First, Last * 10 + C - $0);
+bytes_range_spec_last(R, Acc, First, Last) -> bytes_range_set_sep(R, [{First, Last}|Acc]).
+
+bytes_range_suffix_spec(<< C, R/bits >>, Acc, Suffix) when ?IS_DIGIT(C) -> bytes_range_suffix_spec(R, Acc, Suffix * 10 + C - $0);
+bytes_range_suffix_spec(R, Acc, Suffix) -> bytes_range_set_sep(R, [-Suffix|Acc]).
+
+bytes_range_set_sep(<<>>, Acc) -> {bytes, lists:reverse(Acc)};
+bytes_range_set_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> bytes_range_set_sep(R, Acc);
+bytes_range_set_sep(<< $,, R/bits >>, Acc) -> bytes_range_set(R, Acc).
+
+other_range_unit(<< $=, C, R/bits >>, U) when ?IS_VCHAR(C) ->
+ other_range_set(R, U, << C >>);
+other_range_unit(<< C, R/bits >>, U) when ?IS_TOKEN(C) ->
+ ?LOWER(other_range_unit, R, U).
+
+other_range_set(<<>>, U, S) ->
+ {U, S};
+other_range_set(<< C, R/bits >>, U, S) when ?IS_VCHAR(C) ->
+ other_range_set(R, U, << S/binary, C >>).
+
+-ifdef(TEST).
+bytes_range() ->
+ ?LET(BytesSet,
+ non_empty(list(oneof([
+ ?SUCHTHAT({First, Last}, {pos_integer(), pos_integer()}, First =< Last),
+ {pos_integer(), infinity},
+ ?LET(I, pos_integer(), -I)
+ ]))),
+ {{bytes, BytesSet}, begin
+ << _, Set/bits >> = iolist_to_binary([
+ case Spec of
+ {First, infinity} -> [$,, integer_to_binary(First), $-];
+ {First, Last} -> [$,, integer_to_binary(First), $-, integer_to_binary(Last)];
+ Suffix -> [$,, integer_to_binary(Suffix)]
+ end || Spec <- BytesSet]),
+ <<"bytes=", Set/binary >>
+ end}).
+
+other_range() ->
+ ?LET(Range = {Unit, Set},
+ {token(), ?LET(L, non_empty(list(vchar())), list_to_binary(L))},
+ {Range, << Unit/binary, $=, Set/binary >>}).
+
+range() ->
+ oneof([
+ bytes_range(),
+ other_range()
+ ]).
+
+prop_parse_range() ->
+ ?FORALL({Range, RangeBin},
+ range(),
+ begin
+ Range2 = case Range of
+ {bytes, _} -> Range;
+ {Unit, Set} -> {?LOWER(Unit), Set}
+ end,
+ Range2 =:= parse_range(RangeBin)
+ end).
+
+parse_range_test_() ->
+ Tests = [
+ {<<"bytes=0-499">>, {bytes, [{0, 499}]}},
+ {<<"bytes=500-999">>, {bytes, [{500, 999}]}},
+ {<<"bytes=-500">>, {bytes, [-500]}},
+ {<<"bytes=9500-">>, {bytes, [{9500, infinity}]}},
+ {<<"bytes=0-0,-1">>, {bytes, [{0, 0}, -1]}},
+ {<<"bytes=500-600,601-999">>, {bytes, [{500, 600}, {601, 999}]}},
+ {<<"bytes=500-700,601-999">>, {bytes, [{500, 700}, {601, 999}]}},
+ {<<"books=I-III,V-IX">>, {<<"books">>, <<"I-III,V-IX">>}}
+ ],
+ [{V, fun() -> R = parse_range(V) end} || {V, R} <- Tests].
+
+parse_range_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_range(V)) end} || V <- Tests].
+
+horse_parse_range_first_last() ->
+ horse:repeat(200000,
+ parse_range(<<"bytes=500-999">>)
+ ).
+
+horse_parse_range_infinity() ->
+ horse:repeat(200000,
+ parse_range(<<"bytes=9500-">>)
+ ).
+
+horse_parse_range_suffix() ->
+ horse:repeat(200000,
+ parse_range(<<"bytes=-500">>)
+ ).
+
+horse_parse_range_two() ->
+ horse:repeat(200000,
+ parse_range(<<"bytes=500-700,601-999">>)
+ ).
+
+horse_parse_range_other() ->
+ horse:repeat(200000,
+ parse_range(<<"books=I-III,V-IX">>)
+ ).
+-endif.
+
+%% Retry-After header.
+
+-spec parse_retry_after(binary()) -> non_neg_integer() | calendar:datetime().
+parse_retry_after(RetryAfter = << D, _/bits >>) when ?IS_DIGIT(D) ->
+ I = binary_to_integer(RetryAfter),
+ true = I >= 0,
+ I;
+parse_retry_after(RetryAfter) ->
+ cow_date:parse_date(RetryAfter).
+
+-ifdef(TEST).
+parse_retry_after_test_() ->
+ Tests = [
+ {<<"Fri, 31 Dec 1999 23:59:59 GMT">>, {{1999, 12, 31}, {23, 59, 59}}},
+ {<<"120">>, 120}
+ ],
+ [{V, fun() -> R = parse_retry_after(V) end} || {V, R} <- Tests].
+
+parse_retry_after_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_retry_after(V)) end} || V <- Tests].
+
+horse_parse_retry_after_date() ->
+ horse:repeat(200000,
+ parse_retry_after(<<"Fri, 31 Dec 1999 23:59:59 GMT">>)
+ ).
+
+horse_parse_retry_after_delay_seconds() ->
+ horse:repeat(200000,
+ parse_retry_after(<<"120">>)
+ ).
+-endif.
+
+%% Sec-WebSocket-Accept header.
+%%
+%% The argument is returned without any processing. This value is
+%% expected to be matched directly by the client so no parsing is
+%% needed.
+
+-spec parse_sec_websocket_accept(binary()) -> binary().
+parse_sec_websocket_accept(SecWebSocketAccept) ->
+ SecWebSocketAccept.
+
+%% Sec-WebSocket-Extensions header.
+
+-spec parse_sec_websocket_extensions(binary()) -> [{binary(), [binary() | {binary(), binary()}]}].
+parse_sec_websocket_extensions(SecWebSocketExtensions) ->
+ nonempty(ws_extension_list(SecWebSocketExtensions, [])).
+
+ws_extension_list(<<>>, Acc) -> lists:reverse(Acc);
+ws_extension_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> ws_extension_list(R, Acc);
+ws_extension_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> ws_extension(R, Acc, << C >>).
+
+ws_extension(<< C, R/bits >>, Acc, E) when ?IS_TOKEN(C) -> ws_extension(R, Acc, << E/binary, C >>);
+ws_extension(R, Acc, E) -> ws_extension_param_sep(R, Acc, E, []).
+
+ws_extension_param_sep(<<>>, Acc, E, P) -> lists:reverse([{E, lists:reverse(P)}|Acc]);
+ws_extension_param_sep(<< $,, R/bits >>, Acc, E, P) -> ws_extension_list(R, [{E, lists:reverse(P)}|Acc]);
+ws_extension_param_sep(<< $;, R/bits >>, Acc, E, P) -> ws_extension_before_param(R, Acc, E, P);
+ws_extension_param_sep(<< C, R/bits >>, Acc, E, P) when ?IS_WS(C) -> ws_extension_param_sep(R, Acc, E, P).
+
+ws_extension_before_param(<< C, R/bits >>, Acc, E, P) when ?IS_WS(C) -> ws_extension_before_param(R, Acc, E, P);
+ws_extension_before_param(<< C, R/bits >>, Acc, E, P) when ?IS_TOKEN(C) -> ws_extension_param(R, Acc, E, P, << C >>).
+
+ws_extension_param(<< $=, $", R/bits >>, Acc, E, P, K) -> ws_extension_quoted(R, Acc, E, P, K, <<>>);
+ws_extension_param(<< $=, C, R/bits >>, Acc, E, P, K) when ?IS_TOKEN(C) -> ws_extension_value(R, Acc, E, P, K, << C >>);
+ws_extension_param(<< C, R/bits >>, Acc, E, P, K) when ?IS_TOKEN(C) -> ws_extension_param(R, Acc, E, P, << K/binary, C >>);
+ws_extension_param(R, Acc, E, P, K) -> ws_extension_param_sep(R, Acc, E, [K|P]).
+
+ws_extension_quoted(<< $", R/bits >>, Acc, E, P, K, V) -> ws_extension_param_sep(R, Acc, E, [{K, V}|P]);
+ws_extension_quoted(<< $\\, C, R/bits >>, Acc, E, P, K, V) when ?IS_TOKEN(C) -> ws_extension_quoted(R, Acc, E, P, K, << V/binary, C >>);
+ws_extension_quoted(<< C, R/bits >>, Acc, E, P, K, V) when ?IS_TOKEN(C) -> ws_extension_quoted(R, Acc, E, P, K, << V/binary, C >>).
+
+ws_extension_value(<< C, R/bits >>, Acc, E, P, K, V) when ?IS_TOKEN(C) -> ws_extension_value(R, Acc, E, P, K, << V/binary, C >>);
+ws_extension_value(R, Acc, E, P, K, V) -> ws_extension_param_sep(R, Acc, E, [{K, V}|P]).
+
+-ifdef(TEST).
+quoted_token() ->
+ ?LET(T,
+ non_empty(list(frequency([
+ {99, tchar()},
+ {1, [$\\, tchar()]}
+ ]))),
+ [$", T, $"]).
+
+ws_extension() ->
+ ?LET({E, PL},
+ {token(), small_list({ows(), ows(), oneof([token(), {token(), oneof([token(), quoted_token()])}])})},
+ {E, PL, iolist_to_binary([E,
+ [case P of
+ {OWS1, OWS2, {K, V}} -> [OWS1, $;, OWS2, K, $=, V];
+ {OWS1, OWS2, K} -> [OWS1, $;, OWS2, K]
+ end || P <- PL]
+ ])}).
+
+prop_parse_sec_websocket_extensions() ->
+ ?FORALL(L,
+ vector(1, 50, ws_extension()),
+ begin
+ << _, SecWebsocketExtensions/binary >> = iolist_to_binary([[$,, E] || {_, _, E} <- L]),
+ ResL = parse_sec_websocket_extensions(SecWebsocketExtensions),
+ CheckedL = [begin
+ ExpectedPL = [case P of
+ {_, _, {K, V}} -> {K, unquote(V)};
+ {_, _, K} -> K
+ end || P <- PL],
+ E =:= ResE andalso ExpectedPL =:= ResPL
+ end || {{E, PL, _}, {ResE, ResPL}} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_sec_websocket_extensions_test_() ->
+ Tests = [
+ {<<"foo">>, [{<<"foo">>, []}]},
+ {<<"bar; baz=2">>, [{<<"bar">>, [{<<"baz">>, <<"2">>}]}]},
+ {<<"foo, bar; baz=2">>, [{<<"foo">>, []}, {<<"bar">>, [{<<"baz">>, <<"2">>}]}]},
+ {<<"deflate-stream">>, [{<<"deflate-stream">>, []}]},
+ {<<"mux; max-channels=4; flow-control, deflate-stream">>,
+ [{<<"mux">>, [{<<"max-channels">>, <<"4">>}, <<"flow-control">>]}, {<<"deflate-stream">>, []}]},
+ {<<"private-extension">>, [{<<"private-extension">>, []}]}
+ ],
+ [{V, fun() -> R = parse_sec_websocket_extensions(V) end} || {V, R} <- Tests].
+
+parse_sec_websocket_extensions_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_extensions(V)) end}
+ || V <- Tests].
+
+horse_parse_sec_websocket_extensions() ->
+ horse:repeat(200000,
+ parse_sec_websocket_extensions(<<"mux; max-channels=4; flow-control, deflate-stream">>)
+ ).
+-endif.
+
+%% Sec-WebSocket-Key header.
+%%
+%% The argument is returned without any processing. This value is
+%% expected to be prepended to a static value, the result of which
+%% hashed to form a new base64 value returned in Sec-WebSocket-Accept,
+%% therefore no parsing is needed.
+
+-spec parse_sec_websocket_key(binary()) -> binary().
+parse_sec_websocket_key(SecWebSocketKey) ->
+ SecWebSocketKey.
+
+%% Sec-WebSocket-Protocol request header.
+
+-spec parse_sec_websocket_protocol_req(binary()) -> [binary()].
+parse_sec_websocket_protocol_req(SecWebSocketProtocol) ->
+ nonempty(token_list(SecWebSocketProtocol, [])).
+
+-ifdef(TEST).
+parse_sec_websocket_protocol_req_test_() ->
+ Tests = [
+ {<<"chat, superchat">>, [<<"chat">>, <<"superchat">>]},
+ {<<"Chat, SuperChat">>, [<<"Chat">>, <<"SuperChat">>]}
+ ],
+ [{V, fun() -> R = parse_sec_websocket_protocol_req(V) end} || {V, R} <- Tests].
+
+parse_sec_websocket_protocol_req_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_protocol_req(V)) end}
+ || V <- Tests].
+
+horse_parse_sec_websocket_protocol_req() ->
+ horse:repeat(200000,
+ parse_sec_websocket_protocol_req(<<"chat, superchat">>)
+ ).
+-endif.
+
+%% Sec-Websocket-Protocol response header.
+
+-spec parse_sec_websocket_protocol_resp(binary()) -> binary().
+parse_sec_websocket_protocol_resp(Protocol) ->
+ true = <<>> =/= Protocol,
+ ok = validate_token(Protocol),
+ Protocol.
+
+-ifdef(TEST).
+prop_parse_sec_websocket_protocol_resp() ->
+ ?FORALL(T,
+ token(),
+ T =:= parse_sec_websocket_protocol_resp(T)).
+
+parse_sec_websocket_protocol_resp_test_() ->
+ Tests = [
+ {<<"chat">>, <<"chat">>},
+ {<<"CHAT">>, <<"CHAT">>}
+ ],
+ [{V, fun() -> R = parse_sec_websocket_protocol_resp(V) end} || {V, R} <- Tests].
+
+parse_sec_websocket_protocol_resp_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_protocol_resp(V)) end}
+ || V <- Tests].
+
+horse_parse_sec_websocket_protocol_resp() ->
+ horse:repeat(200000,
+ parse_sec_websocket_protocol_resp(<<"chat">>)
+ ).
+-endif.
+
+%% Sec-WebSocket-Version request header.
+
+-spec parse_sec_websocket_version_req(binary()) -> websocket_version().
+parse_sec_websocket_version_req(SecWebSocketVersion) when byte_size(SecWebSocketVersion) < 4 ->
+ Version = binary_to_integer(SecWebSocketVersion),
+ true = Version >= 0 andalso Version =< 255,
+ Version.
+
+-ifdef(TEST).
+prop_parse_sec_websocket_version_req() ->
+ ?FORALL(Version,
+ integer(0, 255),
+ Version =:= parse_sec_websocket_version_req(integer_to_binary(Version))).
+
+parse_sec_websocket_version_req_test_() ->
+ Tests = [
+ {<<"13">>, 13},
+ {<<"25">>, 25}
+ ],
+ [{V, fun() -> R = parse_sec_websocket_version_req(V) end} || {V, R} <- Tests].
+
+parse_sec_websocket_version_req_error_test_() ->
+ Tests = [
+ <<>>,
+ <<" ">>,
+ <<"7, 8, 13">>,
+ <<"invalid">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_version_req(V)) end}
+ || V <- Tests].
+
+horse_parse_sec_websocket_version_req_13() ->
+ horse:repeat(200000,
+ parse_sec_websocket_version_req(<<"13">>)
+ ).
+
+horse_parse_sec_websocket_version_req_255() ->
+ horse:repeat(200000,
+ parse_sec_websocket_version_req(<<"255">>)
+ ).
+-endif.
+
+%% Sec-WebSocket-Version response header.
+
+-spec parse_sec_websocket_version_resp(binary()) -> [websocket_version()].
+parse_sec_websocket_version_resp(SecWebSocketVersion) ->
+ nonempty(ws_version_list(SecWebSocketVersion, [])).
+
+ws_version_list(<<>>, Acc) -> lists:reverse(Acc);
+ws_version_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> ws_version_list(R, Acc);
+ws_version_list(<< C, R/bits >>, Acc) when ?IS_DIGIT(C) -> ws_version(R, Acc, C - $0).
+
+ws_version(<< C, R/bits >>, Acc, V) when ?IS_DIGIT(C) -> ws_version(R, Acc, V * 10 + C - $0);
+ws_version(R, Acc, V) -> ws_version_list_sep(R, [V|Acc]).
+
+ws_version_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+ws_version_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> ws_version_list_sep(R, Acc);
+ws_version_list_sep(<< $,, R/bits >>, Acc) -> ws_version_list(R, Acc).
+
+-ifdef(TEST).
+sec_websocket_version_resp() ->
+ ?LET(L,
+ non_empty(list({ows(), ows(), integer(0, 255)})),
+ begin
+ << _, SecWebSocketVersion/binary >> = iolist_to_binary(
+ [[OWS1, $,, OWS2, integer_to_binary(V)] || {OWS1, OWS2, V} <- L]),
+ {[V || {_, _, V} <- L], SecWebSocketVersion}
+ end).
+
+prop_parse_sec_websocket_version_resp() ->
+ ?FORALL({L, SecWebSocketVersion},
+ sec_websocket_version_resp(),
+ L =:= parse_sec_websocket_version_resp(SecWebSocketVersion)).
+
+parse_sec_websocket_version_resp_test_() ->
+ Tests = [
+ {<<"13, 8, 7">>, [13, 8, 7]}
+ ],
+ [{V, fun() -> R = parse_sec_websocket_version_resp(V) end} || {V, R} <- Tests].
+
+parse_sec_websocket_version_resp_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_sec_websocket_version_resp(V)) end}
+ || V <- Tests].
+
+horse_parse_sec_websocket_version_resp() ->
+ horse:repeat(200000,
+ parse_sec_websocket_version_resp(<<"13, 8, 7">>)
+ ).
+-endif.
+
+%% Set-Cookie header.
+
+-spec parse_set_cookie(binary())
+ -> {ok, binary(), binary(), cow_cookie:cookie_attrs()}
+ | ignore.
+parse_set_cookie(SetCookie) ->
+ cow_cookie:parse_set_cookie(SetCookie).
+
+%% TE header.
+%%
+%% This function does not support parsing of transfer-parameter.
+
+-spec parse_te(binary()) -> {trailers | no_trailers, [{binary(), qvalue()}]}.
+parse_te(TE) ->
+ te_list(TE, no_trailers, []).
+
+te_list(<<>>, Trail, Acc) -> {Trail, lists:reverse(Acc)};
+te_list(<< C, R/bits >>, Trail, Acc) when ?IS_WS_COMMA(C) -> te_list(R, Trail, Acc);
+te_list(<< "trailers", R/bits >>, Trail, Acc) -> te(R, Trail, Acc, <<"trailers">>);
+te_list(<< "compress", R/bits >>, Trail, Acc) -> te(R, Trail, Acc, <<"compress">>);
+te_list(<< "deflate", R/bits >>, Trail, Acc) -> te(R, Trail, Acc, <<"deflate">>);
+te_list(<< "gzip", R/bits >>, Trail, Acc) -> te(R, Trail, Acc, <<"gzip">>);
+te_list(<< C, R/bits >>, Trail, Acc) when ?IS_TOKEN(C) ->
+ ?LOWER(te, R, Trail, Acc, <<>>).
+
+te(<<>>, _, Acc, <<"trailers">>) -> {trailers, lists:reverse(Acc)};
+te(<< $,, R/bits >>, _, Acc, <<"trailers">>) -> te_list(R, trailers, Acc);
+te(<< $;, R/bits >>, Trail, Acc, T) when T =/= <<"trailers">> -> te_before_weight(R, Trail, Acc, T);
+te(<< C, R/bits >>, _, Acc, <<"trailers">>) when ?IS_WS(C) -> te_list_sep(R, trailers, Acc);
+te(<< C, R/bits >>, Trail, Acc, T) when ?IS_TOKEN(C) ->
+ ?LOWER(te, R, Trail, Acc, T);
+te(R, Trail, Acc, T) -> te_param_sep(R, Trail, Acc, T).
+
+te_param_sep(<<>>, Trail, Acc, T) -> {Trail, lists:reverse([{T, 1000}|Acc])};
+te_param_sep(<< $,, R/bits >>, Trail, Acc, T) -> te_list(R, Trail, [{T, 1000}|Acc]);
+te_param_sep(<< C, R/bits >>, Trail, Acc, T) when ?IS_WS(C) -> te_param_sep(R, Trail, Acc, T).
+
+te_before_weight(<< C, R/bits >>, Trail, Acc, T) when ?IS_WS(C) -> te_before_weight(R, Trail, Acc, T);
+te_before_weight(<< $q, $=, R/bits >>, Trail, Acc, T) -> te_weight(R, Trail, Acc, T).
+
+te_weight(<< "1.000", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 1000}|Acc]);
+te_weight(<< "1.00", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 1000}|Acc]);
+te_weight(<< "1.0", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 1000}|Acc]);
+te_weight(<< "1.", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 1000}|Acc]);
+te_weight(<< "1", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 1000}|Acc]);
+te_weight(<< "0.", A, B, C, R/bits >>, Trail, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B), ?IS_DIGIT(C) ->
+ te_list_sep(R, Trail, [{T, (A - $0) * 100 + (B - $0) * 10 + (C - $0)}|Acc]);
+te_weight(<< "0.", A, B, R/bits >>, Trail, Acc, T) when ?IS_DIGIT(A), ?IS_DIGIT(B) ->
+ te_list_sep(R, Trail, [{T, (A - $0) * 100 + (B - $0) * 10}|Acc]);
+te_weight(<< "0.", A, R/bits >>, Trail, Acc, T) when ?IS_DIGIT(A) ->
+ te_list_sep(R, Trail, [{T, (A - $0) * 100}|Acc]);
+te_weight(<< "0.", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 0}|Acc]);
+te_weight(<< "0", R/bits >>, Trail, Acc, T) -> te_list_sep(R, Trail, [{T, 0}|Acc]).
+
+te_list_sep(<<>>, Trail, Acc) -> {Trail, lists:reverse(Acc)};
+te_list_sep(<< C, R/bits >>, Trail, Acc) when ?IS_WS(C) -> te_list_sep(R, Trail, Acc);
+te_list_sep(<< $,, R/bits >>, Trail, Acc) -> te_list(R, Trail, Acc).
+
+-ifdef(TEST).
+te() ->
+ ?LET({Trail, L},
+ {elements([trailers, no_trailers]),
+ small_non_empty_list({?SUCHTHAT(T, token(), T =/= <<"trailers">>), weight()})},
+ {Trail, L, begin
+ L2 = case Trail of
+ no_trailers -> L;
+ trailers ->
+ Rand = rand:uniform(length(L) + 1) - 1,
+ {Before, After} = lists:split(Rand, L),
+ Before ++ [{<<"trailers">>, undefined}|After]
+ end,
+ << _, TE/binary >> = iolist_to_binary([case W of
+ undefined -> [$,, T];
+ _ -> [$,, T, <<";q=">>, qvalue_to_iodata(W)]
+ end || {T, W} <- L2]),
+ TE
+ end}
+ ).
+
+prop_parse_te() ->
+ ?FORALL({Trail, L, TE},
+ te(),
+ begin
+ {ResTrail, ResL} = parse_te(TE),
+ CheckedL = [begin
+ ResT =:= ?LOWER(T)
+ andalso (ResW =:= W orelse (W =:= undefined andalso ResW =:= 1000))
+ end || {{T, W}, {ResT, ResW}} <- lists:zip(L, ResL)],
+ ResTrail =:= Trail andalso [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_te_test_() ->
+ Tests = [
+ {<<"deflate">>, {no_trailers, [{<<"deflate">>, 1000}]}},
+ {<<>>, {no_trailers, []}},
+ {<<"trailers, deflate;q=0.5">>, {trailers, [{<<"deflate">>, 500}]}}
+ ],
+ [{V, fun() -> R = parse_te(V) end} || {V, R} <- Tests].
+
+horse_parse_te() ->
+ horse:repeat(200000,
+ parse_te(<<"trailers, deflate;q=0.5">>)
+ ).
+-endif.
+
+%% Trailer header.
+
+-spec parse_trailer(binary()) -> [binary()].
+parse_trailer(Trailer) ->
+ nonempty(token_ci_list(Trailer, [])).
+
+-ifdef(TEST).
+parse_trailer_test_() ->
+ Tests = [
+ {<<"Date, Content-MD5">>, [<<"date">>, <<"content-md5">>]}
+ ],
+ [{V, fun() -> R = parse_trailer(V) end} || {V, R} <- Tests].
+
+parse_trailer_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_trailer(V)) end} || V <- Tests].
+
+horse_parse_trailer() ->
+ horse:repeat(200000,
+ parse_trailer(<<"Date, Content-MD5">>)
+ ).
+-endif.
+
+%% Transfer-Encoding header.
+%%
+%% This function does not support parsing of transfer-parameter.
+
+-spec parse_transfer_encoding(binary()) -> [binary()].
+parse_transfer_encoding(<<"chunked">>) ->
+ [<<"chunked">>];
+parse_transfer_encoding(TransferEncoding) ->
+ nonempty(token_ci_list(TransferEncoding, [])).
+
+-ifdef(TEST).
+prop_parse_transfer_encoding() ->
+ ?FORALL(L,
+ non_empty(list(token())),
+ begin
+ << _, TransferEncoding/binary >> = iolist_to_binary([[$,, C] || C <- L]),
+ ResL = parse_transfer_encoding(TransferEncoding),
+ CheckedL = [?LOWER(Co) =:= ResC || {Co, ResC} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_transfer_encoding_test_() ->
+ Tests = [
+ {<<"a , , , ">>, [<<"a">>]},
+ {<<" , , , a">>, [<<"a">>]},
+ {<<"a , , b">>, [<<"a">>, <<"b">>]},
+ {<<"chunked">>, [<<"chunked">>]},
+ {<<"chunked, something">>, [<<"chunked">>, <<"something">>]},
+ {<<"gzip, chunked">>, [<<"gzip">>, <<"chunked">>]}
+ ],
+ [{V, fun() -> R = parse_transfer_encoding(V) end} || {V, R} <- Tests].
+
+parse_transfer_encoding_error_test_() ->
+ Tests = [
+ <<>>,
+ <<" ">>,
+ <<" , ">>,
+ <<",,,">>,
+ <<"a b">>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_transfer_encoding(V)) end}
+ || V <- Tests].
+
+horse_parse_transfer_encoding_chunked() ->
+ horse:repeat(200000,
+ parse_transfer_encoding(<<"chunked">>)
+ ).
+
+horse_parse_transfer_encoding_custom() ->
+ horse:repeat(200000,
+ parse_transfer_encoding(<<"chunked, something">>)
+ ).
+-endif.
+
+%% Upgrade header.
+%%
+%% It is unclear from the RFC whether the values here are
+%% case sensitive.
+%%
+%% We handle them in a case insensitive manner because they
+%% are described as case insensitive in the Websocket RFC.
+
+-spec parse_upgrade(binary()) -> [binary()].
+parse_upgrade(Upgrade) ->
+ nonempty(protocol_list(Upgrade, [])).
+
+protocol_list(<<>>, Acc) -> lists:reverse(Acc);
+protocol_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> protocol_list(R, Acc);
+protocol_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
+ ?LOWER(protocol_name, R, Acc, <<>>).
+
+protocol_name(<< $/, C, R/bits >>, Acc, P) ->
+ ?LOWER(protocol_version, R, Acc, << P/binary, $/ >>);
+protocol_name(<< C, R/bits >>, Acc, P) when ?IS_TOKEN(C) ->
+ ?LOWER(protocol_name, R, Acc, P);
+protocol_name(R, Acc, P) -> protocol_list_sep(R, [P|Acc]).
+
+protocol_version(<< C, R/bits >>, Acc, P) when ?IS_TOKEN(C) ->
+ ?LOWER(protocol_version, R, Acc, P);
+protocol_version(R, Acc, P) -> protocol_list_sep(R, [P|Acc]).
+
+protocol_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+protocol_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> protocol_list_sep(R, Acc);
+protocol_list_sep(<< $,, R/bits >>, Acc) -> protocol_list(R, Acc).
+
+-ifdef(TEST).
+protocols() ->
+ ?LET(P,
+ oneof([token(), [token(), $/, token()]]),
+ iolist_to_binary(P)).
+
+prop_parse_upgrade() ->
+ ?FORALL(L,
+ non_empty(list(protocols())),
+ begin
+ << _, Upgrade/binary >> = iolist_to_binary([[$,, P] || P <- L]),
+ ResL = parse_upgrade(Upgrade),
+ CheckedL = [?LOWER(P) =:= ResP || {P, ResP} <- lists:zip(L, ResL)],
+ [true] =:= lists:usort(CheckedL)
+ end).
+
+parse_upgrade_test_() ->
+ Tests = [
+ {<<"HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11">>,
+ [<<"http/2.0">>, <<"shttp/1.3">>, <<"irc/6.9">>, <<"rta/x11">>]},
+ {<<"HTTP/2.0">>, [<<"http/2.0">>]}
+ ],
+ [{V, fun() -> R = parse_upgrade(V) end} || {V, R} <- Tests].
+
+parse_upgrade_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_upgrade(V)) end}
+ || V <- Tests].
+-endif.
+
+%% Variant-Key-06 (draft) header.
+%%
+%% The Variants header must be parsed first in order to know
+%% the NumMembers argument as it is the number of members in
+%% the Variants dictionary.
+
+-spec parse_variant_key(binary(), pos_integer()) -> [[binary()]].
+parse_variant_key(VariantKey, NumMembers) ->
+ List = cow_http_struct_hd:parse_list(VariantKey),
+ [case Inner of
+ {list, InnerList, []} ->
+ NumMembers = length(InnerList),
+ [case Item of
+ {item, {token, Value}, []} -> Value;
+ {item, {string, Value}, []} -> Value
+ end || Item <- InnerList]
+ end || Inner <- List].
+
+-ifdef(TEST).
+parse_variant_key_test_() ->
+ Tests = [
+ {<<"(en)">>, 1, [[<<"en">>]]},
+ {<<"(gzip fr)">>, 2, [[<<"gzip">>, <<"fr">>]]},
+ {<<"(gzip fr), (\"identity\" fr)">>, 2, [[<<"gzip">>, <<"fr">>], [<<"identity">>, <<"fr">>]]},
+ {<<"(\"gzip \" fr)">>, 2, [[<<"gzip ">>, <<"fr">>]]},
+ {<<"(en br)">>, 2, [[<<"en">>, <<"br">>]]},
+ {<<"(\"0\")">>, 1, [[<<"0">>]]},
+ {<<"(silver), (\"bronze\")">>, 1, [[<<"silver">>], [<<"bronze">>]]},
+ {<<"(some_person)">>, 1, [[<<"some_person">>]]},
+ {<<"(gold europe)">>, 2, [[<<"gold">>, <<"europe">>]]}
+ ],
+ [{V, fun() -> R = parse_variant_key(V, N) end} || {V, N, R} <- Tests].
+
+parse_variant_key_error_test_() ->
+ Tests = [
+ {<<"(gzip fr), (identity fr), (br fr oops)">>, 2}
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_variant_key(V, N)) end} || {V, N} <- Tests].
+-endif.
+
+-spec variant_key([[binary()]]) -> iolist().
+%% We assume that the lists are of correct length.
+variant_key(VariantKeys) ->
+ cow_http_struct_hd:list([
+ {list, [
+ {item, {string, Value}, []}
+ || Value <- InnerList], []}
+ || InnerList <- VariantKeys]).
+
+-ifdef(TEST).
+variant_key_identity_test_() ->
+ Tests = [
+ {1, [[<<"en">>]]},
+ {2, [[<<"gzip">>, <<"fr">>]]},
+ {2, [[<<"gzip">>, <<"fr">>], [<<"identity">>, <<"fr">>]]},
+ {2, [[<<"gzip ">>, <<"fr">>]]},
+ {2, [[<<"en">>, <<"br">>]]},
+ {1, [[<<"0">>]]},
+ {1, [[<<"silver">>], [<<"bronze">>]]},
+ {1, [[<<"some_person">>]]},
+ {2, [[<<"gold">>, <<"europe">>]]}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> V = parse_variant_key(iolist_to_binary(variant_key(V)), N) end} || {N, V} <- Tests].
+-endif.
+
+%% Variants-06 (draft) header.
+
+-spec parse_variants(binary()) -> [{binary(), [binary()]}].
+parse_variants(Variants) ->
+ Dict = cow_http_struct_hd:parse_dictionary(Variants),
+ [case DictItem of
+ {Key, {list, List, []}} ->
+ {Key, [case Item of
+ {item, {token, Value}, []} -> Value;
+ {item, {string, Value}, []} -> Value
+ end || Item <- List]}
+ end || DictItem <- Dict].
+
+-ifdef(TEST).
+parse_variants_test_() ->
+ Tests = [
+ {<<"accept-language=(de en jp)">>, [{<<"accept-language">>, [<<"de">>, <<"en">>, <<"jp">>]}]},
+ {<<"accept-encoding=(gzip)">>, [{<<"accept-encoding">>, [<<"gzip">>]}]},
+ {<<"accept-encoding=()">>, [{<<"accept-encoding">>, []}]},
+ {<<"accept-encoding=(gzip br), accept-language=(en fr)">>, [
+ {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]},
+ {<<"accept-language">>, [<<"en">>, <<"fr">>]}
+ ]},
+ {<<"accept-language=(en fr de), accept-encoding=(gzip br)">>, [
+ {<<"accept-language">>, [<<"en">>, <<"fr">>, <<"de">>]},
+ {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]}
+ ]}
+ ],
+ [{V, fun() -> R = parse_variants(V) end} || {V, R} <- Tests].
+-endif.
+
+-spec variants([{binary(), [binary()]}]) -> iolist().
+variants(Variants) ->
+ cow_http_struct_hd:dictionary([
+ {Key, {list, [
+ {item, {string, Value}, []}
+ || Value <- List], []}}
+ || {Key, List} <- Variants]).
+
+-ifdef(TEST).
+variants_identity_test_() ->
+ Tests = [
+ [{<<"accept-language">>, [<<"de">>, <<"en">>, <<"jp">>]}],
+ [{<<"accept-encoding">>, [<<"gzip">>]}],
+ [{<<"accept-encoding">>, []}],
+ [
+ {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]},
+ {<<"accept-language">>, [<<"en">>, <<"fr">>]}
+ ],
+ [
+ {<<"accept-language">>, [<<"en">>, <<"fr">>, <<"de">>]},
+ {<<"accept-encoding">>, [<<"gzip">>, <<"br">>]}
+ ]
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> V = parse_variants(iolist_to_binary(variants(V))) end} || V <- Tests].
+-endif.
+
+%% Vary header.
+
+-spec parse_vary(binary()) -> '*' | [binary()].
+parse_vary(<<"*">>) ->
+ '*';
+parse_vary(Vary) ->
+ nonempty(token_ci_list(Vary, [])).
+
+-ifdef(TEST).
+parse_vary_test_() ->
+ Tests = [
+ {<<"*">>, '*'},
+ {<<"Accept-Encoding">>, [<<"accept-encoding">>]},
+ {<<"accept-encoding, accept-language">>, [<<"accept-encoding">>, <<"accept-language">>]}
+ ],
+ [{V, fun() -> R = parse_vary(V) end} || {V, R} <- Tests].
+
+parse_vary_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_vary(V)) end} || V <- Tests].
+-endif.
+
+%% WWW-Authenticate header.
+%%
+%% Unknown schemes are represented as the lowercase binary
+%% instead of an atom. Unlike with parse_authorization/1,
+%% we do not crash on unknown schemes.
+%%
+%% When parsing auth-params, we do not accept BWS characters around the "=".
+
+-spec parse_www_authenticate(binary()) -> [{basic, binary()}
+ | {bearer | digest | binary(), [{binary(), binary()}]}].
+parse_www_authenticate(Authenticate) ->
+ nonempty(www_auth_list(Authenticate, [])).
+
+www_auth_list(<<>>, Acc) -> lists:reverse(Acc);
+www_auth_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> www_auth_list(R, Acc);
+www_auth_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) ->
+ ?LOWER(www_auth_scheme, R, Acc, <<>>).
+
+www_auth_scheme(<< C, R/bits >>, Acc, Scheme0) when ?IS_WS(C) ->
+ Scheme = case Scheme0 of
+ <<"basic">> -> basic;
+ <<"bearer">> -> bearer;
+ <<"digest">> -> digest;
+ _ -> Scheme0
+ end,
+ www_auth_params_list(R, Acc, Scheme, []);
+www_auth_scheme(<< C, R/bits >>, Acc, Scheme) when ?IS_TOKEN(C) ->
+ ?LOWER(www_auth_scheme, R, Acc, Scheme).
+
+www_auth_params_list(<<>>, Acc, Scheme, Params) ->
+ lists:reverse([www_auth_tuple(Scheme, nonempty(Params))|Acc]);
+www_auth_params_list(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS_COMMA(C) ->
+ www_auth_params_list(R, Acc, Scheme, Params);
+www_auth_params_list(<< "algorithm=", C, R/bits >>, Acc, Scheme, Params) when ?IS_TOKEN(C) ->
+ www_auth_token(R, Acc, Scheme, Params, <<"algorithm">>, << C >>);
+www_auth_params_list(<< "domain=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"domain">>, <<>>);
+www_auth_params_list(<< "error=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"error">>, <<>>);
+www_auth_params_list(<< "error_description=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"error_description">>, <<>>);
+www_auth_params_list(<< "error_uri=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"error_uri">>, <<>>);
+www_auth_params_list(<< "nonce=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"nonce">>, <<>>);
+www_auth_params_list(<< "opaque=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"opaque">>, <<>>);
+www_auth_params_list(<< "qop=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"qop">>, <<>>);
+www_auth_params_list(<< "realm=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"realm">>, <<>>);
+www_auth_params_list(<< "scope=\"", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_quoted(R, Acc, Scheme, Params, <<"scope">>, <<>>);
+www_auth_params_list(<< "stale=false", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{<<"stale">>, <<"false">>}|Params]);
+www_auth_params_list(<< "stale=true", R/bits >>, Acc, Scheme, Params) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{<<"stale">>, <<"true">>}|Params]);
+www_auth_params_list(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_TOKEN(C) ->
+ ?LOWER(www_auth_param, R, Acc, Scheme, Params, <<>>).
+
+www_auth_param(<< $=, $", R/bits >>, Acc, Scheme, Params, K) ->
+ www_auth_quoted(R, Acc, Scheme, Params, K, <<>>);
+www_auth_param(<< $=, C, R/bits >>, Acc, Scheme, Params, K) when ?IS_TOKEN(C) ->
+ www_auth_token(R, Acc, Scheme, Params, K, << C >>);
+www_auth_param(<< C, R/bits >>, Acc, Scheme, Params, K) when ?IS_TOKEN(C) ->
+ ?LOWER(www_auth_param, R, Acc, Scheme, Params, K);
+www_auth_param(R, Acc, Scheme, Params, NewScheme) ->
+ www_auth_scheme(R, [www_auth_tuple(Scheme, Params)|Acc], NewScheme).
+
+www_auth_token(<< C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_TOKEN(C) ->
+ www_auth_token(R, Acc, Scheme, Params, K, << V/binary, C >>);
+www_auth_token(R, Acc, Scheme, Params, K, V) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{K, V}|Params]).
+
+www_auth_quoted(<< $", R/bits >>, Acc, Scheme, Params, K, V) ->
+ www_auth_params_list_sep(R, Acc, Scheme, [{K, V}|Params]);
+www_auth_quoted(<< $\\, C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_VCHAR_OBS(C) ->
+ www_auth_quoted(R, Acc, Scheme, Params, K, << V/binary, C >>);
+www_auth_quoted(<< C, R/bits >>, Acc, Scheme, Params, K, V) when ?IS_VCHAR_OBS(C) ->
+ www_auth_quoted(R, Acc, Scheme, Params, K, << V/binary, C >>).
+
+www_auth_params_list_sep(<<>>, Acc, Scheme, Params) ->
+ lists:reverse([www_auth_tuple(Scheme, Params)|Acc]);
+www_auth_params_list_sep(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS(C) ->
+ www_auth_params_list_sep(R, Acc, Scheme, Params);
+www_auth_params_list_sep(<< $,, R/bits >>, Acc, Scheme, Params) ->
+ www_auth_params_list_after_sep(R, Acc, Scheme, Params).
+
+www_auth_params_list_after_sep(<<>>, Acc, Scheme, Params) ->
+ lists:reverse([www_auth_tuple(Scheme, Params)|Acc]);
+www_auth_params_list_after_sep(<< C, R/bits >>, Acc, Scheme, Params) when ?IS_WS_COMMA(C) ->
+ www_auth_params_list_after_sep(R, Acc, Scheme, Params);
+www_auth_params_list_after_sep(R, Acc, Scheme, Params) ->
+ www_auth_params_list(R, Acc, Scheme, Params).
+
+www_auth_tuple(basic, Params) ->
+ %% Unknown parameters MUST be ignored. (RFC7617 2)
+ {<<"realm">>, Realm} = lists:keyfind(<<"realm">>, 1, Params),
+ {basic, Realm};
+www_auth_tuple(Scheme, Params) ->
+ {Scheme, lists:reverse(Params)}.
+
+-ifdef(TEST).
+parse_www_authenticate_test_() ->
+ Tests = [
+ {<<"Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"">>,
+ [{<<"newauth">>, [
+ {<<"realm">>, <<"apps">>},
+ {<<"type">>, <<"1">>},
+ {<<"title">>, <<"Login to \"apps\"">>}]},
+ {basic, <<"simple">>}]},
+ %% Same test, different order.
+ {<<"Basic realm=\"simple\", Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\"">>,
+ [{basic, <<"simple">>},
+ {<<"newauth">>, [
+ {<<"realm">>, <<"apps">>},
+ {<<"type">>, <<"1">>},
+ {<<"title">>, <<"Login to \"apps\"">>}]}]},
+ {<<"Bearer realm=\"example\"">>,
+ [{bearer, [{<<"realm">>, <<"example">>}]}]},
+ {<<"Bearer realm=\"example\", error=\"invalid_token\", error_description=\"The access token expired\"">>,
+ [{bearer, [
+ {<<"realm">>, <<"example">>},
+ {<<"error">>, <<"invalid_token">>},
+ {<<"error_description">>, <<"The access token expired">>}
+ ]}]},
+ {<<"Basic realm=\"WallyWorld\"">>,
+ [{basic, <<"WallyWorld">>}]},
+ %% RFC7617 2.1.
+ {<<"Basic realm=\"foo\", charset=\"UTF-8\"">>,
+ [{basic, <<"foo">>}]},
+ %% A real-world example.
+ {<<"Basic realm=\"https://123456789012.dkr.ecr.eu-north-1.amazonaws.com/\",service=\"ecr.amazonaws.com\"">>,
+ [{basic, <<"https://123456789012.dkr.ecr.eu-north-1.amazonaws.com/">>}]},
+ {<<"Bearer realm=\"example\", Basic realm=\"foo\", charset=\"UTF-8\"">>,
+ [{bearer, [{<<"realm">>, <<"example">>}]},
+ {basic, <<"foo">>}]},
+ {<<"Basic realm=\"foo\", foo=\"bar\", charset=\"UTF-8\", Bearer realm=\"example\",foo=\"bar\"">>,
+ [{basic, <<"foo">>},
+ {bearer, [{<<"realm">>, <<"example">>}, {<<"foo">>,<<"bar">>}]}]},
+ {<<"Digest realm=\"testrealm@host.com\", qop=\"auth,auth-int\", "
+ "nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
+ "opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"">>,
+ [{digest, [
+ {<<"realm">>, <<"testrealm@host.com">>},
+ {<<"qop">>, <<"auth,auth-int">>},
+ {<<"nonce">>, <<"dcd98b7102dd2f0e8b11d0f600bfb0c093">>},
+ {<<"opaque">>, <<"5ccc069c403ebaf9f0171e9517f40e41">>}
+ ]}]}
+ ],
+ [{V, fun() -> R = parse_www_authenticate(V) end} || {V, R} <- Tests].
+
+parse_www_authenticate_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_www_authenticate(V)) end} || V <- Tests].
+
+horse_parse_www_authenticate() ->
+ horse:repeat(200000,
+ parse_www_authenticate(<<"Newauth realm=\"apps\", type=1, title=\"Login to \\\"apps\\\"\", Basic realm=\"simple\"">>)
+ ).
+-endif.
+
+%% X-Forwarded-For header.
+%%
+%% This header has no specification but *looks like* it is
+%% a list of tokens.
+%%
+%% This header is deprecated in favor of the Forwarded header.
+
+-spec parse_x_forwarded_for(binary()) -> [binary()].
+parse_x_forwarded_for(XForwardedFor) ->
+ nonempty(nodeid_list(XForwardedFor, [])).
+
+-define(IS_NODEID_TOKEN(C),
+ ?IS_ALPHA(C) or ?IS_DIGIT(C)
+ or (C =:= $:) or (C =:= $.) or (C =:= $_)
+ or (C =:= $-) or (C =:= $[) or (C =:= $])).
+
+nodeid_list(<<>>, Acc) -> lists:reverse(Acc);
+nodeid_list(<<C, R/bits>>, Acc) when ?IS_WS_COMMA(C) -> nodeid_list(R, Acc);
+nodeid_list(<<C, R/bits>>, Acc) when ?IS_NODEID_TOKEN(C) -> nodeid(R, Acc, <<C>>).
+
+nodeid(<<C, R/bits>>, Acc, T) when ?IS_NODEID_TOKEN(C) -> nodeid(R, Acc, <<T/binary, C>>);
+nodeid(R, Acc, T) -> nodeid_list_sep(R, [T|Acc]).
+
+nodeid_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+nodeid_list_sep(<<C, R/bits>>, Acc) when ?IS_WS(C) -> nodeid_list_sep(R, Acc);
+nodeid_list_sep(<<$,, R/bits>>, Acc) -> nodeid_list(R, Acc).
+
+-ifdef(TEST).
+parse_x_forwarded_for_test_() ->
+ Tests = [
+ {<<"client, proxy1, proxy2">>,
+ [<<"client">>, <<"proxy1">>, <<"proxy2">>]},
+ {<<"128.138.243.150, unknown, 192.52.106.30">>,
+ [<<"128.138.243.150">>, <<"unknown">>, <<"192.52.106.30">>]},
+ %% Examples from Mozilla DN.
+ {<<"2001:db8:85a3:8d3:1319:8a2e:370:7348">>,
+ [<<"2001:db8:85a3:8d3:1319:8a2e:370:7348">>]},
+ {<<"203.0.113.195">>,
+ [<<"203.0.113.195">>]},
+ {<<"203.0.113.195, 70.41.3.18, 150.172.238.178">>,
+ [<<"203.0.113.195">>, <<"70.41.3.18">>, <<"150.172.238.178">>]},
+ %% Examples from RFC7239 modified for x-forwarded-for.
+ {<<"[2001:db8:cafe::17]:4711">>,
+ [<<"[2001:db8:cafe::17]:4711">>]},
+ {<<"192.0.2.43, 198.51.100.17">>,
+ [<<"192.0.2.43">>, <<"198.51.100.17">>]},
+ {<<"_hidden">>,
+ [<<"_hidden">>]},
+ {<<"192.0.2.43,[2001:db8:cafe::17],unknown">>,
+ [<<"192.0.2.43">>, <<"[2001:db8:cafe::17]">>, <<"unknown">>]},
+ {<<"192.0.2.43, [2001:db8:cafe::17], unknown">>,
+ [<<"192.0.2.43">>, <<"[2001:db8:cafe::17]">>, <<"unknown">>]},
+ {<<"192.0.2.43, 2001:db8:cafe::17">>,
+ [<<"192.0.2.43">>, <<"2001:db8:cafe::17">>]},
+ {<<"192.0.2.43, [2001:db8:cafe::17]">>,
+ [<<"192.0.2.43">>, <<"[2001:db8:cafe::17]">>]}
+ ],
+ [{V, fun() -> R = parse_x_forwarded_for(V) end} || {V, R} <- Tests].
+
+parse_x_forwarded_for_error_test_() ->
+ Tests = [
+ <<>>
+ ],
+ [{V, fun() -> {'EXIT', _} = (catch parse_x_forwarded_for(V)) end} || V <- Tests].
+-endif.
+
+%% Internal.
+
+%% Only return if the list is not empty.
+nonempty(L) when L =/= [] -> L.
+
+%% Parse a list of case sensitive tokens.
+token_list(<<>>, Acc) -> lists:reverse(Acc);
+token_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> token_list(R, Acc);
+token_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> token(R, Acc, << C >>).
+
+token(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> token(R, Acc, << T/binary, C >>);
+token(R, Acc, T) -> token_list_sep(R, [T|Acc]).
+
+token_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+token_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> token_list_sep(R, Acc);
+token_list_sep(<< $,, R/bits >>, Acc) -> token_list(R, Acc).
+
+%% Parse a list of case insensitive tokens.
+token_ci_list(<<>>, Acc) -> lists:reverse(Acc);
+token_ci_list(<< C, R/bits >>, Acc) when ?IS_WS_COMMA(C) -> token_ci_list(R, Acc);
+token_ci_list(<< C, R/bits >>, Acc) when ?IS_TOKEN(C) -> ?LOWER(token_ci, R, Acc, <<>>).
+
+token_ci(<< C, R/bits >>, Acc, T) when ?IS_TOKEN(C) -> ?LOWER(token_ci, R, Acc, T);
+token_ci(R, Acc, T) -> token_ci_list_sep(R, [T|Acc]).
+
+token_ci_list_sep(<<>>, Acc) -> lists:reverse(Acc);
+token_ci_list_sep(<< C, R/bits >>, Acc) when ?IS_WS(C) -> token_ci_list_sep(R, Acc);
+token_ci_list_sep(<< $,, R/bits >>, Acc) -> token_ci_list(R, Acc).
+
+join_token_list([]) -> [];
+join_token_list([H|T]) -> join_token_list(T, [H]).
+
+join_token_list([], Acc) -> lists:reverse(Acc);
+join_token_list([H|T], Acc) -> join_token_list(T, [H,<<", ">>|Acc]).
diff --git a/server/_build/default/lib/cowlib/src/cow_http_struct_hd.erl b/server/_build/default/lib/cowlib/src/cow_http_struct_hd.erl
new file mode 100644
index 0000000..a79c691
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http_struct_hd.erl
@@ -0,0 +1,522 @@
+%% Copyright (c) 2019-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% The mapping between Erlang and structured headers types is as follow:
+%%
+%% List: list()
+%% Inner list: {list, [item()], params()}
+%% Dictionary: [{binary(), item()}]
+%% There is no distinction between empty list and empty dictionary.
+%% Item with parameters: {item, bare_item(), params()}
+%% Parameters: [{binary(), bare_item()}]
+%% Bare item: one bare_item() that can be of type:
+%% Integer: integer()
+%% Decimal: {decimal, {integer(), integer()}}
+%% String: {string, binary()}
+%% Token: {token, binary()}
+%% Byte sequence: {binary, binary()}
+%% Boolean: boolean()
+
+-module(cow_http_struct_hd).
+
+-export([parse_dictionary/1]).
+-export([parse_item/1]).
+-export([parse_list/1]).
+-export([dictionary/1]).
+-export([item/1]).
+-export([list/1]).
+
+-include("cow_parse.hrl").
+
+-type sh_list() :: [sh_item() | sh_inner_list()].
+-type sh_inner_list() :: {list, [sh_item()], sh_params()}.
+-type sh_params() :: [{binary(), sh_bare_item()}].
+-type sh_dictionary() :: [{binary(), sh_item() | sh_inner_list()}].
+-type sh_item() :: {item, sh_bare_item(), sh_params()}.
+-type sh_bare_item() :: integer() | sh_decimal() | boolean()
+ | {string | token | binary, binary()}.
+-type sh_decimal() :: {decimal, {integer(), integer()}}.
+
+-define(IS_LC_ALPHA(C),
+ (C =:= $a) or (C =:= $b) or (C =:= $c) or (C =:= $d) or (C =:= $e) or
+ (C =:= $f) or (C =:= $g) or (C =:= $h) or (C =:= $i) or (C =:= $j) or
+ (C =:= $k) or (C =:= $l) or (C =:= $m) or (C =:= $n) or (C =:= $o) or
+ (C =:= $p) or (C =:= $q) or (C =:= $r) or (C =:= $s) or (C =:= $t) or
+ (C =:= $u) or (C =:= $v) or (C =:= $w) or (C =:= $x) or (C =:= $y) or
+ (C =:= $z)
+).
+
+%% Parsing.
+
+-spec parse_dictionary(binary()) -> sh_dictionary().
+parse_dictionary(<<>>) ->
+ [];
+parse_dictionary(<<C,R/bits>>) when ?IS_LC_ALPHA(C) or (C =:= $*) ->
+ parse_dict_key(R, [], <<C>>).
+
+parse_dict_key(<<$=,$(,R0/bits>>, Acc, K) ->
+ {Item, R} = parse_inner_list(R0, []),
+ parse_dict_before_sep(R, lists:keystore(K, 1, Acc, {K, Item}));
+parse_dict_key(<<$=,R0/bits>>, Acc, K) ->
+ {Item, R} = parse_item1(R0),
+ parse_dict_before_sep(R, lists:keystore(K, 1, Acc, {K, Item}));
+parse_dict_key(<<C,R/bits>>, Acc, K)
+ when ?IS_LC_ALPHA(C) or ?IS_DIGIT(C)
+ or (C =:= $_) or (C =:= $-) or (C =:= $.) or (C =:= $*) ->
+ parse_dict_key(R, Acc, <<K/binary,C>>);
+parse_dict_key(<<$;,R0/bits>>, Acc, K) ->
+ {Params, R} = parse_before_param(R0, []),
+ parse_dict_before_sep(R, lists:keystore(K, 1, Acc, {K, {item, true, Params}}));
+parse_dict_key(R, Acc, K) ->
+ parse_dict_before_sep(R, lists:keystore(K, 1, Acc, {K, {item, true, []}})).
+
+parse_dict_before_sep(<<$\s,R/bits>>, Acc) ->
+ parse_dict_before_sep(R, Acc);
+parse_dict_before_sep(<<$\t,R/bits>>, Acc) ->
+ parse_dict_before_sep(R, Acc);
+parse_dict_before_sep(<<C,R/bits>>, Acc) when C =:= $, ->
+ parse_dict_before_member(R, Acc);
+parse_dict_before_sep(<<>>, Acc) ->
+ Acc.
+
+parse_dict_before_member(<<$\s,R/bits>>, Acc) ->
+ parse_dict_before_member(R, Acc);
+parse_dict_before_member(<<$\t,R/bits>>, Acc) ->
+ parse_dict_before_member(R, Acc);
+parse_dict_before_member(<<C,R/bits>>, Acc) when ?IS_LC_ALPHA(C) or (C =:= $*) ->
+ parse_dict_key(R, Acc, <<C>>).
+
+-spec parse_item(binary()) -> sh_item().
+parse_item(Bin) ->
+ {Item, <<>>} = parse_item1(Bin),
+ Item.
+
+parse_item1(Bin) ->
+ case parse_bare_item(Bin) of
+ {Item, <<$;,R/bits>>} ->
+ {Params, Rest} = parse_before_param(R, []),
+ {{item, Item, Params}, Rest};
+ {Item, Rest} ->
+ {{item, Item, []}, Rest}
+ end.
+
+-spec parse_list(binary()) -> sh_list().
+parse_list(<<>>) ->
+ [];
+parse_list(Bin) ->
+ parse_list_before_member(Bin, []).
+
+parse_list_member(<<$(,R0/bits>>, Acc) ->
+ {Item, R} = parse_inner_list(R0, []),
+ parse_list_before_sep(R, [Item|Acc]);
+parse_list_member(R0, Acc) ->
+ {Item, R} = parse_item1(R0),
+ parse_list_before_sep(R, [Item|Acc]).
+
+parse_list_before_sep(<<$\s,R/bits>>, Acc) ->
+ parse_list_before_sep(R, Acc);
+parse_list_before_sep(<<$\t,R/bits>>, Acc) ->
+ parse_list_before_sep(R, Acc);
+parse_list_before_sep(<<$,,R/bits>>, Acc) ->
+ parse_list_before_member(R, Acc);
+parse_list_before_sep(<<>>, Acc) ->
+ lists:reverse(Acc).
+
+parse_list_before_member(<<$\s,R/bits>>, Acc) ->
+ parse_list_before_member(R, Acc);
+parse_list_before_member(<<$\t,R/bits>>, Acc) ->
+ parse_list_before_member(R, Acc);
+parse_list_before_member(R, Acc) ->
+ parse_list_member(R, Acc).
+
+%% Internal.
+
+parse_inner_list(<<$\s,R/bits>>, Acc) ->
+ parse_inner_list(R, Acc);
+parse_inner_list(<<$),$;,R0/bits>>, Acc) ->
+ {Params, R} = parse_before_param(R0, []),
+ {{list, lists:reverse(Acc), Params}, R};
+parse_inner_list(<<$),R/bits>>, Acc) ->
+ {{list, lists:reverse(Acc), []}, R};
+parse_inner_list(R0, Acc) ->
+ {Item, R = <<C,_/bits>>} = parse_item1(R0),
+ true = (C =:= $\s) orelse (C =:= $)),
+ parse_inner_list(R, [Item|Acc]).
+
+parse_before_param(<<$\s,R/bits>>, Acc) ->
+ parse_before_param(R, Acc);
+parse_before_param(<<C,R/bits>>, Acc) when ?IS_LC_ALPHA(C) or (C =:= $*) ->
+ parse_param(R, Acc, <<C>>).
+
+parse_param(<<$;,R/bits>>, Acc, K) ->
+ parse_before_param(R, lists:keystore(K, 1, Acc, {K, true}));
+parse_param(<<$=,R0/bits>>, Acc, K) ->
+ case parse_bare_item(R0) of
+ {Item, <<$;,R/bits>>} ->
+ parse_before_param(R, lists:keystore(K, 1, Acc, {K, Item}));
+ {Item, R} ->
+ {lists:keystore(K, 1, Acc, {K, Item}), R}
+ end;
+parse_param(<<C,R/bits>>, Acc, K)
+ when ?IS_LC_ALPHA(C) or ?IS_DIGIT(C)
+ or (C =:= $_) or (C =:= $-) or (C =:= $.) or (C =:= $*) ->
+ parse_param(R, Acc, <<K/binary,C>>);
+parse_param(R, Acc, K) ->
+ {lists:keystore(K, 1, Acc, {K, true}), R}.
+
+%% Integer or decimal.
+parse_bare_item(<<$-,R/bits>>) -> parse_number(R, 0, <<$->>);
+parse_bare_item(<<C,R/bits>>) when ?IS_DIGIT(C) -> parse_number(R, 1, <<C>>);
+%% String.
+parse_bare_item(<<$",R/bits>>) -> parse_string(R, <<>>);
+%% Token.
+parse_bare_item(<<C,R/bits>>) when ?IS_ALPHA(C) or (C =:= $*) -> parse_token(R, <<C>>);
+%% Byte sequence.
+parse_bare_item(<<$:,R/bits>>) -> parse_binary(R, <<>>);
+%% Boolean.
+parse_bare_item(<<"?0",R/bits>>) -> {false, R};
+parse_bare_item(<<"?1",R/bits>>) -> {true, R}.
+
+parse_number(<<C,R/bits>>, L, Acc) when ?IS_DIGIT(C) ->
+ parse_number(R, L+1, <<Acc/binary,C>>);
+parse_number(<<$.,R/bits>>, L, Acc) ->
+ parse_decimal(R, L, 0, Acc, <<>>);
+parse_number(R, L, Acc) when L =< 15 ->
+ {binary_to_integer(Acc), R}.
+
+parse_decimal(<<C,R/bits>>, L1, L2, IntAcc, FracAcc) when ?IS_DIGIT(C) ->
+ parse_decimal(R, L1, L2+1, IntAcc, <<FracAcc/binary,C>>);
+parse_decimal(R, L1, L2, IntAcc, FracAcc0) when L1 =< 12, L2 >= 1, L2 =< 3 ->
+ %% While not strictly required this gives a more consistent representation.
+ FracAcc = case FracAcc0 of
+ <<$0>> -> <<>>;
+ <<$0,$0>> -> <<>>;
+ <<$0,$0,$0>> -> <<>>;
+ <<A,B,$0>> -> <<A,B>>;
+ <<A,$0,$0>> -> <<A>>;
+ <<A,$0>> -> <<A>>;
+ _ -> FracAcc0
+ end,
+ Mul = case byte_size(FracAcc) of
+ 3 -> 1000;
+ 2 -> 100;
+ 1 -> 10;
+ 0 -> 1
+ end,
+ Int = binary_to_integer(IntAcc),
+ Frac = case FracAcc of
+ <<>> -> 0;
+ %% Mind the sign.
+ _ when Int < 0 -> -binary_to_integer(FracAcc);
+ _ -> binary_to_integer(FracAcc)
+ end,
+ {{decimal, {Int * Mul + Frac, -byte_size(FracAcc)}}, R}.
+
+parse_string(<<$\\,$",R/bits>>, Acc) ->
+ parse_string(R, <<Acc/binary,$">>);
+parse_string(<<$\\,$\\,R/bits>>, Acc) ->
+ parse_string(R, <<Acc/binary,$\\>>);
+parse_string(<<$",R/bits>>, Acc) ->
+ {{string, Acc}, R};
+parse_string(<<C,R/bits>>, Acc) when
+ C >= 16#20, C =< 16#21;
+ C >= 16#23, C =< 16#5b;
+ C >= 16#5d, C =< 16#7e ->
+ parse_string(R, <<Acc/binary,C>>).
+
+parse_token(<<C,R/bits>>, Acc) when ?IS_TOKEN(C) or (C =:= $:) or (C =:= $/) ->
+ parse_token(R, <<Acc/binary,C>>);
+parse_token(R, Acc) ->
+ {{token, Acc}, R}.
+
+parse_binary(<<$:,R/bits>>, Acc) ->
+ {{binary, base64:decode(Acc)}, R};
+parse_binary(<<C,R/bits>>, Acc) when ?IS_ALPHANUM(C) or (C =:= $+) or (C =:= $/) or (C =:= $=) ->
+ parse_binary(R, <<Acc/binary,C>>).
+
+-ifdef(TEST).
+parse_struct_hd_test_() ->
+ Files = filelib:wildcard("deps/structured-header-tests/*.json"),
+ lists:flatten([begin
+ {ok, JSON} = file:read_file(File),
+ Tests = jsx:decode(JSON, [return_maps]),
+ [
+ {iolist_to_binary(io_lib:format("~s: ~s", [filename:basename(File), Name])), fun() ->
+ %% The implementation is strict. We fail whenever we can.
+ CanFail = maps:get(<<"can_fail">>, Test, false),
+ MustFail = maps:get(<<"must_fail">>, Test, false),
+ io:format("must fail ~p~nexpected json ~0p~n",
+ [MustFail, maps:get(<<"expected">>, Test, undefined)]),
+ Expected = case MustFail of
+ true -> undefined;
+ false -> expected_to_term(maps:get(<<"expected">>, Test))
+ end,
+ io:format("expected term: ~0p", [Expected]),
+ Raw = raw_to_binary(Raw0),
+ case HeaderType of
+ <<"dictionary">> when MustFail; CanFail ->
+ {'EXIT', _} = (catch parse_dictionary(Raw));
+ %% The test "binary.json: non-zero pad bits" does not fail
+ %% due to our reliance on Erlang/OTP's base64 module.
+ <<"item">> when CanFail ->
+ case (catch parse_item(Raw)) of
+ {'EXIT', _} -> ok;
+ Expected -> ok
+ end;
+ <<"item">> when MustFail ->
+ {'EXIT', _} = (catch parse_item(Raw));
+ <<"list">> when MustFail; CanFail ->
+ {'EXIT', _} = (catch parse_list(Raw));
+ <<"dictionary">> ->
+ Expected = (catch parse_dictionary(Raw));
+ <<"item">> ->
+ Expected = (catch parse_item(Raw));
+ <<"list">> ->
+ Expected = (catch parse_list(Raw))
+ end
+ end}
+ || Test=#{
+ <<"name">> := Name,
+ <<"header_type">> := HeaderType,
+ <<"raw">> := Raw0
+ } <- Tests]
+ end || File <- Files]).
+
+%% The tests JSON use arrays for almost everything. Identifying
+%% what is what requires looking deeper in the values:
+%%
+%% dict: [["k", v], ["k2", v2]] (values may have params)
+%% params: [["k", v], ["k2", v2]] (no params for values)
+%% list: [e1, e2, e3]
+%% inner-list: [[ [items...], params]]
+%% item: [bare, params]
+
+%% Item.
+expected_to_term([Bare, []])
+ when is_boolean(Bare); is_number(Bare); is_binary(Bare); is_map(Bare) ->
+ {item, e2tb(Bare), []};
+expected_to_term([Bare, Params = [[<<_/bits>>, _]|_]])
+ when is_boolean(Bare); is_number(Bare); is_binary(Bare); is_map(Bare) ->
+ {item, e2tb(Bare), e2tp(Params)};
+%% Empty list or dictionary.
+expected_to_term([]) ->
+ [];
+%% Dictionary.
+%%
+%% We exclude empty list from values because that could
+%% be confused with an outer list of strings. There is
+%% currently no conflicts in the tests thankfully.
+expected_to_term(Dict = [[<<_/bits>>, V]|_]) when V =/= [] ->
+ e2t(Dict);
+%% Outer list.
+expected_to_term(List) when is_list(List) ->
+ [e2t(E) || E <- List].
+
+%% Dictionary.
+e2t(Dict = [[<<_/bits>>, _]|_]) ->
+ [{K, e2t(V)} || [K, V] <- Dict];
+%% Inner list.
+e2t([List, Params]) when is_list(List) ->
+ {list, [e2t(E) || E <- List], e2tp(Params)};
+%% Item.
+e2t([Bare, Params]) ->
+ {item, e2tb(Bare), e2tp(Params)}.
+
+%% Bare item.
+e2tb(#{<<"__type">> := <<"token">>, <<"value">> := V}) ->
+ {token, V};
+e2tb(#{<<"__type">> := <<"binary">>, <<"value">> := V}) ->
+ {binary, base32:decode(V)};
+e2tb(V) when is_binary(V) ->
+ {string, V};
+e2tb(V) when is_float(V) ->
+ %% There should be no rounding needed for the test cases.
+ {decimal, decimal:to_decimal(V, #{precision => 3, rounding => round_down})};
+e2tb(V) ->
+ V.
+
+%% Params.
+e2tp([]) ->
+ [];
+e2tp(Params) ->
+ [{K, e2tb(V)} || [K, V] <- Params].
+
+%% The Cowlib parsers currently do not support resuming parsing
+%% in the case of multiple headers. To make tests work we modify
+%% the raw value the same way Cowboy does when encountering
+%% multiple headers: by adding a comma and space in between.
+%%
+%% Similarly, the Cowlib parsers expect the leading and trailing
+%% whitespace to be removed before calling the parser.
+raw_to_binary(RawList) ->
+ trim_ws(iolist_to_binary(lists:join(<<", ">>, RawList))).
+
+trim_ws(<<$\s,R/bits>>) -> trim_ws(R);
+trim_ws(R) -> trim_ws_end(R, byte_size(R) - 1).
+
+trim_ws_end(_, -1) ->
+ <<>>;
+trim_ws_end(Value, N) ->
+ case binary:at(Value, N) of
+ $\s -> trim_ws_end(Value, N - 1);
+ _ ->
+ S = N + 1,
+ << Value2:S/binary, _/bits >> = Value,
+ Value2
+ end.
+-endif.
+
+%% Building.
+
+-spec dictionary(#{binary() => sh_item() | sh_inner_list()} | sh_dictionary())
+ -> iolist().
+dictionary(Map) when is_map(Map) ->
+ dictionary(maps:to_list(Map));
+dictionary(KVList) when is_list(KVList) ->
+ lists:join(<<", ">>, [
+ case Value of
+ true -> Key;
+ _ -> [Key, $=, item_or_inner_list(Value)]
+ end
+ || {Key, Value} <- KVList]).
+
+-spec item(sh_item()) -> iolist().
+item({item, BareItem, Params}) ->
+ [bare_item(BareItem), params(Params)].
+
+-spec list(sh_list()) -> iolist().
+list(List) ->
+ lists:join(<<", ">>, [item_or_inner_list(Value) || Value <- List]).
+
+item_or_inner_list(Value = {list, _, _}) ->
+ inner_list(Value);
+item_or_inner_list(Value) ->
+ item(Value).
+
+inner_list({list, List, Params}) ->
+ [$(, lists:join($\s, [item(Value) || Value <- List]), $), params(Params)].
+
+bare_item({string, String}) ->
+ [$", escape_string(String, <<>>), $"];
+%% @todo Must fail if Token has invalid characters.
+bare_item({token, Token}) ->
+ Token;
+bare_item({binary, Binary}) ->
+ [$:, base64:encode(Binary), $:];
+bare_item({decimal, {Base, Exp}}) when Exp >= 0 ->
+ Mul = case Exp of
+ 0 -> 1;
+ 1 -> 10;
+ 2 -> 100;
+ 3 -> 1000;
+ 4 -> 10000;
+ 5 -> 100000;
+ 6 -> 1000000;
+ 7 -> 10000000;
+ 8 -> 100000000;
+ 9 -> 1000000000;
+ 10 -> 10000000000;
+ 11 -> 100000000000;
+ 12 -> 1000000000000
+ end,
+ MaxLenWithSign = if
+ Base < 0 -> 13;
+ true -> 12
+ end,
+ Bin = integer_to_binary(Base * Mul),
+ true = byte_size(Bin) =< MaxLenWithSign,
+ [Bin, <<".0">>];
+bare_item({decimal, {Base, -1}}) ->
+ Int = Base div 10,
+ Frac = abs(Base) rem 10,
+ [integer_to_binary(Int), $., integer_to_binary(Frac)];
+bare_item({decimal, {Base, -2}}) ->
+ Int = Base div 100,
+ Frac = abs(Base) rem 100,
+ [integer_to_binary(Int), $., integer_to_binary(Frac)];
+bare_item({decimal, {Base, -3}}) ->
+ Int = Base div 1000,
+ Frac = abs(Base) rem 1000,
+ [integer_to_binary(Int), $., integer_to_binary(Frac)];
+bare_item({decimal, {Base, Exp}}) ->
+ Div = exp_div(Exp),
+ Int0 = Base div Div,
+ true = abs(Int0) < 1000000000000,
+ Frac0 = abs(Base) rem Div,
+ DivFrac = Div div 1000,
+ Frac1 = Frac0 div DivFrac,
+ {Int, Frac} = if
+ (Frac0 rem DivFrac) > (DivFrac div 2) ->
+ case Frac1 of
+ 999 when Int0 < 0 -> {Int0 - 1, 0};
+ 999 -> {Int0 + 1, 0};
+ _ -> {Int0, Frac1 + 1}
+ end;
+ true ->
+ {Int0, Frac1}
+ end,
+ [integer_to_binary(Int), $., if
+ Frac < 10 -> [$0, $0, integer_to_binary(Frac)];
+ Frac < 100 -> [$0, integer_to_binary(Frac)];
+ true -> integer_to_binary(Frac)
+ end];
+bare_item(Integer) when is_integer(Integer) ->
+ integer_to_binary(Integer);
+bare_item(true) ->
+ <<"?1">>;
+bare_item(false) ->
+ <<"?0">>.
+
+exp_div(0) -> 1;
+exp_div(N) -> 10 * exp_div(N + 1).
+
+escape_string(<<>>, Acc) -> Acc;
+escape_string(<<$\\,R/bits>>, Acc) -> escape_string(R, <<Acc/binary,$\\,$\\>>);
+escape_string(<<$",R/bits>>, Acc) -> escape_string(R, <<Acc/binary,$\\,$">>);
+escape_string(<<C,R/bits>>, Acc) -> escape_string(R, <<Acc/binary,C>>).
+
+params(Params) ->
+ [case Param of
+ {Key, true} -> [$;, Key];
+ {Key, Value} -> [$;, Key, $=, bare_item(Value)]
+ end || Param <- Params].
+
+-ifdef(TEST).
+struct_hd_identity_test_() ->
+ Files = filelib:wildcard("deps/structured-header-tests/*.json"),
+ lists:flatten([begin
+ {ok, JSON} = file:read_file(File),
+ Tests = jsx:decode(JSON, [return_maps]),
+ [
+ {iolist_to_binary(io_lib:format("~s: ~s", [filename:basename(File), Name])), fun() ->
+ io:format("expected json ~0p~n", [Expected0]),
+ Expected = expected_to_term(Expected0),
+ io:format("expected term: ~0p", [Expected]),
+ case HeaderType of
+ <<"dictionary">> ->
+ Expected = parse_dictionary(iolist_to_binary(dictionary(Expected)));
+ <<"item">> ->
+ Expected = parse_item(iolist_to_binary(item(Expected)));
+ <<"list">> ->
+ Expected = parse_list(iolist_to_binary(list(Expected)))
+ end
+ end}
+ || #{
+ <<"name">> := Name,
+ <<"header_type">> := HeaderType,
+ %% We only run tests that must not fail.
+ <<"expected">> := Expected0
+ } <- Tests]
+ end || File <- Files]).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_http_te.erl b/server/_build/default/lib/cowlib/src/cow_http_te.erl
new file mode 100644
index 0000000..e3473cf
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_http_te.erl
@@ -0,0 +1,373 @@
+%% Copyright (c) 2014-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_http_te).
+
+%% Identity.
+-export([stream_identity/2]).
+-export([identity/1]).
+
+%% Chunked.
+-export([stream_chunked/2]).
+-export([chunk/1]).
+-export([last_chunk/0]).
+
+%% The state type is the same for both identity and chunked.
+-type state() :: {non_neg_integer(), non_neg_integer()}.
+-export_type([state/0]).
+
+-type decode_ret() :: more
+ | {more, Data::binary(), state()}
+ | {more, Data::binary(), RemLen::non_neg_integer(), state()}
+ | {more, Data::binary(), Rest::binary(), state()}
+ | {done, HasTrailers::trailers | no_trailers, Rest::binary()}
+ | {done, Data::binary(), HasTrailers::trailers | no_trailers, Rest::binary()}.
+-export_type([decode_ret/0]).
+
+-include("cow_parse.hrl").
+
+-ifdef(TEST).
+dripfeed(<< C, Rest/bits >>, Acc, State, F) ->
+ case F(<< Acc/binary, C >>, State) of
+ more ->
+ dripfeed(Rest, << Acc/binary, C >>, State, F);
+ {more, _, State2} ->
+ dripfeed(Rest, <<>>, State2, F);
+ {more, _, Length, State2} when is_integer(Length) ->
+ dripfeed(Rest, <<>>, State2, F);
+ {more, _, Acc2, State2} ->
+ dripfeed(Rest, Acc2, State2, F);
+ {done, _, <<>>} ->
+ ok;
+ {done, _, _, <<>>} ->
+ ok
+ end.
+-endif.
+
+%% Identity.
+
+%% @doc Decode an identity stream.
+
+-spec stream_identity(Data, State)
+ -> {more, Data, Len, State} | {done, Data, Len, Data}
+ when Data::binary(), State::state(), Len::non_neg_integer().
+stream_identity(Data, {Streamed, Total}) ->
+ Streamed2 = Streamed + byte_size(Data),
+ if
+ Streamed2 < Total ->
+ {more, Data, Total - Streamed2, {Streamed2, Total}};
+ true ->
+ Size = Total - Streamed,
+ << Data2:Size/binary, Rest/bits >> = Data,
+ {done, Data2, Total, Rest}
+ end.
+
+-spec identity(Data) -> Data when Data::iodata().
+identity(Data) ->
+ Data.
+
+-ifdef(TEST).
+stream_identity_test() ->
+ {done, <<>>, 0, <<>>}
+ = stream_identity(identity(<<>>), {0, 0}),
+ {done, <<"\r\n">>, 2, <<>>}
+ = stream_identity(identity(<<"\r\n">>), {0, 2}),
+ {done, << 0:80000 >>, 10000, <<>>}
+ = stream_identity(identity(<< 0:80000 >>), {0, 10000}),
+ ok.
+
+stream_identity_parts_test() ->
+ {more, << 0:8000 >>, 1999, S1}
+ = stream_identity(<< 0:8000 >>, {0, 2999}),
+ {more, << 0:8000 >>, 999, S2}
+ = stream_identity(<< 0:8000 >>, S1),
+ {done, << 0:7992 >>, 2999, <<>>}
+ = stream_identity(<< 0:7992 >>, S2),
+ ok.
+
+%% Using the same data as the chunked one for comparison.
+horse_stream_identity() ->
+ horse:repeat(10000,
+ stream_identity(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, {0, 43})
+ ).
+
+horse_stream_identity_dripfeed() ->
+ horse:repeat(10000,
+ dripfeed(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, <<>>, {0, 43}, fun stream_identity/2)
+ ).
+-endif.
+
+%% Chunked.
+
+%% @doc Decode a chunked stream.
+
+-spec stream_chunked(Data, State)
+ -> more | {more, Data, State} | {more, Data, non_neg_integer(), State}
+ | {more, Data, Data, State}
+ | {done, HasTrailers, Data} | {done, Data, HasTrailers, Data}
+ when Data::binary(), State::state(), HasTrailers::trailers | no_trailers.
+stream_chunked(Data, State) ->
+ stream_chunked(Data, State, <<>>).
+
+%% New chunk.
+stream_chunked(Data = << C, _/bits >>, {0, Streamed}, Acc) when C =/= $\r ->
+ case chunked_len(Data, Streamed, Acc, 0) of
+ {next, Rest, State, Acc2} ->
+ stream_chunked(Rest, State, Acc2);
+ {more, State, Acc2} ->
+ {more, Acc2, Data, State};
+ Ret ->
+ Ret
+ end;
+%% Trailing \r\n before next chunk.
+stream_chunked(<< "\r\n", Rest/bits >>, {2, Streamed}, Acc) ->
+ stream_chunked(Rest, {0, Streamed}, Acc);
+%% Trailing \r before next chunk.
+stream_chunked(<< "\r" >>, {2, Streamed}, Acc) ->
+ {more, Acc, {1, Streamed}};
+%% Trailing \n before next chunk.
+stream_chunked(<< "\n", Rest/bits >>, {1, Streamed}, Acc) ->
+ stream_chunked(Rest, {0, Streamed}, Acc);
+%% More data needed.
+stream_chunked(<<>>, State = {Rem, _}, Acc) ->
+ {more, Acc, Rem, State};
+%% Chunk data.
+stream_chunked(Data, {Rem, Streamed}, Acc) when Rem > 2 ->
+ DataSize = byte_size(Data),
+ RemSize = Rem - 2,
+ case Data of
+ << Chunk:RemSize/binary, "\r\n", Rest/bits >> ->
+ stream_chunked(Rest, {0, Streamed + RemSize}, << Acc/binary, Chunk/binary >>);
+ << Chunk:RemSize/binary, "\r" >> ->
+ {more, << Acc/binary, Chunk/binary >>, {1, Streamed + RemSize}};
+ %% Everything in Data is part of the chunk. If we have more
+ %% data than the chunk accepts, then this is an error and we crash.
+ _ when DataSize =< RemSize ->
+ Rem2 = Rem - DataSize,
+ {more, << Acc/binary, Data/binary >>, Rem2, {Rem2, Streamed + DataSize}}
+ end.
+
+chunked_len(<< $0, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16);
+chunked_len(<< $1, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 1);
+chunked_len(<< $2, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 2);
+chunked_len(<< $3, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 3);
+chunked_len(<< $4, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 4);
+chunked_len(<< $5, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 5);
+chunked_len(<< $6, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 6);
+chunked_len(<< $7, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 7);
+chunked_len(<< $8, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 8);
+chunked_len(<< $9, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 9);
+chunked_len(<< $A, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 10);
+chunked_len(<< $B, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 11);
+chunked_len(<< $C, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 12);
+chunked_len(<< $D, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 13);
+chunked_len(<< $E, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 14);
+chunked_len(<< $F, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 15);
+chunked_len(<< $a, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 10);
+chunked_len(<< $b, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 11);
+chunked_len(<< $c, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 12);
+chunked_len(<< $d, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 13);
+chunked_len(<< $e, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 14);
+chunked_len(<< $f, R/bits >>, S, A, Len) -> chunked_len(R, S, A, Len * 16 + 15);
+%% Chunk extensions.
+%%
+%% Note that we currently skip the first character we encounter here,
+%% and not in the skip_chunk_ext function. If we latter implement
+%% chunk extensions (unlikely) we will need to change this clause too.
+chunked_len(<< C, R/bits >>, S, A, Len) when ?IS_WS(C); C =:= $; -> skip_chunk_ext(R, S, A, Len, 0);
+%% Final chunk.
+%%
+%% When trailers are following we simply return them as the Rest.
+%% Then the user code can decide to call the stream_trailers function
+%% to parse them. The user can therefore ignore trailers as necessary
+%% if they do not wish to handle them.
+chunked_len(<< "\r\n\r\n", R/bits >>, _, <<>>, 0) -> {done, no_trailers, R};
+chunked_len(<< "\r\n\r\n", R/bits >>, _, A, 0) -> {done, A, no_trailers, R};
+chunked_len(<< "\r\n", R/bits >>, _, <<>>, 0) when byte_size(R) > 2 -> {done, trailers, R};
+chunked_len(<< "\r\n", R/bits >>, _, A, 0) when byte_size(R) > 2 -> {done, A, trailers, R};
+chunked_len(_, _, _, 0) -> more;
+%% Normal chunk. Add 2 to Len for the trailing \r\n.
+chunked_len(<< "\r\n", R/bits >>, S, A, Len) -> {next, R, {Len + 2, S}, A};
+chunked_len(<<"\r">>, _, <<>>, _) -> more;
+chunked_len(<<"\r">>, S, A, _) -> {more, {0, S}, A};
+chunked_len(<<>>, _, <<>>, _) -> more;
+chunked_len(<<>>, S, A, _) -> {more, {0, S}, A}.
+
+skip_chunk_ext(R = << "\r", _/bits >>, S, A, Len, _) -> chunked_len(R, S, A, Len);
+skip_chunk_ext(R = <<>>, S, A, Len, _) -> chunked_len(R, S, A, Len);
+%% We skip up to 128 characters of chunk extensions. The value
+%% is hardcoded: chunk extensions are very rarely seen in the
+%% wild and Cowboy doesn't do anything with them anyway.
+%%
+%% Line breaks are not allowed in the middle of chunk extensions.
+skip_chunk_ext(<< C, R/bits >>, S, A, Len, Skipped) when C =/= $\n, Skipped < 128 ->
+ skip_chunk_ext(R, S, A, Len, Skipped + 1).
+
+%% @doc Encode a chunk.
+
+-spec chunk(D) -> D when D::iodata().
+chunk(Data) ->
+ [integer_to_list(iolist_size(Data), 16), <<"\r\n">>,
+ Data, <<"\r\n">>].
+
+%% @doc Encode the last chunk of a chunked stream.
+
+-spec last_chunk() -> << _:40 >>.
+last_chunk() ->
+ <<"0\r\n\r\n">>.
+
+-ifdef(TEST).
+stream_chunked_identity_test() ->
+ {done, <<"Wikipedia in\r\n\r\nchunks.">>, no_trailers, <<>>}
+ = stream_chunked(iolist_to_binary([
+ chunk("Wiki"),
+ chunk("pedia"),
+ chunk(" in\r\n\r\nchunks."),
+ last_chunk()
+ ]), {0, 0}),
+ ok.
+
+stream_chunked_one_pass_test() ->
+ {done, no_trailers, <<>>} = stream_chunked(<<"0\r\n\r\n">>, {0, 0}),
+ {done, <<"Wikipedia in\r\n\r\nchunks.">>, no_trailers, <<>>}
+ = stream_chunked(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, {0, 0}),
+ %% Same but with extra spaces or chunk extensions.
+ {done, <<"Wikipedia in\r\n\r\nchunks.">>, no_trailers, <<>>}
+ = stream_chunked(<<
+ "4 \r\n"
+ "Wiki\r\n"
+ "5 ; ext = abc\r\n"
+ "pedia\r\n"
+ "e;ext=abc\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0;ext\r\n"
+ "\r\n">>, {0, 0}),
+ %% Same but with trailers.
+ {done, <<"Wikipedia in\r\n\r\nchunks.">>, trailers, Rest}
+ = stream_chunked(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "x-foo-bar: bar foo\r\n"
+ "\r\n">>, {0, 0}),
+ {[{<<"x-foo-bar">>, <<"bar foo">>}], <<>>} = cow_http:parse_headers(Rest),
+ ok.
+
+stream_chunked_n_passes_test() ->
+ S0 = {0, 0},
+ more = stream_chunked(<<"4\r">>, S0),
+ {more, <<>>, 6, S1} = stream_chunked(<<"4\r\n">>, S0),
+ {more, <<"Wiki">>, 0, S2} = stream_chunked(<<"Wiki\r\n">>, S1),
+ {more, <<"pedia">>, <<"e\r">>, S3} = stream_chunked(<<"5\r\npedia\r\ne\r">>, S2),
+ {more, <<" in\r\n\r\nchunks.">>, 2, S4} = stream_chunked(<<"e\r\n in\r\n\r\nchunks.">>, S3),
+ {done, no_trailers, <<>>} = stream_chunked(<<"\r\n0\r\n\r\n">>, S4),
+ %% A few extra for coverage purposes.
+ more = stream_chunked(<<"\n3">>, {1, 0}),
+ {more, <<"abc">>, 2, {2, 3}} = stream_chunked(<<"\n3\r\nabc">>, {1, 0}),
+ {more, <<"abc">>, {1, 3}} = stream_chunked(<<"3\r\nabc\r">>, {0, 0}),
+ {more, <<"abc">>, <<"123">>, {0, 3}} = stream_chunked(<<"3\r\nabc\r\n123">>, {0, 0}),
+ ok.
+
+stream_chunked_dripfeed_test() ->
+ dripfeed(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, <<>>, {0, 0}, fun stream_chunked/2).
+
+do_body_to_chunks(_, <<>>, Acc) ->
+ lists:reverse([<<"0\r\n\r\n">>|Acc]);
+do_body_to_chunks(ChunkSize, Body, Acc) ->
+ BodySize = byte_size(Body),
+ ChunkSize2 = case BodySize < ChunkSize of
+ true -> BodySize;
+ false -> ChunkSize
+ end,
+ << Chunk:ChunkSize2/binary, Rest/binary >> = Body,
+ ChunkSizeBin = list_to_binary(integer_to_list(ChunkSize2, 16)),
+ do_body_to_chunks(ChunkSize, Rest,
+ [<< ChunkSizeBin/binary, "\r\n", Chunk/binary, "\r\n" >>|Acc]).
+
+stream_chunked_dripfeed2_test() ->
+ Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])),
+ Body2 = iolist_to_binary(do_body_to_chunks(50, Body, [])),
+ dripfeed(Body2, <<>>, {0, 0}, fun stream_chunked/2).
+
+stream_chunked_error_test_() ->
+ Tests = [
+ {<<>>, undefined},
+ {<<"\n\naaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa">>, {2, 0}}
+ ],
+ [{lists:flatten(io_lib:format("value ~p state ~p", [V, S])),
+ fun() -> {'EXIT', _} = (catch stream_chunked(V, S)) end}
+ || {V, S} <- Tests].
+
+horse_stream_chunked() ->
+ horse:repeat(10000,
+ stream_chunked(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, {0, 0})
+ ).
+
+horse_stream_chunked_dripfeed() ->
+ horse:repeat(10000,
+ dripfeed(<<
+ "4\r\n"
+ "Wiki\r\n"
+ "5\r\n"
+ "pedia\r\n"
+ "e\r\n"
+ " in\r\n\r\nchunks.\r\n"
+ "0\r\n"
+ "\r\n">>, <<>>, {0, 43}, fun stream_chunked/2)
+ ).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_iolists.erl b/server/_build/default/lib/cowlib/src/cow_iolists.erl
new file mode 100644
index 0000000..a5e75df
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_iolists.erl
@@ -0,0 +1,95 @@
+%% Copyright (c) 2017-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_iolists).
+
+-export([split/2]).
+
+-ifdef(TEST).
+-include_lib("proper/include/proper.hrl").
+-endif.
+
+-spec split(non_neg_integer(), iodata()) -> {iodata(), iodata()}.
+split(N, Iolist) ->
+ case split(N, Iolist, []) of
+ {ok, Before, After} ->
+ {Before, After};
+ {more, _, Before} ->
+ {lists:reverse(Before), <<>>}
+ end.
+
+split(0, Rest, Acc) ->
+ {ok, lists:reverse(Acc), Rest};
+split(N, [], Acc) ->
+ {more, N, Acc};
+split(N, Binary, Acc) when byte_size(Binary) =< N ->
+ {more, N - byte_size(Binary), [Binary|Acc]};
+split(N, Binary, Acc) when is_binary(Binary) ->
+ << Before:N/binary, After/bits >> = Binary,
+ {ok, lists:reverse([Before|Acc]), After};
+split(N, [Binary|Tail], Acc) when byte_size(Binary) =< N ->
+ split(N - byte_size(Binary), Tail, [Binary|Acc]);
+split(N, [Binary|Tail], Acc) when is_binary(Binary) ->
+ << Before:N/binary, After/bits >> = Binary,
+ {ok, lists:reverse([Before|Acc]), [After|Tail]};
+split(N, [Char|Tail], Acc) when is_integer(Char) ->
+ split(N - 1, Tail, [Char|Acc]);
+split(N, [List|Tail], Acc0) ->
+ case split(N, List, Acc0) of
+ {ok, Before, After} ->
+ {ok, Before, [After|Tail]};
+ {more, More, Acc} ->
+ split(More, Tail, Acc)
+ end.
+
+-ifdef(TEST).
+
+split_test_() ->
+ Tests = [
+ {10, "Hello world!", "Hello worl", "d!"},
+ {10, <<"Hello world!">>, "Hello worl", "d!"},
+ {10, ["He", [<<"llo">>], $\s, [["world"], <<"!">>]], "Hello worl", "d!"},
+ {10, ["Hello "|<<"world!">>], "Hello worl", "d!"},
+ {10, "Hello!", "Hello!", ""},
+ {10, <<"Hello!">>, "Hello!", ""},
+ {10, ["He", [<<"ll">>], $o, [["!"]]], "Hello!", ""},
+ {10, ["Hel"|<<"lo!">>], "Hello!", ""},
+ {10, [[<<>>|<<>>], [], <<"Hello world!">>], "Hello worl", "d!"},
+ {10, [[<<"He">>|<<"llo">>], [$\s], <<"world!">>], "Hello worl", "d!"},
+ {10, [[[]|<<"He">>], [[]|<<"llo wor">>]|<<"ld!">>], "Hello worl", "d!"}
+ ],
+ [{iolist_to_binary(V), fun() ->
+ {B, A} = split(N, V),
+ true = iolist_to_binary(RB) =:= iolist_to_binary(B),
+ true = iolist_to_binary(RA) =:= iolist_to_binary(A)
+ end} || {N, V, RB, RA} <- Tests].
+
+prop_split_test() ->
+ ?FORALL({N, Input},
+ {non_neg_integer(), iolist()},
+ begin
+ Size = iolist_size(Input),
+ {Before, After} = split(N, Input),
+ if
+ N >= Size ->
+ ((iolist_size(After) =:= 0)
+ andalso iolist_to_binary(Before) =:= iolist_to_binary(Input));
+ true ->
+ <<ExpectBefore:N/binary, ExpectAfter/bits>> = iolist_to_binary(Input),
+ (ExpectBefore =:= iolist_to_binary(Before))
+ andalso (ExpectAfter =:= iolist_to_binary(After))
+ end
+ end).
+
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_link.erl b/server/_build/default/lib/cowlib/src/cow_link.erl
new file mode 100644
index 0000000..b649786
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_link.erl
@@ -0,0 +1,445 @@
+%% Copyright (c) 2019-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_link).
+-compile({no_auto_import, [link/1]}).
+
+-export([parse_link/1]).
+-export([resolve_link/2]).
+-export([resolve_link/3]).
+-export([link/1]).
+
+-include("cow_inline.hrl").
+-include("cow_parse.hrl").
+
+-type link() :: #{
+ target := binary(),
+ rel := binary(),
+ attributes := [{binary(), binary()}]
+}.
+-export_type([link/0]).
+
+-type resolve_opts() :: #{
+ allow_anchor => boolean()
+}.
+
+-type uri() :: uri_string:uri_map() | uri_string:uri_string() | undefined.
+
+%% Parse a link header.
+
+%% This function returns the URI target from the header directly.
+%% Relative URIs must then be resolved as per RFC3986 5. In some
+%% cases it might not be possible to resolve URIs, for example when
+%% the link header is returned with a 404 status code.
+-spec parse_link(binary()) -> [link()].
+parse_link(Link) ->
+ before_target(Link, []).
+
+before_target(<<>>, Acc) -> lists:reverse(Acc);
+before_target(<<$<,R/bits>>, Acc) -> target(R, Acc, <<>>);
+before_target(<<C,R/bits>>, Acc) when ?IS_WS(C) -> before_target(R, Acc).
+
+target(<<$>,R/bits>>, Acc, T) -> param_sep(R, Acc, T, []);
+target(<<C,R/bits>>, Acc, T) -> target(R, Acc, <<T/binary, C>>).
+
+param_sep(<<>>, Acc, T, P) -> lists:reverse(acc_link(Acc, T, P));
+param_sep(<<$,,R/bits>>, Acc, T, P) -> before_target(R, acc_link(Acc, T, P));
+param_sep(<<$;,R/bits>>, Acc, T, P) -> before_param(R, Acc, T, P);
+param_sep(<<C,R/bits>>, Acc, T, P) when ?IS_WS(C) -> param_sep(R, Acc, T, P).
+
+before_param(<<C,R/bits>>, Acc, T, P) when ?IS_WS(C) -> before_param(R, Acc, T, P);
+before_param(<<C,R/bits>>, Acc, T, P) when ?IS_TOKEN(C) -> ?LOWER(param, R, Acc, T, P, <<>>).
+
+param(<<$=,$",R/bits>>, Acc, T, P, K) -> quoted(R, Acc, T, P, K, <<>>);
+param(<<$=,C,R/bits>>, Acc, T, P, K) when ?IS_TOKEN(C) -> value(R, Acc, T, P, K, <<C>>);
+param(<<C,R/bits>>, Acc, T, P, K) when ?IS_TOKEN(C) -> ?LOWER(param, R, Acc, T, P, K).
+
+quoted(<<$",R/bits>>, Acc, T, P, K, V) -> param_sep(R, Acc, T, [{K, V}|P]);
+quoted(<<$\\,C,R/bits>>, Acc, T, P, K, V) when ?IS_VCHAR_OBS(C) -> quoted(R, Acc, T, P, K, <<V/binary,C>>);
+quoted(<<C,R/bits>>, Acc, T, P, K, V) when ?IS_VCHAR_OBS(C) -> quoted(R, Acc, T, P, K, <<V/binary,C>>).
+
+value(<<C,R/bits>>, Acc, T, P, K, V) when ?IS_TOKEN(C) -> value(R, Acc, T, P, K, <<V/binary,C>>);
+value(R, Acc, T, P, K, V) -> param_sep(R, Acc, T, [{K, V}|P]).
+
+acc_link(Acc, Target, Params0) ->
+ Params1 = lists:reverse(Params0),
+ %% The rel parameter MUST be present. (RFC8288 3.3)
+ {value, {_, Rel}, Params2} = lists:keytake(<<"rel">>, 1, Params1),
+ %% Occurrences after the first MUST be ignored by parsers.
+ Params = filter_out_duplicates(Params2, #{}),
+ [#{
+ target => Target,
+ rel => ?LOWER(Rel),
+ attributes => Params
+ }|Acc].
+
+%% This function removes duplicates for attributes that don't allow them.
+filter_out_duplicates([], _) ->
+ [];
+%% The "rel" is mandatory and was already removed from params.
+filter_out_duplicates([{<<"rel">>, _}|Tail], State) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([{<<"anchor">>, _}|Tail], State=#{anchor := true}) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([{<<"media">>, _}|Tail], State=#{media := true}) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([{<<"title">>, _}|Tail], State=#{title := true}) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([{<<"title*">>, _}|Tail], State=#{title_star := true}) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([{<<"type">>, _}|Tail], State=#{type := true}) ->
+ filter_out_duplicates(Tail, State);
+filter_out_duplicates([Tuple={<<"anchor">>, _}|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State#{anchor => true})];
+filter_out_duplicates([Tuple={<<"media">>, _}|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State#{media => true})];
+filter_out_duplicates([Tuple={<<"title">>, _}|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State#{title => true})];
+filter_out_duplicates([Tuple={<<"title*">>, _}|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State#{title_star => true})];
+filter_out_duplicates([Tuple={<<"type">>, _}|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State#{type => true})];
+filter_out_duplicates([Tuple|Tail], State) ->
+ [Tuple|filter_out_duplicates(Tail, State)].
+
+-ifdef(TEST).
+parse_link_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<" ">>, []},
+ %% Examples from the RFC.
+ {<<"<http://example.com/TheBook/chapter2>; rel=\"previous\"; title=\"previous chapter\"">>, [
+ #{
+ target => <<"http://example.com/TheBook/chapter2">>,
+ rel => <<"previous">>,
+ attributes => [
+ {<<"title">>, <<"previous chapter">>}
+ ]
+ }
+ ]},
+ {<<"</>; rel=\"http://example.net/foo\"">>, [
+ #{
+ target => <<"/">>,
+ rel => <<"http://example.net/foo">>,
+ attributes => []
+ }
+ ]},
+ {<<"</terms>; rel=\"copyright\"; anchor=\"#foo\"">>, [
+ #{
+ target => <<"/terms">>,
+ rel => <<"copyright">>,
+ attributes => [
+ {<<"anchor">>, <<"#foo">>}
+ ]
+ }
+ ]},
+% {<<"</TheBook/chapter2>; rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, "
+% "</TheBook/chapter4>; rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel">>, [
+% %% @todo
+% ]}
+ {<<"<http://example.org/>; rel=\"start http://example.net/relation/other\"">>, [
+ #{
+ target => <<"http://example.org/">>,
+ rel => <<"start http://example.net/relation/other">>,
+ attributes => []
+ }
+ ]},
+ {<<"<https://example.org/>; rel=\"start\", "
+ "<https://example.org/index>; rel=\"index\"">>, [
+ #{
+ target => <<"https://example.org/">>,
+ rel => <<"start">>,
+ attributes => []
+ },
+ #{
+ target => <<"https://example.org/index">>,
+ rel => <<"index">>,
+ attributes => []
+ }
+ ]},
+ %% Relation types are case insensitive.
+ {<<"</>; rel=\"SELF\"">>, [
+ #{
+ target => <<"/">>,
+ rel => <<"self">>,
+ attributes => []
+ }
+ ]},
+ {<<"</>; rel=\"HTTP://EXAMPLE.NET/FOO\"">>, [
+ #{
+ target => <<"/">>,
+ rel => <<"http://example.net/foo">>,
+ attributes => []
+ }
+ ]},
+ %% Attribute names are case insensitive.
+ {<<"</terms>; REL=\"copyright\"; ANCHOR=\"#foo\"">>, [
+ #{
+ target => <<"/terms">>,
+ rel => <<"copyright">>,
+ attributes => [
+ {<<"anchor">>, <<"#foo">>}
+ ]
+ }
+ ]}
+ ],
+ [{V, fun() -> R = parse_link(V) end} || {V, R} <- Tests].
+-endif.
+
+%% Resolve a link based on the context URI and options.
+
+-spec resolve_link(Link, uri()) -> Link | false when Link::link().
+resolve_link(Link, ContextURI) ->
+ resolve_link(Link, ContextURI, #{}).
+
+-spec resolve_link(Link, uri(), resolve_opts()) -> Link | false when Link::link().
+%% When we do not have a context URI we only succeed when the target URI is absolute.
+%% The target URI will only be normalized in that case.
+resolve_link(Link=#{target := TargetURI}, undefined, _) ->
+ case uri_string:parse(TargetURI) of
+ URIMap = #{scheme := _} ->
+ Link#{target => uri_string:normalize(URIMap)};
+ _ ->
+ false
+ end;
+resolve_link(Link=#{attributes := Params}, ContextURI, Opts) ->
+ AllowAnchor = maps:get(allow_anchor, Opts, true),
+ case lists:keyfind(<<"anchor">>, 1, Params) of
+ false ->
+ do_resolve_link(Link, ContextURI);
+ {_, Anchor} when AllowAnchor ->
+ do_resolve_link(Link, resolve(Anchor, ContextURI));
+ _ ->
+ false
+ end.
+
+do_resolve_link(Link=#{target := TargetURI}, ContextURI) ->
+ Link#{target => uri_string:recompose(resolve(TargetURI, ContextURI))}.
+
+-ifdef(TEST).
+resolve_link_test_() ->
+ Tests = [
+ %% No context URI available.
+ {#{target => <<"http://a/b/./c">>}, undefined, #{},
+ #{target => <<"http://a/b/c">>}},
+ {#{target => <<"a/b/./c">>}, undefined, #{},
+ false},
+ %% Context URI available, allow_anchor => true.
+ {#{target => <<"http://a/b">>, attributes => []}, <<"http://a/c">>, #{},
+ #{target => <<"http://a/b">>, attributes => []}},
+ {#{target => <<"b">>, attributes => []}, <<"http://a/c">>, #{},
+ #{target => <<"http://a/b">>, attributes => []}},
+ {#{target => <<"b">>, attributes => [{<<"anchor">>, <<"#frag">>}]}, <<"http://a/c">>, #{},
+ #{target => <<"http://a/b">>, attributes => [{<<"anchor">>, <<"#frag">>}]}},
+ {#{target => <<"b">>, attributes => [{<<"anchor">>, <<"d/e">>}]}, <<"http://a/c">>, #{},
+ #{target => <<"http://a/d/b">>, attributes => [{<<"anchor">>, <<"d/e">>}]}},
+ %% Context URI available, allow_anchor => false.
+ {#{target => <<"http://a/b">>, attributes => []}, <<"http://a/c">>, #{allow_anchor => false},
+ #{target => <<"http://a/b">>, attributes => []}},
+ {#{target => <<"b">>, attributes => []}, <<"http://a/c">>, #{allow_anchor => false},
+ #{target => <<"http://a/b">>, attributes => []}},
+ {#{target => <<"b">>, attributes => [{<<"anchor">>, <<"#frag">>}]},
+ <<"http://a/c">>, #{allow_anchor => false}, false},
+ {#{target => <<"b">>, attributes => [{<<"anchor">>, <<"d/e">>}]},
+ <<"http://a/c">>, #{allow_anchor => false}, false}
+ ],
+ [{iolist_to_binary(io_lib:format("~0p", [L])),
+ fun() -> R = resolve_link(L, C, O) end} || {L, C, O, R} <- Tests].
+-endif.
+
+%% @todo This function has been added to Erlang/OTP 22.3 as uri_string:resolve/2,3.
+resolve(URI, BaseURI) ->
+ case resolve1(ensure_map_uri(URI), BaseURI) of
+ TargetURI = #{path := Path0} ->
+ %% We remove dot segments. Normalizing the entire URI
+ %% will sometimes add an extra slash we don't want.
+ #{path := Path} = uri_string:normalize(#{path => Path0}, [return_map]),
+ TargetURI#{path => Path};
+ TargetURI ->
+ TargetURI
+ end.
+
+resolve1(URI=#{scheme := _}, _) ->
+ URI;
+resolve1(URI=#{host := _}, BaseURI) ->
+ #{scheme := Scheme} = ensure_map_uri(BaseURI),
+ URI#{scheme => Scheme};
+resolve1(URI=#{path := <<>>}, BaseURI0) ->
+ BaseURI = ensure_map_uri(BaseURI0),
+ Keys = case maps:is_key(query, URI) of
+ true -> [scheme, host, port, path];
+ false -> [scheme, host, port, path, query]
+ end,
+ maps:merge(URI, maps:with(Keys, BaseURI));
+resolve1(URI=#{path := <<"/",_/bits>>}, BaseURI0) ->
+ BaseURI = ensure_map_uri(BaseURI0),
+ maps:merge(URI, maps:with([scheme, host, port], BaseURI));
+resolve1(URI=#{path := Path}, BaseURI0) ->
+ BaseURI = ensure_map_uri(BaseURI0),
+ maps:merge(
+ URI#{path := merge_paths(Path, BaseURI)},
+ maps:with([scheme, host, port], BaseURI)).
+
+merge_paths(Path, #{host := _, path := <<>>}) ->
+ <<$/, Path/binary>>;
+merge_paths(Path, #{path := BasePath0}) ->
+ case string:split(BasePath0, <<$/>>, trailing) of
+ [BasePath, _] -> <<BasePath/binary, $/, Path/binary>>;
+ [_] -> <<$/, Path/binary>>
+ end.
+
+ensure_map_uri(URI) when is_map(URI) -> URI;
+ensure_map_uri(URI) -> uri_string:parse(iolist_to_binary(URI)).
+
+-ifdef(TEST).
+resolve_test_() ->
+ Tests = [
+ %% 5.4.1. Normal Examples
+ {<<"g:h">>, <<"g:h">>},
+ {<<"g">>, <<"http://a/b/c/g">>},
+ {<<"./g">>, <<"http://a/b/c/g">>},
+ {<<"g/">>, <<"http://a/b/c/g/">>},
+ {<<"/g">>, <<"http://a/g">>},
+ {<<"//g">>, <<"http://g">>},
+ {<<"?y">>, <<"http://a/b/c/d;p?y">>},
+ {<<"g?y">>, <<"http://a/b/c/g?y">>},
+ {<<"#s">>, <<"http://a/b/c/d;p?q#s">>},
+ {<<"g#s">>, <<"http://a/b/c/g#s">>},
+ {<<"g?y#s">>, <<"http://a/b/c/g?y#s">>},
+ {<<";x">>, <<"http://a/b/c/;x">>},
+ {<<"g;x">>, <<"http://a/b/c/g;x">>},
+ {<<"g;x?y#s">>, <<"http://a/b/c/g;x?y#s">>},
+ {<<"">>, <<"http://a/b/c/d;p?q">>},
+ {<<".">>, <<"http://a/b/c/">>},
+ {<<"./">>, <<"http://a/b/c/">>},
+ {<<"..">>, <<"http://a/b/">>},
+ {<<"../">>, <<"http://a/b/">>},
+ {<<"../g">>, <<"http://a/b/g">>},
+ {<<"../..">>, <<"http://a/">>},
+ {<<"../../">>, <<"http://a/">>},
+ {<<"../../g">>, <<"http://a/g">>},
+ %% 5.4.2. Abnormal Examples
+ {<<"../../../g">>, <<"http://a/g">>},
+ {<<"../../../../g">>, <<"http://a/g">>},
+ {<<"/./g">>, <<"http://a/g">>},
+ {<<"/../g">>, <<"http://a/g">>},
+ {<<"g.">>, <<"http://a/b/c/g.">>},
+ {<<".g">>, <<"http://a/b/c/.g">>},
+ {<<"g..">>, <<"http://a/b/c/g..">>},
+ {<<"..g">>, <<"http://a/b/c/..g">>},
+ {<<"./../g">>, <<"http://a/b/g">>},
+ {<<"./g/.">>, <<"http://a/b/c/g/">>},
+ {<<"g/./h">>, <<"http://a/b/c/g/h">>},
+ {<<"g/../h">>, <<"http://a/b/c/h">>},
+ {<<"g;x=1/./y">>, <<"http://a/b/c/g;x=1/y">>},
+ {<<"g;x=1/../y">>, <<"http://a/b/c/y">>},
+ {<<"g?y/./x">>, <<"http://a/b/c/g?y/./x">>},
+ {<<"g?y/../x">>, <<"http://a/b/c/g?y/../x">>},
+ {<<"g#s/./x">>, <<"http://a/b/c/g#s/./x">>},
+ {<<"g#s/../x">>, <<"http://a/b/c/g#s/../x">>},
+ {<<"http:g">>, <<"http:g">>} %% for strict parsers
+ ],
+ [{V, fun() -> R = uri_string:recompose(resolve(V, <<"http://a/b/c/d;p?q">>)) end} || {V, R} <- Tests].
+-endif.
+
+%% Build a link header.
+
+-spec link([#{
+ target := binary(),
+ rel := binary(),
+ attributes := [{binary(), binary()}]
+}]) -> iodata().
+link(Links) ->
+ lists:join(<<", ">>, [do_link(Link) || Link <- Links]).
+
+do_link(#{target := TargetURI, rel := Rel, attributes := Params}) ->
+ [
+ $<, TargetURI, <<">"
+ "; rel=\"">>, Rel, $",
+ [[<<"; ">>, Key, <<"=\"">>, escape(iolist_to_binary(Value), <<>>), $"]
+ || {Key, Value} <- Params]
+ ].
+
+escape(<<>>, Acc) -> Acc;
+escape(<<$\\,R/bits>>, Acc) -> escape(R, <<Acc/binary,$\\,$\\>>);
+escape(<<$\",R/bits>>, Acc) -> escape(R, <<Acc/binary,$\\,$\">>);
+escape(<<C,R/bits>>, Acc) -> escape(R, <<Acc/binary,C>>).
+
+-ifdef(TEST).
+link_test_() ->
+ Tests = [
+ {<<>>, []},
+ %% Examples from the RFC.
+ {<<"<http://example.com/TheBook/chapter2>; rel=\"previous\"; title=\"previous chapter\"">>, [
+ #{
+ target => <<"http://example.com/TheBook/chapter2">>,
+ rel => <<"previous">>,
+ attributes => [
+ {<<"title">>, <<"previous chapter">>}
+ ]
+ }
+ ]},
+ {<<"</>; rel=\"http://example.net/foo\"">>, [
+ #{
+ target => <<"/">>,
+ rel => <<"http://example.net/foo">>,
+ attributes => []
+ }
+ ]},
+ {<<"</terms>; rel=\"copyright\"; anchor=\"#foo\"">>, [
+ #{
+ target => <<"/terms">>,
+ rel => <<"copyright">>,
+ attributes => [
+ {<<"anchor">>, <<"#foo">>}
+ ]
+ }
+ ]},
+% {<<"</TheBook/chapter2>; rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, "
+% "</TheBook/chapter4>; rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel">>, [
+% %% @todo
+% ]}
+ {<<"<http://example.org/>; rel=\"start http://example.net/relation/other\"">>, [
+ #{
+ target => <<"http://example.org/">>,
+ rel => <<"start http://example.net/relation/other">>,
+ attributes => []
+ }
+ ]},
+ {<<"<https://example.org/>; rel=\"start\", "
+ "<https://example.org/index>; rel=\"index\"">>, [
+ #{
+ target => <<"https://example.org/">>,
+ rel => <<"start">>,
+ attributes => []
+ },
+ #{
+ target => <<"https://example.org/index">>,
+ rel => <<"index">>,
+ attributes => []
+ }
+ ]},
+ {<<"</>; rel=\"previous\"; quoted=\"name=\\\"value\\\"\"">>, [
+ #{
+ target => <<"/">>,
+ rel => <<"previous">>,
+ attributes => [
+ {<<"quoted">>, <<"name=\"value\"">>}
+ ]
+ }
+ ]}
+ ],
+ [{iolist_to_binary(io_lib:format("~0p", [V])),
+ fun() -> R = iolist_to_binary(link(V)) end} || {R, V} <- Tests].
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_mimetypes.erl b/server/_build/default/lib/cowlib/src/cow_mimetypes.erl
new file mode 100644
index 0000000..756e609
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_mimetypes.erl
@@ -0,0 +1,1045 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_mimetypes).
+
+-export([all/1]).
+-export([web/1]).
+
+%% @doc Return the mimetype for any file by looking at its extension.
+
+-spec all(binary()) -> {binary(), binary(), []}.
+all(Path) ->
+ case filename:extension(Path) of
+ <<>> -> {<<"application">>, <<"octet-stream">>, []};
+ %% @todo Convert to string:lowercase on OTP-20+.
+ << $., Ext/binary >> -> all_ext(list_to_binary(string:to_lower(binary_to_list(Ext))))
+ end.
+
+%% @doc Return the mimetype for a Web related file by looking at its extension.
+
+-spec web(binary()) -> {binary(), binary(), []}.
+web(Path) ->
+ case filename:extension(Path) of
+ <<>> -> {<<"application">>, <<"octet-stream">>, []};
+ %% @todo Convert to string:lowercase on OTP-20+.
+ << $., Ext/binary >> -> web_ext(list_to_binary(string:to_lower(binary_to_list(Ext))))
+ end.
+
+%% Internal.
+
+%% GENERATED
+all_ext(<<"123">>) -> {<<"application">>, <<"vnd.lotus-1-2-3">>, []};
+all_ext(<<"3dml">>) -> {<<"text">>, <<"vnd.in3d.3dml">>, []};
+all_ext(<<"3ds">>) -> {<<"image">>, <<"x-3ds">>, []};
+all_ext(<<"3g2">>) -> {<<"video">>, <<"3gpp2">>, []};
+all_ext(<<"3gp">>) -> {<<"video">>, <<"3gpp">>, []};
+all_ext(<<"7z">>) -> {<<"application">>, <<"x-7z-compressed">>, []};
+all_ext(<<"aab">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
+all_ext(<<"aac">>) -> {<<"audio">>, <<"x-aac">>, []};
+all_ext(<<"aam">>) -> {<<"application">>, <<"x-authorware-map">>, []};
+all_ext(<<"aas">>) -> {<<"application">>, <<"x-authorware-seg">>, []};
+all_ext(<<"abw">>) -> {<<"application">>, <<"x-abiword">>, []};
+all_ext(<<"ac">>) -> {<<"application">>, <<"pkix-attr-cert">>, []};
+all_ext(<<"acc">>) -> {<<"application">>, <<"vnd.americandynamics.acc">>, []};
+all_ext(<<"ace">>) -> {<<"application">>, <<"x-ace-compressed">>, []};
+all_ext(<<"acu">>) -> {<<"application">>, <<"vnd.acucobol">>, []};
+all_ext(<<"acutc">>) -> {<<"application">>, <<"vnd.acucorp">>, []};
+all_ext(<<"adp">>) -> {<<"audio">>, <<"adpcm">>, []};
+all_ext(<<"aep">>) -> {<<"application">>, <<"vnd.audiograph">>, []};
+all_ext(<<"afm">>) -> {<<"application">>, <<"x-font-type1">>, []};
+all_ext(<<"afp">>) -> {<<"application">>, <<"vnd.ibm.modcap">>, []};
+all_ext(<<"ahead">>) -> {<<"application">>, <<"vnd.ahead.space">>, []};
+all_ext(<<"ai">>) -> {<<"application">>, <<"postscript">>, []};
+all_ext(<<"aif">>) -> {<<"audio">>, <<"x-aiff">>, []};
+all_ext(<<"aifc">>) -> {<<"audio">>, <<"x-aiff">>, []};
+all_ext(<<"aiff">>) -> {<<"audio">>, <<"x-aiff">>, []};
+all_ext(<<"air">>) -> {<<"application">>, <<"vnd.adobe.air-application-installer-package+zip">>, []};
+all_ext(<<"ait">>) -> {<<"application">>, <<"vnd.dvb.ait">>, []};
+all_ext(<<"ami">>) -> {<<"application">>, <<"vnd.amiga.ami">>, []};
+all_ext(<<"apk">>) -> {<<"application">>, <<"vnd.android.package-archive">>, []};
+all_ext(<<"appcache">>) -> {<<"text">>, <<"cache-manifest">>, []};
+all_ext(<<"application">>) -> {<<"application">>, <<"x-ms-application">>, []};
+all_ext(<<"apr">>) -> {<<"application">>, <<"vnd.lotus-approach">>, []};
+all_ext(<<"arc">>) -> {<<"application">>, <<"x-freearc">>, []};
+all_ext(<<"asc">>) -> {<<"application">>, <<"pgp-signature">>, []};
+all_ext(<<"asf">>) -> {<<"video">>, <<"x-ms-asf">>, []};
+all_ext(<<"asm">>) -> {<<"text">>, <<"x-asm">>, []};
+all_ext(<<"aso">>) -> {<<"application">>, <<"vnd.accpac.simply.aso">>, []};
+all_ext(<<"asx">>) -> {<<"video">>, <<"x-ms-asf">>, []};
+all_ext(<<"atc">>) -> {<<"application">>, <<"vnd.acucorp">>, []};
+all_ext(<<"atom">>) -> {<<"application">>, <<"atom+xml">>, []};
+all_ext(<<"atomcat">>) -> {<<"application">>, <<"atomcat+xml">>, []};
+all_ext(<<"atomsvc">>) -> {<<"application">>, <<"atomsvc+xml">>, []};
+all_ext(<<"atx">>) -> {<<"application">>, <<"vnd.antix.game-component">>, []};
+all_ext(<<"au">>) -> {<<"audio">>, <<"basic">>, []};
+all_ext(<<"avi">>) -> {<<"video">>, <<"x-msvideo">>, []};
+all_ext(<<"aw">>) -> {<<"application">>, <<"applixware">>, []};
+all_ext(<<"azf">>) -> {<<"application">>, <<"vnd.airzip.filesecure.azf">>, []};
+all_ext(<<"azs">>) -> {<<"application">>, <<"vnd.airzip.filesecure.azs">>, []};
+all_ext(<<"azw">>) -> {<<"application">>, <<"vnd.amazon.ebook">>, []};
+all_ext(<<"bat">>) -> {<<"application">>, <<"x-msdownload">>, []};
+all_ext(<<"bcpio">>) -> {<<"application">>, <<"x-bcpio">>, []};
+all_ext(<<"bdf">>) -> {<<"application">>, <<"x-font-bdf">>, []};
+all_ext(<<"bdm">>) -> {<<"application">>, <<"vnd.syncml.dm+wbxml">>, []};
+all_ext(<<"bed">>) -> {<<"application">>, <<"vnd.realvnc.bed">>, []};
+all_ext(<<"bh2">>) -> {<<"application">>, <<"vnd.fujitsu.oasysprs">>, []};
+all_ext(<<"bin">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"blb">>) -> {<<"application">>, <<"x-blorb">>, []};
+all_ext(<<"blorb">>) -> {<<"application">>, <<"x-blorb">>, []};
+all_ext(<<"bmi">>) -> {<<"application">>, <<"vnd.bmi">>, []};
+all_ext(<<"bmp">>) -> {<<"image">>, <<"bmp">>, []};
+all_ext(<<"book">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
+all_ext(<<"box">>) -> {<<"application">>, <<"vnd.previewsystems.box">>, []};
+all_ext(<<"boz">>) -> {<<"application">>, <<"x-bzip2">>, []};
+all_ext(<<"bpk">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"btif">>) -> {<<"image">>, <<"prs.btif">>, []};
+all_ext(<<"bz2">>) -> {<<"application">>, <<"x-bzip2">>, []};
+all_ext(<<"bz">>) -> {<<"application">>, <<"x-bzip">>, []};
+all_ext(<<"c11amc">>) -> {<<"application">>, <<"vnd.cluetrust.cartomobile-config">>, []};
+all_ext(<<"c11amz">>) -> {<<"application">>, <<"vnd.cluetrust.cartomobile-config-pkg">>, []};
+all_ext(<<"c4d">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
+all_ext(<<"c4f">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
+all_ext(<<"c4g">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
+all_ext(<<"c4p">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
+all_ext(<<"c4u">>) -> {<<"application">>, <<"vnd.clonk.c4group">>, []};
+all_ext(<<"cab">>) -> {<<"application">>, <<"vnd.ms-cab-compressed">>, []};
+all_ext(<<"caf">>) -> {<<"audio">>, <<"x-caf">>, []};
+all_ext(<<"cap">>) -> {<<"application">>, <<"vnd.tcpdump.pcap">>, []};
+all_ext(<<"car">>) -> {<<"application">>, <<"vnd.curl.car">>, []};
+all_ext(<<"cat">>) -> {<<"application">>, <<"vnd.ms-pki.seccat">>, []};
+all_ext(<<"cb7">>) -> {<<"application">>, <<"x-cbr">>, []};
+all_ext(<<"cba">>) -> {<<"application">>, <<"x-cbr">>, []};
+all_ext(<<"cbr">>) -> {<<"application">>, <<"x-cbr">>, []};
+all_ext(<<"cbt">>) -> {<<"application">>, <<"x-cbr">>, []};
+all_ext(<<"cbz">>) -> {<<"application">>, <<"x-cbr">>, []};
+all_ext(<<"cct">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"cc">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"ccxml">>) -> {<<"application">>, <<"ccxml+xml">>, []};
+all_ext(<<"cdbcmsg">>) -> {<<"application">>, <<"vnd.contact.cmsg">>, []};
+all_ext(<<"cdf">>) -> {<<"application">>, <<"x-netcdf">>, []};
+all_ext(<<"cdkey">>) -> {<<"application">>, <<"vnd.mediastation.cdkey">>, []};
+all_ext(<<"cdmia">>) -> {<<"application">>, <<"cdmi-capability">>, []};
+all_ext(<<"cdmic">>) -> {<<"application">>, <<"cdmi-container">>, []};
+all_ext(<<"cdmid">>) -> {<<"application">>, <<"cdmi-domain">>, []};
+all_ext(<<"cdmio">>) -> {<<"application">>, <<"cdmi-object">>, []};
+all_ext(<<"cdmiq">>) -> {<<"application">>, <<"cdmi-queue">>, []};
+all_ext(<<"cdx">>) -> {<<"chemical">>, <<"x-cdx">>, []};
+all_ext(<<"cdxml">>) -> {<<"application">>, <<"vnd.chemdraw+xml">>, []};
+all_ext(<<"cdy">>) -> {<<"application">>, <<"vnd.cinderella">>, []};
+all_ext(<<"cer">>) -> {<<"application">>, <<"pkix-cert">>, []};
+all_ext(<<"cfs">>) -> {<<"application">>, <<"x-cfs-compressed">>, []};
+all_ext(<<"cgm">>) -> {<<"image">>, <<"cgm">>, []};
+all_ext(<<"chat">>) -> {<<"application">>, <<"x-chat">>, []};
+all_ext(<<"chm">>) -> {<<"application">>, <<"vnd.ms-htmlhelp">>, []};
+all_ext(<<"chrt">>) -> {<<"application">>, <<"vnd.kde.kchart">>, []};
+all_ext(<<"cif">>) -> {<<"chemical">>, <<"x-cif">>, []};
+all_ext(<<"cii">>) -> {<<"application">>, <<"vnd.anser-web-certificate-issue-initiation">>, []};
+all_ext(<<"cil">>) -> {<<"application">>, <<"vnd.ms-artgalry">>, []};
+all_ext(<<"cla">>) -> {<<"application">>, <<"vnd.claymore">>, []};
+all_ext(<<"class">>) -> {<<"application">>, <<"java-vm">>, []};
+all_ext(<<"clkk">>) -> {<<"application">>, <<"vnd.crick.clicker.keyboard">>, []};
+all_ext(<<"clkp">>) -> {<<"application">>, <<"vnd.crick.clicker.palette">>, []};
+all_ext(<<"clkt">>) -> {<<"application">>, <<"vnd.crick.clicker.template">>, []};
+all_ext(<<"clkw">>) -> {<<"application">>, <<"vnd.crick.clicker.wordbank">>, []};
+all_ext(<<"clkx">>) -> {<<"application">>, <<"vnd.crick.clicker">>, []};
+all_ext(<<"clp">>) -> {<<"application">>, <<"x-msclip">>, []};
+all_ext(<<"cmc">>) -> {<<"application">>, <<"vnd.cosmocaller">>, []};
+all_ext(<<"cmdf">>) -> {<<"chemical">>, <<"x-cmdf">>, []};
+all_ext(<<"cml">>) -> {<<"chemical">>, <<"x-cml">>, []};
+all_ext(<<"cmp">>) -> {<<"application">>, <<"vnd.yellowriver-custom-menu">>, []};
+all_ext(<<"cmx">>) -> {<<"image">>, <<"x-cmx">>, []};
+all_ext(<<"cod">>) -> {<<"application">>, <<"vnd.rim.cod">>, []};
+all_ext(<<"com">>) -> {<<"application">>, <<"x-msdownload">>, []};
+all_ext(<<"conf">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"cpio">>) -> {<<"application">>, <<"x-cpio">>, []};
+all_ext(<<"cpp">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"cpt">>) -> {<<"application">>, <<"mac-compactpro">>, []};
+all_ext(<<"crd">>) -> {<<"application">>, <<"x-mscardfile">>, []};
+all_ext(<<"crl">>) -> {<<"application">>, <<"pkix-crl">>, []};
+all_ext(<<"crt">>) -> {<<"application">>, <<"x-x509-ca-cert">>, []};
+all_ext(<<"cryptonote">>) -> {<<"application">>, <<"vnd.rig.cryptonote">>, []};
+all_ext(<<"csh">>) -> {<<"application">>, <<"x-csh">>, []};
+all_ext(<<"csml">>) -> {<<"chemical">>, <<"x-csml">>, []};
+all_ext(<<"csp">>) -> {<<"application">>, <<"vnd.commonspace">>, []};
+all_ext(<<"css">>) -> {<<"text">>, <<"css">>, []};
+all_ext(<<"cst">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"csv">>) -> {<<"text">>, <<"csv">>, []};
+all_ext(<<"c">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"cu">>) -> {<<"application">>, <<"cu-seeme">>, []};
+all_ext(<<"curl">>) -> {<<"text">>, <<"vnd.curl">>, []};
+all_ext(<<"cww">>) -> {<<"application">>, <<"prs.cww">>, []};
+all_ext(<<"cxt">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"cxx">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"dae">>) -> {<<"model">>, <<"vnd.collada+xml">>, []};
+all_ext(<<"daf">>) -> {<<"application">>, <<"vnd.mobius.daf">>, []};
+all_ext(<<"dart">>) -> {<<"application">>, <<"vnd.dart">>, []};
+all_ext(<<"dataless">>) -> {<<"application">>, <<"vnd.fdsn.seed">>, []};
+all_ext(<<"davmount">>) -> {<<"application">>, <<"davmount+xml">>, []};
+all_ext(<<"dbk">>) -> {<<"application">>, <<"docbook+xml">>, []};
+all_ext(<<"dcr">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"dcurl">>) -> {<<"text">>, <<"vnd.curl.dcurl">>, []};
+all_ext(<<"dd2">>) -> {<<"application">>, <<"vnd.oma.dd2+xml">>, []};
+all_ext(<<"ddd">>) -> {<<"application">>, <<"vnd.fujixerox.ddd">>, []};
+all_ext(<<"deb">>) -> {<<"application">>, <<"x-debian-package">>, []};
+all_ext(<<"def">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"deploy">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"der">>) -> {<<"application">>, <<"x-x509-ca-cert">>, []};
+all_ext(<<"dfac">>) -> {<<"application">>, <<"vnd.dreamfactory">>, []};
+all_ext(<<"dgc">>) -> {<<"application">>, <<"x-dgc-compressed">>, []};
+all_ext(<<"dic">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"dir">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"dis">>) -> {<<"application">>, <<"vnd.mobius.dis">>, []};
+all_ext(<<"dist">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"distz">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"djv">>) -> {<<"image">>, <<"vnd.djvu">>, []};
+all_ext(<<"djvu">>) -> {<<"image">>, <<"vnd.djvu">>, []};
+all_ext(<<"dll">>) -> {<<"application">>, <<"x-msdownload">>, []};
+all_ext(<<"dmg">>) -> {<<"application">>, <<"x-apple-diskimage">>, []};
+all_ext(<<"dmp">>) -> {<<"application">>, <<"vnd.tcpdump.pcap">>, []};
+all_ext(<<"dms">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"dna">>) -> {<<"application">>, <<"vnd.dna">>, []};
+all_ext(<<"doc">>) -> {<<"application">>, <<"msword">>, []};
+all_ext(<<"docm">>) -> {<<"application">>, <<"vnd.ms-word.document.macroenabled.12">>, []};
+all_ext(<<"docx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.wordprocessingml.document">>, []};
+all_ext(<<"dot">>) -> {<<"application">>, <<"msword">>, []};
+all_ext(<<"dotm">>) -> {<<"application">>, <<"vnd.ms-word.template.macroenabled.12">>, []};
+all_ext(<<"dotx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.wordprocessingml.template">>, []};
+all_ext(<<"dp">>) -> {<<"application">>, <<"vnd.osgi.dp">>, []};
+all_ext(<<"dpg">>) -> {<<"application">>, <<"vnd.dpgraph">>, []};
+all_ext(<<"dra">>) -> {<<"audio">>, <<"vnd.dra">>, []};
+all_ext(<<"dsc">>) -> {<<"text">>, <<"prs.lines.tag">>, []};
+all_ext(<<"dssc">>) -> {<<"application">>, <<"dssc+der">>, []};
+all_ext(<<"dtb">>) -> {<<"application">>, <<"x-dtbook+xml">>, []};
+all_ext(<<"dtd">>) -> {<<"application">>, <<"xml-dtd">>, []};
+all_ext(<<"dts">>) -> {<<"audio">>, <<"vnd.dts">>, []};
+all_ext(<<"dtshd">>) -> {<<"audio">>, <<"vnd.dts.hd">>, []};
+all_ext(<<"dump">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"dvb">>) -> {<<"video">>, <<"vnd.dvb.file">>, []};
+all_ext(<<"dvi">>) -> {<<"application">>, <<"x-dvi">>, []};
+all_ext(<<"dwf">>) -> {<<"model">>, <<"vnd.dwf">>, []};
+all_ext(<<"dwg">>) -> {<<"image">>, <<"vnd.dwg">>, []};
+all_ext(<<"dxf">>) -> {<<"image">>, <<"vnd.dxf">>, []};
+all_ext(<<"dxp">>) -> {<<"application">>, <<"vnd.spotfire.dxp">>, []};
+all_ext(<<"dxr">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"ecelp4800">>) -> {<<"audio">>, <<"vnd.nuera.ecelp4800">>, []};
+all_ext(<<"ecelp7470">>) -> {<<"audio">>, <<"vnd.nuera.ecelp7470">>, []};
+all_ext(<<"ecelp9600">>) -> {<<"audio">>, <<"vnd.nuera.ecelp9600">>, []};
+all_ext(<<"ecma">>) -> {<<"application">>, <<"ecmascript">>, []};
+all_ext(<<"edm">>) -> {<<"application">>, <<"vnd.novadigm.edm">>, []};
+all_ext(<<"edx">>) -> {<<"application">>, <<"vnd.novadigm.edx">>, []};
+all_ext(<<"efif">>) -> {<<"application">>, <<"vnd.picsel">>, []};
+all_ext(<<"ei6">>) -> {<<"application">>, <<"vnd.pg.osasli">>, []};
+all_ext(<<"elc">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"emf">>) -> {<<"application">>, <<"x-msmetafile">>, []};
+all_ext(<<"eml">>) -> {<<"message">>, <<"rfc822">>, []};
+all_ext(<<"emma">>) -> {<<"application">>, <<"emma+xml">>, []};
+all_ext(<<"emz">>) -> {<<"application">>, <<"x-msmetafile">>, []};
+all_ext(<<"eol">>) -> {<<"audio">>, <<"vnd.digital-winds">>, []};
+all_ext(<<"eot">>) -> {<<"application">>, <<"vnd.ms-fontobject">>, []};
+all_ext(<<"eps">>) -> {<<"application">>, <<"postscript">>, []};
+all_ext(<<"epub">>) -> {<<"application">>, <<"epub+zip">>, []};
+all_ext(<<"es3">>) -> {<<"application">>, <<"vnd.eszigno3+xml">>, []};
+all_ext(<<"esa">>) -> {<<"application">>, <<"vnd.osgi.subsystem">>, []};
+all_ext(<<"esf">>) -> {<<"application">>, <<"vnd.epson.esf">>, []};
+all_ext(<<"et3">>) -> {<<"application">>, <<"vnd.eszigno3+xml">>, []};
+all_ext(<<"etx">>) -> {<<"text">>, <<"x-setext">>, []};
+all_ext(<<"eva">>) -> {<<"application">>, <<"x-eva">>, []};
+all_ext(<<"evy">>) -> {<<"application">>, <<"x-envoy">>, []};
+all_ext(<<"exe">>) -> {<<"application">>, <<"x-msdownload">>, []};
+all_ext(<<"exi">>) -> {<<"application">>, <<"exi">>, []};
+all_ext(<<"ext">>) -> {<<"application">>, <<"vnd.novadigm.ext">>, []};
+all_ext(<<"ez2">>) -> {<<"application">>, <<"vnd.ezpix-album">>, []};
+all_ext(<<"ez3">>) -> {<<"application">>, <<"vnd.ezpix-package">>, []};
+all_ext(<<"ez">>) -> {<<"application">>, <<"andrew-inset">>, []};
+all_ext(<<"f4v">>) -> {<<"video">>, <<"x-f4v">>, []};
+all_ext(<<"f77">>) -> {<<"text">>, <<"x-fortran">>, []};
+all_ext(<<"f90">>) -> {<<"text">>, <<"x-fortran">>, []};
+all_ext(<<"fbs">>) -> {<<"image">>, <<"vnd.fastbidsheet">>, []};
+all_ext(<<"fcdt">>) -> {<<"application">>, <<"vnd.adobe.formscentral.fcdt">>, []};
+all_ext(<<"fcs">>) -> {<<"application">>, <<"vnd.isac.fcs">>, []};
+all_ext(<<"fdf">>) -> {<<"application">>, <<"vnd.fdf">>, []};
+all_ext(<<"fe_launch">>) -> {<<"application">>, <<"vnd.denovo.fcselayout-link">>, []};
+all_ext(<<"fg5">>) -> {<<"application">>, <<"vnd.fujitsu.oasysgp">>, []};
+all_ext(<<"fgd">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"fh4">>) -> {<<"image">>, <<"x-freehand">>, []};
+all_ext(<<"fh5">>) -> {<<"image">>, <<"x-freehand">>, []};
+all_ext(<<"fh7">>) -> {<<"image">>, <<"x-freehand">>, []};
+all_ext(<<"fhc">>) -> {<<"image">>, <<"x-freehand">>, []};
+all_ext(<<"fh">>) -> {<<"image">>, <<"x-freehand">>, []};
+all_ext(<<"fig">>) -> {<<"application">>, <<"x-xfig">>, []};
+all_ext(<<"flac">>) -> {<<"audio">>, <<"x-flac">>, []};
+all_ext(<<"fli">>) -> {<<"video">>, <<"x-fli">>, []};
+all_ext(<<"flo">>) -> {<<"application">>, <<"vnd.micrografx.flo">>, []};
+all_ext(<<"flv">>) -> {<<"video">>, <<"x-flv">>, []};
+all_ext(<<"flw">>) -> {<<"application">>, <<"vnd.kde.kivio">>, []};
+all_ext(<<"flx">>) -> {<<"text">>, <<"vnd.fmi.flexstor">>, []};
+all_ext(<<"fly">>) -> {<<"text">>, <<"vnd.fly">>, []};
+all_ext(<<"fm">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
+all_ext(<<"fnc">>) -> {<<"application">>, <<"vnd.frogans.fnc">>, []};
+all_ext(<<"for">>) -> {<<"text">>, <<"x-fortran">>, []};
+all_ext(<<"fpx">>) -> {<<"image">>, <<"vnd.fpx">>, []};
+all_ext(<<"frame">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
+all_ext(<<"fsc">>) -> {<<"application">>, <<"vnd.fsc.weblaunch">>, []};
+all_ext(<<"fst">>) -> {<<"image">>, <<"vnd.fst">>, []};
+all_ext(<<"ftc">>) -> {<<"application">>, <<"vnd.fluxtime.clip">>, []};
+all_ext(<<"f">>) -> {<<"text">>, <<"x-fortran">>, []};
+all_ext(<<"fti">>) -> {<<"application">>, <<"vnd.anser-web-funds-transfer-initiation">>, []};
+all_ext(<<"fvt">>) -> {<<"video">>, <<"vnd.fvt">>, []};
+all_ext(<<"fxp">>) -> {<<"application">>, <<"vnd.adobe.fxp">>, []};
+all_ext(<<"fxpl">>) -> {<<"application">>, <<"vnd.adobe.fxp">>, []};
+all_ext(<<"fzs">>) -> {<<"application">>, <<"vnd.fuzzysheet">>, []};
+all_ext(<<"g2w">>) -> {<<"application">>, <<"vnd.geoplan">>, []};
+all_ext(<<"g3">>) -> {<<"image">>, <<"g3fax">>, []};
+all_ext(<<"g3w">>) -> {<<"application">>, <<"vnd.geospace">>, []};
+all_ext(<<"gac">>) -> {<<"application">>, <<"vnd.groove-account">>, []};
+all_ext(<<"gam">>) -> {<<"application">>, <<"x-tads">>, []};
+all_ext(<<"gbr">>) -> {<<"application">>, <<"rpki-ghostbusters">>, []};
+all_ext(<<"gca">>) -> {<<"application">>, <<"x-gca-compressed">>, []};
+all_ext(<<"gdl">>) -> {<<"model">>, <<"vnd.gdl">>, []};
+all_ext(<<"geo">>) -> {<<"application">>, <<"vnd.dynageo">>, []};
+all_ext(<<"gex">>) -> {<<"application">>, <<"vnd.geometry-explorer">>, []};
+all_ext(<<"ggb">>) -> {<<"application">>, <<"vnd.geogebra.file">>, []};
+all_ext(<<"ggt">>) -> {<<"application">>, <<"vnd.geogebra.tool">>, []};
+all_ext(<<"ghf">>) -> {<<"application">>, <<"vnd.groove-help">>, []};
+all_ext(<<"gif">>) -> {<<"image">>, <<"gif">>, []};
+all_ext(<<"gim">>) -> {<<"application">>, <<"vnd.groove-identity-message">>, []};
+all_ext(<<"gml">>) -> {<<"application">>, <<"gml+xml">>, []};
+all_ext(<<"gmx">>) -> {<<"application">>, <<"vnd.gmx">>, []};
+all_ext(<<"gnumeric">>) -> {<<"application">>, <<"x-gnumeric">>, []};
+all_ext(<<"gph">>) -> {<<"application">>, <<"vnd.flographit">>, []};
+all_ext(<<"gpx">>) -> {<<"application">>, <<"gpx+xml">>, []};
+all_ext(<<"gqf">>) -> {<<"application">>, <<"vnd.grafeq">>, []};
+all_ext(<<"gqs">>) -> {<<"application">>, <<"vnd.grafeq">>, []};
+all_ext(<<"gram">>) -> {<<"application">>, <<"srgs">>, []};
+all_ext(<<"gramps">>) -> {<<"application">>, <<"x-gramps-xml">>, []};
+all_ext(<<"gre">>) -> {<<"application">>, <<"vnd.geometry-explorer">>, []};
+all_ext(<<"grv">>) -> {<<"application">>, <<"vnd.groove-injector">>, []};
+all_ext(<<"grxml">>) -> {<<"application">>, <<"srgs+xml">>, []};
+all_ext(<<"gsf">>) -> {<<"application">>, <<"x-font-ghostscript">>, []};
+all_ext(<<"gtar">>) -> {<<"application">>, <<"x-gtar">>, []};
+all_ext(<<"gtm">>) -> {<<"application">>, <<"vnd.groove-tool-message">>, []};
+all_ext(<<"gtw">>) -> {<<"model">>, <<"vnd.gtw">>, []};
+all_ext(<<"gv">>) -> {<<"text">>, <<"vnd.graphviz">>, []};
+all_ext(<<"gxf">>) -> {<<"application">>, <<"gxf">>, []};
+all_ext(<<"gxt">>) -> {<<"application">>, <<"vnd.geonext">>, []};
+all_ext(<<"h261">>) -> {<<"video">>, <<"h261">>, []};
+all_ext(<<"h263">>) -> {<<"video">>, <<"h263">>, []};
+all_ext(<<"h264">>) -> {<<"video">>, <<"h264">>, []};
+all_ext(<<"hal">>) -> {<<"application">>, <<"vnd.hal+xml">>, []};
+all_ext(<<"hbci">>) -> {<<"application">>, <<"vnd.hbci">>, []};
+all_ext(<<"hdf">>) -> {<<"application">>, <<"x-hdf">>, []};
+all_ext(<<"hh">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"hlp">>) -> {<<"application">>, <<"winhlp">>, []};
+all_ext(<<"hpgl">>) -> {<<"application">>, <<"vnd.hp-hpgl">>, []};
+all_ext(<<"hpid">>) -> {<<"application">>, <<"vnd.hp-hpid">>, []};
+all_ext(<<"hps">>) -> {<<"application">>, <<"vnd.hp-hps">>, []};
+all_ext(<<"hqx">>) -> {<<"application">>, <<"mac-binhex40">>, []};
+all_ext(<<"h">>) -> {<<"text">>, <<"x-c">>, []};
+all_ext(<<"htke">>) -> {<<"application">>, <<"vnd.kenameaapp">>, []};
+all_ext(<<"html">>) -> {<<"text">>, <<"html">>, []};
+all_ext(<<"htm">>) -> {<<"text">>, <<"html">>, []};
+all_ext(<<"hvd">>) -> {<<"application">>, <<"vnd.yamaha.hv-dic">>, []};
+all_ext(<<"hvp">>) -> {<<"application">>, <<"vnd.yamaha.hv-voice">>, []};
+all_ext(<<"hvs">>) -> {<<"application">>, <<"vnd.yamaha.hv-script">>, []};
+all_ext(<<"i2g">>) -> {<<"application">>, <<"vnd.intergeo">>, []};
+all_ext(<<"icc">>) -> {<<"application">>, <<"vnd.iccprofile">>, []};
+all_ext(<<"ice">>) -> {<<"x-conference">>, <<"x-cooltalk">>, []};
+all_ext(<<"icm">>) -> {<<"application">>, <<"vnd.iccprofile">>, []};
+all_ext(<<"ico">>) -> {<<"image">>, <<"x-icon">>, []};
+all_ext(<<"ics">>) -> {<<"text">>, <<"calendar">>, []};
+all_ext(<<"ief">>) -> {<<"image">>, <<"ief">>, []};
+all_ext(<<"ifb">>) -> {<<"text">>, <<"calendar">>, []};
+all_ext(<<"ifm">>) -> {<<"application">>, <<"vnd.shana.informed.formdata">>, []};
+all_ext(<<"iges">>) -> {<<"model">>, <<"iges">>, []};
+all_ext(<<"igl">>) -> {<<"application">>, <<"vnd.igloader">>, []};
+all_ext(<<"igm">>) -> {<<"application">>, <<"vnd.insors.igm">>, []};
+all_ext(<<"igs">>) -> {<<"model">>, <<"iges">>, []};
+all_ext(<<"igx">>) -> {<<"application">>, <<"vnd.micrografx.igx">>, []};
+all_ext(<<"iif">>) -> {<<"application">>, <<"vnd.shana.informed.interchange">>, []};
+all_ext(<<"imp">>) -> {<<"application">>, <<"vnd.accpac.simply.imp">>, []};
+all_ext(<<"ims">>) -> {<<"application">>, <<"vnd.ms-ims">>, []};
+all_ext(<<"ink">>) -> {<<"application">>, <<"inkml+xml">>, []};
+all_ext(<<"inkml">>) -> {<<"application">>, <<"inkml+xml">>, []};
+all_ext(<<"install">>) -> {<<"application">>, <<"x-install-instructions">>, []};
+all_ext(<<"in">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"iota">>) -> {<<"application">>, <<"vnd.astraea-software.iota">>, []};
+all_ext(<<"ipfix">>) -> {<<"application">>, <<"ipfix">>, []};
+all_ext(<<"ipk">>) -> {<<"application">>, <<"vnd.shana.informed.package">>, []};
+all_ext(<<"irm">>) -> {<<"application">>, <<"vnd.ibm.rights-management">>, []};
+all_ext(<<"irp">>) -> {<<"application">>, <<"vnd.irepository.package+xml">>, []};
+all_ext(<<"iso">>) -> {<<"application">>, <<"x-iso9660-image">>, []};
+all_ext(<<"itp">>) -> {<<"application">>, <<"vnd.shana.informed.formtemplate">>, []};
+all_ext(<<"ivp">>) -> {<<"application">>, <<"vnd.immervision-ivp">>, []};
+all_ext(<<"ivu">>) -> {<<"application">>, <<"vnd.immervision-ivu">>, []};
+all_ext(<<"jad">>) -> {<<"text">>, <<"vnd.sun.j2me.app-descriptor">>, []};
+all_ext(<<"jam">>) -> {<<"application">>, <<"vnd.jam">>, []};
+all_ext(<<"jar">>) -> {<<"application">>, <<"java-archive">>, []};
+all_ext(<<"java">>) -> {<<"text">>, <<"x-java-source">>, []};
+all_ext(<<"jisp">>) -> {<<"application">>, <<"vnd.jisp">>, []};
+all_ext(<<"jlt">>) -> {<<"application">>, <<"vnd.hp-jlyt">>, []};
+all_ext(<<"jnlp">>) -> {<<"application">>, <<"x-java-jnlp-file">>, []};
+all_ext(<<"joda">>) -> {<<"application">>, <<"vnd.joost.joda-archive">>, []};
+all_ext(<<"jpeg">>) -> {<<"image">>, <<"jpeg">>, []};
+all_ext(<<"jpe">>) -> {<<"image">>, <<"jpeg">>, []};
+all_ext(<<"jpg">>) -> {<<"image">>, <<"jpeg">>, []};
+all_ext(<<"jpgm">>) -> {<<"video">>, <<"jpm">>, []};
+all_ext(<<"jpgv">>) -> {<<"video">>, <<"jpeg">>, []};
+all_ext(<<"jpm">>) -> {<<"video">>, <<"jpm">>, []};
+all_ext(<<"js">>) -> {<<"application">>, <<"javascript">>, []};
+all_ext(<<"json">>) -> {<<"application">>, <<"json">>, []};
+all_ext(<<"jsonml">>) -> {<<"application">>, <<"jsonml+json">>, []};
+all_ext(<<"kar">>) -> {<<"audio">>, <<"midi">>, []};
+all_ext(<<"karbon">>) -> {<<"application">>, <<"vnd.kde.karbon">>, []};
+all_ext(<<"kfo">>) -> {<<"application">>, <<"vnd.kde.kformula">>, []};
+all_ext(<<"kia">>) -> {<<"application">>, <<"vnd.kidspiration">>, []};
+all_ext(<<"kml">>) -> {<<"application">>, <<"vnd.google-earth.kml+xml">>, []};
+all_ext(<<"kmz">>) -> {<<"application">>, <<"vnd.google-earth.kmz">>, []};
+all_ext(<<"kne">>) -> {<<"application">>, <<"vnd.kinar">>, []};
+all_ext(<<"knp">>) -> {<<"application">>, <<"vnd.kinar">>, []};
+all_ext(<<"kon">>) -> {<<"application">>, <<"vnd.kde.kontour">>, []};
+all_ext(<<"kpr">>) -> {<<"application">>, <<"vnd.kde.kpresenter">>, []};
+all_ext(<<"kpt">>) -> {<<"application">>, <<"vnd.kde.kpresenter">>, []};
+all_ext(<<"kpxx">>) -> {<<"application">>, <<"vnd.ds-keypoint">>, []};
+all_ext(<<"ksp">>) -> {<<"application">>, <<"vnd.kde.kspread">>, []};
+all_ext(<<"ktr">>) -> {<<"application">>, <<"vnd.kahootz">>, []};
+all_ext(<<"ktx">>) -> {<<"image">>, <<"ktx">>, []};
+all_ext(<<"ktz">>) -> {<<"application">>, <<"vnd.kahootz">>, []};
+all_ext(<<"kwd">>) -> {<<"application">>, <<"vnd.kde.kword">>, []};
+all_ext(<<"kwt">>) -> {<<"application">>, <<"vnd.kde.kword">>, []};
+all_ext(<<"lasxml">>) -> {<<"application">>, <<"vnd.las.las+xml">>, []};
+all_ext(<<"latex">>) -> {<<"application">>, <<"x-latex">>, []};
+all_ext(<<"lbd">>) -> {<<"application">>, <<"vnd.llamagraphics.life-balance.desktop">>, []};
+all_ext(<<"lbe">>) -> {<<"application">>, <<"vnd.llamagraphics.life-balance.exchange+xml">>, []};
+all_ext(<<"les">>) -> {<<"application">>, <<"vnd.hhe.lesson-player">>, []};
+all_ext(<<"lha">>) -> {<<"application">>, <<"x-lzh-compressed">>, []};
+all_ext(<<"link66">>) -> {<<"application">>, <<"vnd.route66.link66+xml">>, []};
+all_ext(<<"list3820">>) -> {<<"application">>, <<"vnd.ibm.modcap">>, []};
+all_ext(<<"listafp">>) -> {<<"application">>, <<"vnd.ibm.modcap">>, []};
+all_ext(<<"list">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"lnk">>) -> {<<"application">>, <<"x-ms-shortcut">>, []};
+all_ext(<<"log">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"lostxml">>) -> {<<"application">>, <<"lost+xml">>, []};
+all_ext(<<"lrf">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"lrm">>) -> {<<"application">>, <<"vnd.ms-lrm">>, []};
+all_ext(<<"ltf">>) -> {<<"application">>, <<"vnd.frogans.ltf">>, []};
+all_ext(<<"lvp">>) -> {<<"audio">>, <<"vnd.lucent.voice">>, []};
+all_ext(<<"lwp">>) -> {<<"application">>, <<"vnd.lotus-wordpro">>, []};
+all_ext(<<"lzh">>) -> {<<"application">>, <<"x-lzh-compressed">>, []};
+all_ext(<<"m13">>) -> {<<"application">>, <<"x-msmediaview">>, []};
+all_ext(<<"m14">>) -> {<<"application">>, <<"x-msmediaview">>, []};
+all_ext(<<"m1v">>) -> {<<"video">>, <<"mpeg">>, []};
+all_ext(<<"m21">>) -> {<<"application">>, <<"mp21">>, []};
+all_ext(<<"m2a">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"m2v">>) -> {<<"video">>, <<"mpeg">>, []};
+all_ext(<<"m3a">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"m3u8">>) -> {<<"application">>, <<"vnd.apple.mpegurl">>, []};
+all_ext(<<"m3u">>) -> {<<"audio">>, <<"x-mpegurl">>, []};
+all_ext(<<"m4a">>) -> {<<"audio">>, <<"mp4">>, []};
+all_ext(<<"m4u">>) -> {<<"video">>, <<"vnd.mpegurl">>, []};
+all_ext(<<"m4v">>) -> {<<"video">>, <<"x-m4v">>, []};
+all_ext(<<"ma">>) -> {<<"application">>, <<"mathematica">>, []};
+all_ext(<<"mads">>) -> {<<"application">>, <<"mads+xml">>, []};
+all_ext(<<"mag">>) -> {<<"application">>, <<"vnd.ecowin.chart">>, []};
+all_ext(<<"maker">>) -> {<<"application">>, <<"vnd.framemaker">>, []};
+all_ext(<<"man">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"mar">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"mathml">>) -> {<<"application">>, <<"mathml+xml">>, []};
+all_ext(<<"mb">>) -> {<<"application">>, <<"mathematica">>, []};
+all_ext(<<"mbk">>) -> {<<"application">>, <<"vnd.mobius.mbk">>, []};
+all_ext(<<"mbox">>) -> {<<"application">>, <<"mbox">>, []};
+all_ext(<<"mc1">>) -> {<<"application">>, <<"vnd.medcalcdata">>, []};
+all_ext(<<"mcd">>) -> {<<"application">>, <<"vnd.mcd">>, []};
+all_ext(<<"mcurl">>) -> {<<"text">>, <<"vnd.curl.mcurl">>, []};
+all_ext(<<"mdb">>) -> {<<"application">>, <<"x-msaccess">>, []};
+all_ext(<<"mdi">>) -> {<<"image">>, <<"vnd.ms-modi">>, []};
+all_ext(<<"mesh">>) -> {<<"model">>, <<"mesh">>, []};
+all_ext(<<"meta4">>) -> {<<"application">>, <<"metalink4+xml">>, []};
+all_ext(<<"metalink">>) -> {<<"application">>, <<"metalink+xml">>, []};
+all_ext(<<"me">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"mets">>) -> {<<"application">>, <<"mets+xml">>, []};
+all_ext(<<"mfm">>) -> {<<"application">>, <<"vnd.mfmp">>, []};
+all_ext(<<"mft">>) -> {<<"application">>, <<"rpki-manifest">>, []};
+all_ext(<<"mgp">>) -> {<<"application">>, <<"vnd.osgeo.mapguide.package">>, []};
+all_ext(<<"mgz">>) -> {<<"application">>, <<"vnd.proteus.magazine">>, []};
+all_ext(<<"mid">>) -> {<<"audio">>, <<"midi">>, []};
+all_ext(<<"midi">>) -> {<<"audio">>, <<"midi">>, []};
+all_ext(<<"mie">>) -> {<<"application">>, <<"x-mie">>, []};
+all_ext(<<"mif">>) -> {<<"application">>, <<"vnd.mif">>, []};
+all_ext(<<"mime">>) -> {<<"message">>, <<"rfc822">>, []};
+all_ext(<<"mj2">>) -> {<<"video">>, <<"mj2">>, []};
+all_ext(<<"mjp2">>) -> {<<"video">>, <<"mj2">>, []};
+all_ext(<<"mk3d">>) -> {<<"video">>, <<"x-matroska">>, []};
+all_ext(<<"mka">>) -> {<<"audio">>, <<"x-matroska">>, []};
+all_ext(<<"mks">>) -> {<<"video">>, <<"x-matroska">>, []};
+all_ext(<<"mkv">>) -> {<<"video">>, <<"x-matroska">>, []};
+all_ext(<<"mlp">>) -> {<<"application">>, <<"vnd.dolby.mlp">>, []};
+all_ext(<<"mmd">>) -> {<<"application">>, <<"vnd.chipnuts.karaoke-mmd">>, []};
+all_ext(<<"mmf">>) -> {<<"application">>, <<"vnd.smaf">>, []};
+all_ext(<<"mmr">>) -> {<<"image">>, <<"vnd.fujixerox.edmics-mmr">>, []};
+all_ext(<<"mng">>) -> {<<"video">>, <<"x-mng">>, []};
+all_ext(<<"mny">>) -> {<<"application">>, <<"x-msmoney">>, []};
+all_ext(<<"mobi">>) -> {<<"application">>, <<"x-mobipocket-ebook">>, []};
+all_ext(<<"mods">>) -> {<<"application">>, <<"mods+xml">>, []};
+all_ext(<<"movie">>) -> {<<"video">>, <<"x-sgi-movie">>, []};
+all_ext(<<"mov">>) -> {<<"video">>, <<"quicktime">>, []};
+all_ext(<<"mp21">>) -> {<<"application">>, <<"mp21">>, []};
+all_ext(<<"mp2a">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"mp2">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"mp3">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"mp4a">>) -> {<<"audio">>, <<"mp4">>, []};
+all_ext(<<"mp4s">>) -> {<<"application">>, <<"mp4">>, []};
+all_ext(<<"mp4">>) -> {<<"video">>, <<"mp4">>, []};
+all_ext(<<"mp4v">>) -> {<<"video">>, <<"mp4">>, []};
+all_ext(<<"mpc">>) -> {<<"application">>, <<"vnd.mophun.certificate">>, []};
+all_ext(<<"mpeg">>) -> {<<"video">>, <<"mpeg">>, []};
+all_ext(<<"mpe">>) -> {<<"video">>, <<"mpeg">>, []};
+all_ext(<<"mpg4">>) -> {<<"video">>, <<"mp4">>, []};
+all_ext(<<"mpga">>) -> {<<"audio">>, <<"mpeg">>, []};
+all_ext(<<"mpg">>) -> {<<"video">>, <<"mpeg">>, []};
+all_ext(<<"mpkg">>) -> {<<"application">>, <<"vnd.apple.installer+xml">>, []};
+all_ext(<<"mpm">>) -> {<<"application">>, <<"vnd.blueice.multipass">>, []};
+all_ext(<<"mpn">>) -> {<<"application">>, <<"vnd.mophun.application">>, []};
+all_ext(<<"mpp">>) -> {<<"application">>, <<"vnd.ms-project">>, []};
+all_ext(<<"mpt">>) -> {<<"application">>, <<"vnd.ms-project">>, []};
+all_ext(<<"mpy">>) -> {<<"application">>, <<"vnd.ibm.minipay">>, []};
+all_ext(<<"mqy">>) -> {<<"application">>, <<"vnd.mobius.mqy">>, []};
+all_ext(<<"mrc">>) -> {<<"application">>, <<"marc">>, []};
+all_ext(<<"mrcx">>) -> {<<"application">>, <<"marcxml+xml">>, []};
+all_ext(<<"mscml">>) -> {<<"application">>, <<"mediaservercontrol+xml">>, []};
+all_ext(<<"mseed">>) -> {<<"application">>, <<"vnd.fdsn.mseed">>, []};
+all_ext(<<"mseq">>) -> {<<"application">>, <<"vnd.mseq">>, []};
+all_ext(<<"msf">>) -> {<<"application">>, <<"vnd.epson.msf">>, []};
+all_ext(<<"msh">>) -> {<<"model">>, <<"mesh">>, []};
+all_ext(<<"msi">>) -> {<<"application">>, <<"x-msdownload">>, []};
+all_ext(<<"msl">>) -> {<<"application">>, <<"vnd.mobius.msl">>, []};
+all_ext(<<"ms">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"msty">>) -> {<<"application">>, <<"vnd.muvee.style">>, []};
+all_ext(<<"mts">>) -> {<<"model">>, <<"vnd.mts">>, []};
+all_ext(<<"mus">>) -> {<<"application">>, <<"vnd.musician">>, []};
+all_ext(<<"musicxml">>) -> {<<"application">>, <<"vnd.recordare.musicxml+xml">>, []};
+all_ext(<<"mvb">>) -> {<<"application">>, <<"x-msmediaview">>, []};
+all_ext(<<"mwf">>) -> {<<"application">>, <<"vnd.mfer">>, []};
+all_ext(<<"mxf">>) -> {<<"application">>, <<"mxf">>, []};
+all_ext(<<"mxl">>) -> {<<"application">>, <<"vnd.recordare.musicxml">>, []};
+all_ext(<<"mxml">>) -> {<<"application">>, <<"xv+xml">>, []};
+all_ext(<<"mxs">>) -> {<<"application">>, <<"vnd.triscape.mxs">>, []};
+all_ext(<<"mxu">>) -> {<<"video">>, <<"vnd.mpegurl">>, []};
+all_ext(<<"n3">>) -> {<<"text">>, <<"n3">>, []};
+all_ext(<<"nb">>) -> {<<"application">>, <<"mathematica">>, []};
+all_ext(<<"nbp">>) -> {<<"application">>, <<"vnd.wolfram.player">>, []};
+all_ext(<<"nc">>) -> {<<"application">>, <<"x-netcdf">>, []};
+all_ext(<<"ncx">>) -> {<<"application">>, <<"x-dtbncx+xml">>, []};
+all_ext(<<"nfo">>) -> {<<"text">>, <<"x-nfo">>, []};
+all_ext(<<"n-gage">>) -> {<<"application">>, <<"vnd.nokia.n-gage.symbian.install">>, []};
+all_ext(<<"ngdat">>) -> {<<"application">>, <<"vnd.nokia.n-gage.data">>, []};
+all_ext(<<"nitf">>) -> {<<"application">>, <<"vnd.nitf">>, []};
+all_ext(<<"nlu">>) -> {<<"application">>, <<"vnd.neurolanguage.nlu">>, []};
+all_ext(<<"nml">>) -> {<<"application">>, <<"vnd.enliven">>, []};
+all_ext(<<"nnd">>) -> {<<"application">>, <<"vnd.noblenet-directory">>, []};
+all_ext(<<"nns">>) -> {<<"application">>, <<"vnd.noblenet-sealer">>, []};
+all_ext(<<"nnw">>) -> {<<"application">>, <<"vnd.noblenet-web">>, []};
+all_ext(<<"npx">>) -> {<<"image">>, <<"vnd.net-fpx">>, []};
+all_ext(<<"nsc">>) -> {<<"application">>, <<"x-conference">>, []};
+all_ext(<<"nsf">>) -> {<<"application">>, <<"vnd.lotus-notes">>, []};
+all_ext(<<"ntf">>) -> {<<"application">>, <<"vnd.nitf">>, []};
+all_ext(<<"nzb">>) -> {<<"application">>, <<"x-nzb">>, []};
+all_ext(<<"oa2">>) -> {<<"application">>, <<"vnd.fujitsu.oasys2">>, []};
+all_ext(<<"oa3">>) -> {<<"application">>, <<"vnd.fujitsu.oasys3">>, []};
+all_ext(<<"oas">>) -> {<<"application">>, <<"vnd.fujitsu.oasys">>, []};
+all_ext(<<"obd">>) -> {<<"application">>, <<"x-msbinder">>, []};
+all_ext(<<"obj">>) -> {<<"application">>, <<"x-tgif">>, []};
+all_ext(<<"oda">>) -> {<<"application">>, <<"oda">>, []};
+all_ext(<<"odb">>) -> {<<"application">>, <<"vnd.oasis.opendocument.database">>, []};
+all_ext(<<"odc">>) -> {<<"application">>, <<"vnd.oasis.opendocument.chart">>, []};
+all_ext(<<"odf">>) -> {<<"application">>, <<"vnd.oasis.opendocument.formula">>, []};
+all_ext(<<"odft">>) -> {<<"application">>, <<"vnd.oasis.opendocument.formula-template">>, []};
+all_ext(<<"odg">>) -> {<<"application">>, <<"vnd.oasis.opendocument.graphics">>, []};
+all_ext(<<"odi">>) -> {<<"application">>, <<"vnd.oasis.opendocument.image">>, []};
+all_ext(<<"odm">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text-master">>, []};
+all_ext(<<"odp">>) -> {<<"application">>, <<"vnd.oasis.opendocument.presentation">>, []};
+all_ext(<<"ods">>) -> {<<"application">>, <<"vnd.oasis.opendocument.spreadsheet">>, []};
+all_ext(<<"odt">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text">>, []};
+all_ext(<<"oga">>) -> {<<"audio">>, <<"ogg">>, []};
+all_ext(<<"ogg">>) -> {<<"audio">>, <<"ogg">>, []};
+all_ext(<<"ogv">>) -> {<<"video">>, <<"ogg">>, []};
+all_ext(<<"ogx">>) -> {<<"application">>, <<"ogg">>, []};
+all_ext(<<"omdoc">>) -> {<<"application">>, <<"omdoc+xml">>, []};
+all_ext(<<"onepkg">>) -> {<<"application">>, <<"onenote">>, []};
+all_ext(<<"onetmp">>) -> {<<"application">>, <<"onenote">>, []};
+all_ext(<<"onetoc2">>) -> {<<"application">>, <<"onenote">>, []};
+all_ext(<<"onetoc">>) -> {<<"application">>, <<"onenote">>, []};
+all_ext(<<"opf">>) -> {<<"application">>, <<"oebps-package+xml">>, []};
+all_ext(<<"opml">>) -> {<<"text">>, <<"x-opml">>, []};
+all_ext(<<"oprc">>) -> {<<"application">>, <<"vnd.palm">>, []};
+all_ext(<<"org">>) -> {<<"application">>, <<"vnd.lotus-organizer">>, []};
+all_ext(<<"osf">>) -> {<<"application">>, <<"vnd.yamaha.openscoreformat">>, []};
+all_ext(<<"osfpvg">>) -> {<<"application">>, <<"vnd.yamaha.openscoreformat.osfpvg+xml">>, []};
+all_ext(<<"otc">>) -> {<<"application">>, <<"vnd.oasis.opendocument.chart-template">>, []};
+all_ext(<<"otf">>) -> {<<"font">>, <<"otf">>, []};
+all_ext(<<"otg">>) -> {<<"application">>, <<"vnd.oasis.opendocument.graphics-template">>, []};
+all_ext(<<"oth">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text-web">>, []};
+all_ext(<<"oti">>) -> {<<"application">>, <<"vnd.oasis.opendocument.image-template">>, []};
+all_ext(<<"otp">>) -> {<<"application">>, <<"vnd.oasis.opendocument.presentation-template">>, []};
+all_ext(<<"ots">>) -> {<<"application">>, <<"vnd.oasis.opendocument.spreadsheet-template">>, []};
+all_ext(<<"ott">>) -> {<<"application">>, <<"vnd.oasis.opendocument.text-template">>, []};
+all_ext(<<"oxps">>) -> {<<"application">>, <<"oxps">>, []};
+all_ext(<<"oxt">>) -> {<<"application">>, <<"vnd.openofficeorg.extension">>, []};
+all_ext(<<"p10">>) -> {<<"application">>, <<"pkcs10">>, []};
+all_ext(<<"p12">>) -> {<<"application">>, <<"x-pkcs12">>, []};
+all_ext(<<"p7b">>) -> {<<"application">>, <<"x-pkcs7-certificates">>, []};
+all_ext(<<"p7c">>) -> {<<"application">>, <<"pkcs7-mime">>, []};
+all_ext(<<"p7m">>) -> {<<"application">>, <<"pkcs7-mime">>, []};
+all_ext(<<"p7r">>) -> {<<"application">>, <<"x-pkcs7-certreqresp">>, []};
+all_ext(<<"p7s">>) -> {<<"application">>, <<"pkcs7-signature">>, []};
+all_ext(<<"p8">>) -> {<<"application">>, <<"pkcs8">>, []};
+all_ext(<<"pas">>) -> {<<"text">>, <<"x-pascal">>, []};
+all_ext(<<"paw">>) -> {<<"application">>, <<"vnd.pawaafile">>, []};
+all_ext(<<"pbd">>) -> {<<"application">>, <<"vnd.powerbuilder6">>, []};
+all_ext(<<"pbm">>) -> {<<"image">>, <<"x-portable-bitmap">>, []};
+all_ext(<<"pcap">>) -> {<<"application">>, <<"vnd.tcpdump.pcap">>, []};
+all_ext(<<"pcf">>) -> {<<"application">>, <<"x-font-pcf">>, []};
+all_ext(<<"pcl">>) -> {<<"application">>, <<"vnd.hp-pcl">>, []};
+all_ext(<<"pclxl">>) -> {<<"application">>, <<"vnd.hp-pclxl">>, []};
+all_ext(<<"pct">>) -> {<<"image">>, <<"x-pict">>, []};
+all_ext(<<"pcurl">>) -> {<<"application">>, <<"vnd.curl.pcurl">>, []};
+all_ext(<<"pcx">>) -> {<<"image">>, <<"x-pcx">>, []};
+all_ext(<<"pdb">>) -> {<<"application">>, <<"vnd.palm">>, []};
+all_ext(<<"pdf">>) -> {<<"application">>, <<"pdf">>, []};
+all_ext(<<"pfa">>) -> {<<"application">>, <<"x-font-type1">>, []};
+all_ext(<<"pfb">>) -> {<<"application">>, <<"x-font-type1">>, []};
+all_ext(<<"pfm">>) -> {<<"application">>, <<"x-font-type1">>, []};
+all_ext(<<"pfr">>) -> {<<"application">>, <<"font-tdpfr">>, []};
+all_ext(<<"pfx">>) -> {<<"application">>, <<"x-pkcs12">>, []};
+all_ext(<<"pgm">>) -> {<<"image">>, <<"x-portable-graymap">>, []};
+all_ext(<<"pgn">>) -> {<<"application">>, <<"x-chess-pgn">>, []};
+all_ext(<<"pgp">>) -> {<<"application">>, <<"pgp-encrypted">>, []};
+all_ext(<<"pic">>) -> {<<"image">>, <<"x-pict">>, []};
+all_ext(<<"pkg">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"pki">>) -> {<<"application">>, <<"pkixcmp">>, []};
+all_ext(<<"pkipath">>) -> {<<"application">>, <<"pkix-pkipath">>, []};
+all_ext(<<"plb">>) -> {<<"application">>, <<"vnd.3gpp.pic-bw-large">>, []};
+all_ext(<<"plc">>) -> {<<"application">>, <<"vnd.mobius.plc">>, []};
+all_ext(<<"plf">>) -> {<<"application">>, <<"vnd.pocketlearn">>, []};
+all_ext(<<"pls">>) -> {<<"application">>, <<"pls+xml">>, []};
+all_ext(<<"pml">>) -> {<<"application">>, <<"vnd.ctc-posml">>, []};
+all_ext(<<"png">>) -> {<<"image">>, <<"png">>, []};
+all_ext(<<"pnm">>) -> {<<"image">>, <<"x-portable-anymap">>, []};
+all_ext(<<"portpkg">>) -> {<<"application">>, <<"vnd.macports.portpkg">>, []};
+all_ext(<<"pot">>) -> {<<"application">>, <<"vnd.ms-powerpoint">>, []};
+all_ext(<<"potm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.template.macroenabled.12">>, []};
+all_ext(<<"potx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.template">>, []};
+all_ext(<<"ppam">>) -> {<<"application">>, <<"vnd.ms-powerpoint.addin.macroenabled.12">>, []};
+all_ext(<<"ppd">>) -> {<<"application">>, <<"vnd.cups-ppd">>, []};
+all_ext(<<"ppm">>) -> {<<"image">>, <<"x-portable-pixmap">>, []};
+all_ext(<<"pps">>) -> {<<"application">>, <<"vnd.ms-powerpoint">>, []};
+all_ext(<<"ppsm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.slideshow.macroenabled.12">>, []};
+all_ext(<<"ppsx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.slideshow">>, []};
+all_ext(<<"ppt">>) -> {<<"application">>, <<"vnd.ms-powerpoint">>, []};
+all_ext(<<"pptm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.presentation.macroenabled.12">>, []};
+all_ext(<<"pptx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.presentation">>, []};
+all_ext(<<"pqa">>) -> {<<"application">>, <<"vnd.palm">>, []};
+all_ext(<<"prc">>) -> {<<"application">>, <<"x-mobipocket-ebook">>, []};
+all_ext(<<"pre">>) -> {<<"application">>, <<"vnd.lotus-freelance">>, []};
+all_ext(<<"prf">>) -> {<<"application">>, <<"pics-rules">>, []};
+all_ext(<<"ps">>) -> {<<"application">>, <<"postscript">>, []};
+all_ext(<<"psb">>) -> {<<"application">>, <<"vnd.3gpp.pic-bw-small">>, []};
+all_ext(<<"psd">>) -> {<<"image">>, <<"vnd.adobe.photoshop">>, []};
+all_ext(<<"psf">>) -> {<<"application">>, <<"x-font-linux-psf">>, []};
+all_ext(<<"pskcxml">>) -> {<<"application">>, <<"pskc+xml">>, []};
+all_ext(<<"p">>) -> {<<"text">>, <<"x-pascal">>, []};
+all_ext(<<"ptid">>) -> {<<"application">>, <<"vnd.pvi.ptid1">>, []};
+all_ext(<<"pub">>) -> {<<"application">>, <<"x-mspublisher">>, []};
+all_ext(<<"pvb">>) -> {<<"application">>, <<"vnd.3gpp.pic-bw-var">>, []};
+all_ext(<<"pwn">>) -> {<<"application">>, <<"vnd.3m.post-it-notes">>, []};
+all_ext(<<"pya">>) -> {<<"audio">>, <<"vnd.ms-playready.media.pya">>, []};
+all_ext(<<"pyv">>) -> {<<"video">>, <<"vnd.ms-playready.media.pyv">>, []};
+all_ext(<<"qam">>) -> {<<"application">>, <<"vnd.epson.quickanime">>, []};
+all_ext(<<"qbo">>) -> {<<"application">>, <<"vnd.intu.qbo">>, []};
+all_ext(<<"qfx">>) -> {<<"application">>, <<"vnd.intu.qfx">>, []};
+all_ext(<<"qps">>) -> {<<"application">>, <<"vnd.publishare-delta-tree">>, []};
+all_ext(<<"qt">>) -> {<<"video">>, <<"quicktime">>, []};
+all_ext(<<"qwd">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"qwt">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"qxb">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"qxd">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"qxl">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"qxt">>) -> {<<"application">>, <<"vnd.quark.quarkxpress">>, []};
+all_ext(<<"ra">>) -> {<<"audio">>, <<"x-pn-realaudio">>, []};
+all_ext(<<"ram">>) -> {<<"audio">>, <<"x-pn-realaudio">>, []};
+all_ext(<<"rar">>) -> {<<"application">>, <<"x-rar-compressed">>, []};
+all_ext(<<"ras">>) -> {<<"image">>, <<"x-cmu-raster">>, []};
+all_ext(<<"rcprofile">>) -> {<<"application">>, <<"vnd.ipunplugged.rcprofile">>, []};
+all_ext(<<"rdf">>) -> {<<"application">>, <<"rdf+xml">>, []};
+all_ext(<<"rdz">>) -> {<<"application">>, <<"vnd.data-vision.rdz">>, []};
+all_ext(<<"rep">>) -> {<<"application">>, <<"vnd.businessobjects">>, []};
+all_ext(<<"res">>) -> {<<"application">>, <<"x-dtbresource+xml">>, []};
+all_ext(<<"rgb">>) -> {<<"image">>, <<"x-rgb">>, []};
+all_ext(<<"rif">>) -> {<<"application">>, <<"reginfo+xml">>, []};
+all_ext(<<"rip">>) -> {<<"audio">>, <<"vnd.rip">>, []};
+all_ext(<<"ris">>) -> {<<"application">>, <<"x-research-info-systems">>, []};
+all_ext(<<"rl">>) -> {<<"application">>, <<"resource-lists+xml">>, []};
+all_ext(<<"rlc">>) -> {<<"image">>, <<"vnd.fujixerox.edmics-rlc">>, []};
+all_ext(<<"rld">>) -> {<<"application">>, <<"resource-lists-diff+xml">>, []};
+all_ext(<<"rm">>) -> {<<"application">>, <<"vnd.rn-realmedia">>, []};
+all_ext(<<"rmi">>) -> {<<"audio">>, <<"midi">>, []};
+all_ext(<<"rmp">>) -> {<<"audio">>, <<"x-pn-realaudio-plugin">>, []};
+all_ext(<<"rms">>) -> {<<"application">>, <<"vnd.jcp.javame.midlet-rms">>, []};
+all_ext(<<"rmvb">>) -> {<<"application">>, <<"vnd.rn-realmedia-vbr">>, []};
+all_ext(<<"rnc">>) -> {<<"application">>, <<"relax-ng-compact-syntax">>, []};
+all_ext(<<"roa">>) -> {<<"application">>, <<"rpki-roa">>, []};
+all_ext(<<"roff">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"rp9">>) -> {<<"application">>, <<"vnd.cloanto.rp9">>, []};
+all_ext(<<"rpss">>) -> {<<"application">>, <<"vnd.nokia.radio-presets">>, []};
+all_ext(<<"rpst">>) -> {<<"application">>, <<"vnd.nokia.radio-preset">>, []};
+all_ext(<<"rq">>) -> {<<"application">>, <<"sparql-query">>, []};
+all_ext(<<"rs">>) -> {<<"application">>, <<"rls-services+xml">>, []};
+all_ext(<<"rsd">>) -> {<<"application">>, <<"rsd+xml">>, []};
+all_ext(<<"rss">>) -> {<<"application">>, <<"rss+xml">>, []};
+all_ext(<<"rtf">>) -> {<<"application">>, <<"rtf">>, []};
+all_ext(<<"rtx">>) -> {<<"text">>, <<"richtext">>, []};
+all_ext(<<"s3m">>) -> {<<"audio">>, <<"s3m">>, []};
+all_ext(<<"saf">>) -> {<<"application">>, <<"vnd.yamaha.smaf-audio">>, []};
+all_ext(<<"sbml">>) -> {<<"application">>, <<"sbml+xml">>, []};
+all_ext(<<"sc">>) -> {<<"application">>, <<"vnd.ibm.secure-container">>, []};
+all_ext(<<"scd">>) -> {<<"application">>, <<"x-msschedule">>, []};
+all_ext(<<"scm">>) -> {<<"application">>, <<"vnd.lotus-screencam">>, []};
+all_ext(<<"scq">>) -> {<<"application">>, <<"scvp-cv-request">>, []};
+all_ext(<<"scs">>) -> {<<"application">>, <<"scvp-cv-response">>, []};
+all_ext(<<"scurl">>) -> {<<"text">>, <<"vnd.curl.scurl">>, []};
+all_ext(<<"sda">>) -> {<<"application">>, <<"vnd.stardivision.draw">>, []};
+all_ext(<<"sdc">>) -> {<<"application">>, <<"vnd.stardivision.calc">>, []};
+all_ext(<<"sdd">>) -> {<<"application">>, <<"vnd.stardivision.impress">>, []};
+all_ext(<<"sdkd">>) -> {<<"application">>, <<"vnd.solent.sdkm+xml">>, []};
+all_ext(<<"sdkm">>) -> {<<"application">>, <<"vnd.solent.sdkm+xml">>, []};
+all_ext(<<"sdp">>) -> {<<"application">>, <<"sdp">>, []};
+all_ext(<<"sdw">>) -> {<<"application">>, <<"vnd.stardivision.writer">>, []};
+all_ext(<<"see">>) -> {<<"application">>, <<"vnd.seemail">>, []};
+all_ext(<<"seed">>) -> {<<"application">>, <<"vnd.fdsn.seed">>, []};
+all_ext(<<"sema">>) -> {<<"application">>, <<"vnd.sema">>, []};
+all_ext(<<"semd">>) -> {<<"application">>, <<"vnd.semd">>, []};
+all_ext(<<"semf">>) -> {<<"application">>, <<"vnd.semf">>, []};
+all_ext(<<"ser">>) -> {<<"application">>, <<"java-serialized-object">>, []};
+all_ext(<<"setpay">>) -> {<<"application">>, <<"set-payment-initiation">>, []};
+all_ext(<<"setreg">>) -> {<<"application">>, <<"set-registration-initiation">>, []};
+all_ext(<<"sfd-hdstx">>) -> {<<"application">>, <<"vnd.hydrostatix.sof-data">>, []};
+all_ext(<<"sfs">>) -> {<<"application">>, <<"vnd.spotfire.sfs">>, []};
+all_ext(<<"sfv">>) -> {<<"text">>, <<"x-sfv">>, []};
+all_ext(<<"sgi">>) -> {<<"image">>, <<"sgi">>, []};
+all_ext(<<"sgl">>) -> {<<"application">>, <<"vnd.stardivision.writer-global">>, []};
+all_ext(<<"sgml">>) -> {<<"text">>, <<"sgml">>, []};
+all_ext(<<"sgm">>) -> {<<"text">>, <<"sgml">>, []};
+all_ext(<<"sh">>) -> {<<"application">>, <<"x-sh">>, []};
+all_ext(<<"shar">>) -> {<<"application">>, <<"x-shar">>, []};
+all_ext(<<"shf">>) -> {<<"application">>, <<"shf+xml">>, []};
+all_ext(<<"sid">>) -> {<<"image">>, <<"x-mrsid-image">>, []};
+all_ext(<<"sig">>) -> {<<"application">>, <<"pgp-signature">>, []};
+all_ext(<<"sil">>) -> {<<"audio">>, <<"silk">>, []};
+all_ext(<<"silo">>) -> {<<"model">>, <<"mesh">>, []};
+all_ext(<<"sis">>) -> {<<"application">>, <<"vnd.symbian.install">>, []};
+all_ext(<<"sisx">>) -> {<<"application">>, <<"vnd.symbian.install">>, []};
+all_ext(<<"sit">>) -> {<<"application">>, <<"x-stuffit">>, []};
+all_ext(<<"sitx">>) -> {<<"application">>, <<"x-stuffitx">>, []};
+all_ext(<<"skd">>) -> {<<"application">>, <<"vnd.koan">>, []};
+all_ext(<<"skm">>) -> {<<"application">>, <<"vnd.koan">>, []};
+all_ext(<<"skp">>) -> {<<"application">>, <<"vnd.koan">>, []};
+all_ext(<<"skt">>) -> {<<"application">>, <<"vnd.koan">>, []};
+all_ext(<<"sldm">>) -> {<<"application">>, <<"vnd.ms-powerpoint.slide.macroenabled.12">>, []};
+all_ext(<<"sldx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.presentationml.slide">>, []};
+all_ext(<<"slt">>) -> {<<"application">>, <<"vnd.epson.salt">>, []};
+all_ext(<<"sm">>) -> {<<"application">>, <<"vnd.stepmania.stepchart">>, []};
+all_ext(<<"smf">>) -> {<<"application">>, <<"vnd.stardivision.math">>, []};
+all_ext(<<"smi">>) -> {<<"application">>, <<"smil+xml">>, []};
+all_ext(<<"smil">>) -> {<<"application">>, <<"smil+xml">>, []};
+all_ext(<<"smv">>) -> {<<"video">>, <<"x-smv">>, []};
+all_ext(<<"smzip">>) -> {<<"application">>, <<"vnd.stepmania.package">>, []};
+all_ext(<<"snd">>) -> {<<"audio">>, <<"basic">>, []};
+all_ext(<<"snf">>) -> {<<"application">>, <<"x-font-snf">>, []};
+all_ext(<<"so">>) -> {<<"application">>, <<"octet-stream">>, []};
+all_ext(<<"spc">>) -> {<<"application">>, <<"x-pkcs7-certificates">>, []};
+all_ext(<<"spf">>) -> {<<"application">>, <<"vnd.yamaha.smaf-phrase">>, []};
+all_ext(<<"spl">>) -> {<<"application">>, <<"x-futuresplash">>, []};
+all_ext(<<"spot">>) -> {<<"text">>, <<"vnd.in3d.spot">>, []};
+all_ext(<<"spp">>) -> {<<"application">>, <<"scvp-vp-response">>, []};
+all_ext(<<"spq">>) -> {<<"application">>, <<"scvp-vp-request">>, []};
+all_ext(<<"spx">>) -> {<<"audio">>, <<"ogg">>, []};
+all_ext(<<"sql">>) -> {<<"application">>, <<"x-sql">>, []};
+all_ext(<<"src">>) -> {<<"application">>, <<"x-wais-source">>, []};
+all_ext(<<"srt">>) -> {<<"application">>, <<"x-subrip">>, []};
+all_ext(<<"sru">>) -> {<<"application">>, <<"sru+xml">>, []};
+all_ext(<<"srx">>) -> {<<"application">>, <<"sparql-results+xml">>, []};
+all_ext(<<"ssdl">>) -> {<<"application">>, <<"ssdl+xml">>, []};
+all_ext(<<"sse">>) -> {<<"application">>, <<"vnd.kodak-descriptor">>, []};
+all_ext(<<"ssf">>) -> {<<"application">>, <<"vnd.epson.ssf">>, []};
+all_ext(<<"ssml">>) -> {<<"application">>, <<"ssml+xml">>, []};
+all_ext(<<"st">>) -> {<<"application">>, <<"vnd.sailingtracker.track">>, []};
+all_ext(<<"stc">>) -> {<<"application">>, <<"vnd.sun.xml.calc.template">>, []};
+all_ext(<<"std">>) -> {<<"application">>, <<"vnd.sun.xml.draw.template">>, []};
+all_ext(<<"s">>) -> {<<"text">>, <<"x-asm">>, []};
+all_ext(<<"stf">>) -> {<<"application">>, <<"vnd.wt.stf">>, []};
+all_ext(<<"sti">>) -> {<<"application">>, <<"vnd.sun.xml.impress.template">>, []};
+all_ext(<<"stk">>) -> {<<"application">>, <<"hyperstudio">>, []};
+all_ext(<<"stl">>) -> {<<"application">>, <<"vnd.ms-pki.stl">>, []};
+all_ext(<<"str">>) -> {<<"application">>, <<"vnd.pg.format">>, []};
+all_ext(<<"stw">>) -> {<<"application">>, <<"vnd.sun.xml.writer.template">>, []};
+all_ext(<<"sub">>) -> {<<"image">>, <<"vnd.dvb.subtitle">>, []};
+all_ext(<<"sus">>) -> {<<"application">>, <<"vnd.sus-calendar">>, []};
+all_ext(<<"susp">>) -> {<<"application">>, <<"vnd.sus-calendar">>, []};
+all_ext(<<"sv4cpio">>) -> {<<"application">>, <<"x-sv4cpio">>, []};
+all_ext(<<"sv4crc">>) -> {<<"application">>, <<"x-sv4crc">>, []};
+all_ext(<<"svc">>) -> {<<"application">>, <<"vnd.dvb.service">>, []};
+all_ext(<<"svd">>) -> {<<"application">>, <<"vnd.svd">>, []};
+all_ext(<<"svg">>) -> {<<"image">>, <<"svg+xml">>, []};
+all_ext(<<"svgz">>) -> {<<"image">>, <<"svg+xml">>, []};
+all_ext(<<"swa">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"swf">>) -> {<<"application">>, <<"x-shockwave-flash">>, []};
+all_ext(<<"swi">>) -> {<<"application">>, <<"vnd.aristanetworks.swi">>, []};
+all_ext(<<"sxc">>) -> {<<"application">>, <<"vnd.sun.xml.calc">>, []};
+all_ext(<<"sxd">>) -> {<<"application">>, <<"vnd.sun.xml.draw">>, []};
+all_ext(<<"sxg">>) -> {<<"application">>, <<"vnd.sun.xml.writer.global">>, []};
+all_ext(<<"sxi">>) -> {<<"application">>, <<"vnd.sun.xml.impress">>, []};
+all_ext(<<"sxm">>) -> {<<"application">>, <<"vnd.sun.xml.math">>, []};
+all_ext(<<"sxw">>) -> {<<"application">>, <<"vnd.sun.xml.writer">>, []};
+all_ext(<<"t3">>) -> {<<"application">>, <<"x-t3vm-image">>, []};
+all_ext(<<"taglet">>) -> {<<"application">>, <<"vnd.mynfc">>, []};
+all_ext(<<"tao">>) -> {<<"application">>, <<"vnd.tao.intent-module-archive">>, []};
+all_ext(<<"tar">>) -> {<<"application">>, <<"x-tar">>, []};
+all_ext(<<"tcap">>) -> {<<"application">>, <<"vnd.3gpp2.tcap">>, []};
+all_ext(<<"tcl">>) -> {<<"application">>, <<"x-tcl">>, []};
+all_ext(<<"teacher">>) -> {<<"application">>, <<"vnd.smart.teacher">>, []};
+all_ext(<<"tei">>) -> {<<"application">>, <<"tei+xml">>, []};
+all_ext(<<"teicorpus">>) -> {<<"application">>, <<"tei+xml">>, []};
+all_ext(<<"tex">>) -> {<<"application">>, <<"x-tex">>, []};
+all_ext(<<"texi">>) -> {<<"application">>, <<"x-texinfo">>, []};
+all_ext(<<"texinfo">>) -> {<<"application">>, <<"x-texinfo">>, []};
+all_ext(<<"text">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"tfi">>) -> {<<"application">>, <<"thraud+xml">>, []};
+all_ext(<<"tfm">>) -> {<<"application">>, <<"x-tex-tfm">>, []};
+all_ext(<<"tga">>) -> {<<"image">>, <<"x-tga">>, []};
+all_ext(<<"thmx">>) -> {<<"application">>, <<"vnd.ms-officetheme">>, []};
+all_ext(<<"tiff">>) -> {<<"image">>, <<"tiff">>, []};
+all_ext(<<"tif">>) -> {<<"image">>, <<"tiff">>, []};
+all_ext(<<"tmo">>) -> {<<"application">>, <<"vnd.tmobile-livetv">>, []};
+all_ext(<<"torrent">>) -> {<<"application">>, <<"x-bittorrent">>, []};
+all_ext(<<"tpl">>) -> {<<"application">>, <<"vnd.groove-tool-template">>, []};
+all_ext(<<"tpt">>) -> {<<"application">>, <<"vnd.trid.tpt">>, []};
+all_ext(<<"tra">>) -> {<<"application">>, <<"vnd.trueapp">>, []};
+all_ext(<<"trm">>) -> {<<"application">>, <<"x-msterminal">>, []};
+all_ext(<<"tr">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"tsd">>) -> {<<"application">>, <<"timestamped-data">>, []};
+all_ext(<<"tsv">>) -> {<<"text">>, <<"tab-separated-values">>, []};
+all_ext(<<"ttc">>) -> {<<"font">>, <<"collection">>, []};
+all_ext(<<"t">>) -> {<<"text">>, <<"troff">>, []};
+all_ext(<<"ttf">>) -> {<<"font">>, <<"ttf">>, []};
+all_ext(<<"ttl">>) -> {<<"text">>, <<"turtle">>, []};
+all_ext(<<"twd">>) -> {<<"application">>, <<"vnd.simtech-mindmapper">>, []};
+all_ext(<<"twds">>) -> {<<"application">>, <<"vnd.simtech-mindmapper">>, []};
+all_ext(<<"txd">>) -> {<<"application">>, <<"vnd.genomatix.tuxedo">>, []};
+all_ext(<<"txf">>) -> {<<"application">>, <<"vnd.mobius.txf">>, []};
+all_ext(<<"txt">>) -> {<<"text">>, <<"plain">>, []};
+all_ext(<<"u32">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
+all_ext(<<"udeb">>) -> {<<"application">>, <<"x-debian-package">>, []};
+all_ext(<<"ufd">>) -> {<<"application">>, <<"vnd.ufdl">>, []};
+all_ext(<<"ufdl">>) -> {<<"application">>, <<"vnd.ufdl">>, []};
+all_ext(<<"ulx">>) -> {<<"application">>, <<"x-glulx">>, []};
+all_ext(<<"umj">>) -> {<<"application">>, <<"vnd.umajin">>, []};
+all_ext(<<"unityweb">>) -> {<<"application">>, <<"vnd.unity">>, []};
+all_ext(<<"uoml">>) -> {<<"application">>, <<"vnd.uoml+xml">>, []};
+all_ext(<<"uris">>) -> {<<"text">>, <<"uri-list">>, []};
+all_ext(<<"uri">>) -> {<<"text">>, <<"uri-list">>, []};
+all_ext(<<"urls">>) -> {<<"text">>, <<"uri-list">>, []};
+all_ext(<<"ustar">>) -> {<<"application">>, <<"x-ustar">>, []};
+all_ext(<<"utz">>) -> {<<"application">>, <<"vnd.uiq.theme">>, []};
+all_ext(<<"uu">>) -> {<<"text">>, <<"x-uuencode">>, []};
+all_ext(<<"uva">>) -> {<<"audio">>, <<"vnd.dece.audio">>, []};
+all_ext(<<"uvd">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
+all_ext(<<"uvf">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
+all_ext(<<"uvg">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
+all_ext(<<"uvh">>) -> {<<"video">>, <<"vnd.dece.hd">>, []};
+all_ext(<<"uvi">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
+all_ext(<<"uvm">>) -> {<<"video">>, <<"vnd.dece.mobile">>, []};
+all_ext(<<"uvp">>) -> {<<"video">>, <<"vnd.dece.pd">>, []};
+all_ext(<<"uvs">>) -> {<<"video">>, <<"vnd.dece.sd">>, []};
+all_ext(<<"uvt">>) -> {<<"application">>, <<"vnd.dece.ttml+xml">>, []};
+all_ext(<<"uvu">>) -> {<<"video">>, <<"vnd.uvvu.mp4">>, []};
+all_ext(<<"uvva">>) -> {<<"audio">>, <<"vnd.dece.audio">>, []};
+all_ext(<<"uvvd">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
+all_ext(<<"uvvf">>) -> {<<"application">>, <<"vnd.dece.data">>, []};
+all_ext(<<"uvvg">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
+all_ext(<<"uvvh">>) -> {<<"video">>, <<"vnd.dece.hd">>, []};
+all_ext(<<"uvvi">>) -> {<<"image">>, <<"vnd.dece.graphic">>, []};
+all_ext(<<"uvvm">>) -> {<<"video">>, <<"vnd.dece.mobile">>, []};
+all_ext(<<"uvvp">>) -> {<<"video">>, <<"vnd.dece.pd">>, []};
+all_ext(<<"uvvs">>) -> {<<"video">>, <<"vnd.dece.sd">>, []};
+all_ext(<<"uvvt">>) -> {<<"application">>, <<"vnd.dece.ttml+xml">>, []};
+all_ext(<<"uvvu">>) -> {<<"video">>, <<"vnd.uvvu.mp4">>, []};
+all_ext(<<"uvv">>) -> {<<"video">>, <<"vnd.dece.video">>, []};
+all_ext(<<"uvvv">>) -> {<<"video">>, <<"vnd.dece.video">>, []};
+all_ext(<<"uvvx">>) -> {<<"application">>, <<"vnd.dece.unspecified">>, []};
+all_ext(<<"uvvz">>) -> {<<"application">>, <<"vnd.dece.zip">>, []};
+all_ext(<<"uvx">>) -> {<<"application">>, <<"vnd.dece.unspecified">>, []};
+all_ext(<<"uvz">>) -> {<<"application">>, <<"vnd.dece.zip">>, []};
+all_ext(<<"vcard">>) -> {<<"text">>, <<"vcard">>, []};
+all_ext(<<"vcd">>) -> {<<"application">>, <<"x-cdlink">>, []};
+all_ext(<<"vcf">>) -> {<<"text">>, <<"x-vcard">>, []};
+all_ext(<<"vcg">>) -> {<<"application">>, <<"vnd.groove-vcard">>, []};
+all_ext(<<"vcs">>) -> {<<"text">>, <<"x-vcalendar">>, []};
+all_ext(<<"vcx">>) -> {<<"application">>, <<"vnd.vcx">>, []};
+all_ext(<<"vis">>) -> {<<"application">>, <<"vnd.visionary">>, []};
+all_ext(<<"viv">>) -> {<<"video">>, <<"vnd.vivo">>, []};
+all_ext(<<"vob">>) -> {<<"video">>, <<"x-ms-vob">>, []};
+all_ext(<<"vor">>) -> {<<"application">>, <<"vnd.stardivision.writer">>, []};
+all_ext(<<"vox">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
+all_ext(<<"vrml">>) -> {<<"model">>, <<"vrml">>, []};
+all_ext(<<"vsd">>) -> {<<"application">>, <<"vnd.visio">>, []};
+all_ext(<<"vsf">>) -> {<<"application">>, <<"vnd.vsf">>, []};
+all_ext(<<"vss">>) -> {<<"application">>, <<"vnd.visio">>, []};
+all_ext(<<"vst">>) -> {<<"application">>, <<"vnd.visio">>, []};
+all_ext(<<"vsw">>) -> {<<"application">>, <<"vnd.visio">>, []};
+all_ext(<<"vtu">>) -> {<<"model">>, <<"vnd.vtu">>, []};
+all_ext(<<"vxml">>) -> {<<"application">>, <<"voicexml+xml">>, []};
+all_ext(<<"w3d">>) -> {<<"application">>, <<"x-director">>, []};
+all_ext(<<"wad">>) -> {<<"application">>, <<"x-doom">>, []};
+all_ext(<<"wav">>) -> {<<"audio">>, <<"x-wav">>, []};
+all_ext(<<"wax">>) -> {<<"audio">>, <<"x-ms-wax">>, []};
+all_ext(<<"wbmp">>) -> {<<"image">>, <<"vnd.wap.wbmp">>, []};
+all_ext(<<"wbs">>) -> {<<"application">>, <<"vnd.criticaltools.wbs+xml">>, []};
+all_ext(<<"wbxml">>) -> {<<"application">>, <<"vnd.wap.wbxml">>, []};
+all_ext(<<"wcm">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
+all_ext(<<"wdb">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
+all_ext(<<"wdp">>) -> {<<"image">>, <<"vnd.ms-photo">>, []};
+all_ext(<<"weba">>) -> {<<"audio">>, <<"webm">>, []};
+all_ext(<<"webm">>) -> {<<"video">>, <<"webm">>, []};
+all_ext(<<"webp">>) -> {<<"image">>, <<"webp">>, []};
+all_ext(<<"wg">>) -> {<<"application">>, <<"vnd.pmi.widget">>, []};
+all_ext(<<"wgt">>) -> {<<"application">>, <<"widget">>, []};
+all_ext(<<"wks">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
+all_ext(<<"wma">>) -> {<<"audio">>, <<"x-ms-wma">>, []};
+all_ext(<<"wmd">>) -> {<<"application">>, <<"x-ms-wmd">>, []};
+all_ext(<<"wmf">>) -> {<<"application">>, <<"x-msmetafile">>, []};
+all_ext(<<"wmlc">>) -> {<<"application">>, <<"vnd.wap.wmlc">>, []};
+all_ext(<<"wmlsc">>) -> {<<"application">>, <<"vnd.wap.wmlscriptc">>, []};
+all_ext(<<"wmls">>) -> {<<"text">>, <<"vnd.wap.wmlscript">>, []};
+all_ext(<<"wml">>) -> {<<"text">>, <<"vnd.wap.wml">>, []};
+all_ext(<<"wm">>) -> {<<"video">>, <<"x-ms-wm">>, []};
+all_ext(<<"wmv">>) -> {<<"video">>, <<"x-ms-wmv">>, []};
+all_ext(<<"wmx">>) -> {<<"video">>, <<"x-ms-wmx">>, []};
+all_ext(<<"wmz">>) -> {<<"application">>, <<"x-msmetafile">>, []};
+all_ext(<<"woff2">>) -> {<<"font">>, <<"woff2">>, []};
+all_ext(<<"woff">>) -> {<<"font">>, <<"woff">>, []};
+all_ext(<<"wpd">>) -> {<<"application">>, <<"vnd.wordperfect">>, []};
+all_ext(<<"wpl">>) -> {<<"application">>, <<"vnd.ms-wpl">>, []};
+all_ext(<<"wps">>) -> {<<"application">>, <<"vnd.ms-works">>, []};
+all_ext(<<"wqd">>) -> {<<"application">>, <<"vnd.wqd">>, []};
+all_ext(<<"wri">>) -> {<<"application">>, <<"x-mswrite">>, []};
+all_ext(<<"wrl">>) -> {<<"model">>, <<"vrml">>, []};
+all_ext(<<"wsdl">>) -> {<<"application">>, <<"wsdl+xml">>, []};
+all_ext(<<"wspolicy">>) -> {<<"application">>, <<"wspolicy+xml">>, []};
+all_ext(<<"wtb">>) -> {<<"application">>, <<"vnd.webturbo">>, []};
+all_ext(<<"wvx">>) -> {<<"video">>, <<"x-ms-wvx">>, []};
+all_ext(<<"x32">>) -> {<<"application">>, <<"x-authorware-bin">>, []};
+all_ext(<<"x3db">>) -> {<<"model">>, <<"x3d+binary">>, []};
+all_ext(<<"x3dbz">>) -> {<<"model">>, <<"x3d+binary">>, []};
+all_ext(<<"x3d">>) -> {<<"model">>, <<"x3d+xml">>, []};
+all_ext(<<"x3dv">>) -> {<<"model">>, <<"x3d+vrml">>, []};
+all_ext(<<"x3dvz">>) -> {<<"model">>, <<"x3d+vrml">>, []};
+all_ext(<<"x3dz">>) -> {<<"model">>, <<"x3d+xml">>, []};
+all_ext(<<"xaml">>) -> {<<"application">>, <<"xaml+xml">>, []};
+all_ext(<<"xap">>) -> {<<"application">>, <<"x-silverlight-app">>, []};
+all_ext(<<"xar">>) -> {<<"application">>, <<"vnd.xara">>, []};
+all_ext(<<"xbap">>) -> {<<"application">>, <<"x-ms-xbap">>, []};
+all_ext(<<"xbd">>) -> {<<"application">>, <<"vnd.fujixerox.docuworks.binder">>, []};
+all_ext(<<"xbm">>) -> {<<"image">>, <<"x-xbitmap">>, []};
+all_ext(<<"xdf">>) -> {<<"application">>, <<"xcap-diff+xml">>, []};
+all_ext(<<"xdm">>) -> {<<"application">>, <<"vnd.syncml.dm+xml">>, []};
+all_ext(<<"xdp">>) -> {<<"application">>, <<"vnd.adobe.xdp+xml">>, []};
+all_ext(<<"xdssc">>) -> {<<"application">>, <<"dssc+xml">>, []};
+all_ext(<<"xdw">>) -> {<<"application">>, <<"vnd.fujixerox.docuworks">>, []};
+all_ext(<<"xenc">>) -> {<<"application">>, <<"xenc+xml">>, []};
+all_ext(<<"xer">>) -> {<<"application">>, <<"patch-ops-error+xml">>, []};
+all_ext(<<"xfdf">>) -> {<<"application">>, <<"vnd.adobe.xfdf">>, []};
+all_ext(<<"xfdl">>) -> {<<"application">>, <<"vnd.xfdl">>, []};
+all_ext(<<"xht">>) -> {<<"application">>, <<"xhtml+xml">>, []};
+all_ext(<<"xhtml">>) -> {<<"application">>, <<"xhtml+xml">>, []};
+all_ext(<<"xhvml">>) -> {<<"application">>, <<"xv+xml">>, []};
+all_ext(<<"xif">>) -> {<<"image">>, <<"vnd.xiff">>, []};
+all_ext(<<"xla">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xlam">>) -> {<<"application">>, <<"vnd.ms-excel.addin.macroenabled.12">>, []};
+all_ext(<<"xlc">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xlf">>) -> {<<"application">>, <<"x-xliff+xml">>, []};
+all_ext(<<"xlm">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xls">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xlsb">>) -> {<<"application">>, <<"vnd.ms-excel.sheet.binary.macroenabled.12">>, []};
+all_ext(<<"xlsm">>) -> {<<"application">>, <<"vnd.ms-excel.sheet.macroenabled.12">>, []};
+all_ext(<<"xlsx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.spreadsheetml.sheet">>, []};
+all_ext(<<"xlt">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xltm">>) -> {<<"application">>, <<"vnd.ms-excel.template.macroenabled.12">>, []};
+all_ext(<<"xltx">>) -> {<<"application">>, <<"vnd.openxmlformats-officedocument.spreadsheetml.template">>, []};
+all_ext(<<"xlw">>) -> {<<"application">>, <<"vnd.ms-excel">>, []};
+all_ext(<<"xm">>) -> {<<"audio">>, <<"xm">>, []};
+all_ext(<<"xml">>) -> {<<"application">>, <<"xml">>, []};
+all_ext(<<"xo">>) -> {<<"application">>, <<"vnd.olpc-sugar">>, []};
+all_ext(<<"xop">>) -> {<<"application">>, <<"xop+xml">>, []};
+all_ext(<<"xpi">>) -> {<<"application">>, <<"x-xpinstall">>, []};
+all_ext(<<"xpl">>) -> {<<"application">>, <<"xproc+xml">>, []};
+all_ext(<<"xpm">>) -> {<<"image">>, <<"x-xpixmap">>, []};
+all_ext(<<"xpr">>) -> {<<"application">>, <<"vnd.is-xpr">>, []};
+all_ext(<<"xps">>) -> {<<"application">>, <<"vnd.ms-xpsdocument">>, []};
+all_ext(<<"xpw">>) -> {<<"application">>, <<"vnd.intercon.formnet">>, []};
+all_ext(<<"xpx">>) -> {<<"application">>, <<"vnd.intercon.formnet">>, []};
+all_ext(<<"xsl">>) -> {<<"application">>, <<"xml">>, []};
+all_ext(<<"xslt">>) -> {<<"application">>, <<"xslt+xml">>, []};
+all_ext(<<"xsm">>) -> {<<"application">>, <<"vnd.syncml+xml">>, []};
+all_ext(<<"xspf">>) -> {<<"application">>, <<"xspf+xml">>, []};
+all_ext(<<"xul">>) -> {<<"application">>, <<"vnd.mozilla.xul+xml">>, []};
+all_ext(<<"xvm">>) -> {<<"application">>, <<"xv+xml">>, []};
+all_ext(<<"xvml">>) -> {<<"application">>, <<"xv+xml">>, []};
+all_ext(<<"xwd">>) -> {<<"image">>, <<"x-xwindowdump">>, []};
+all_ext(<<"xyz">>) -> {<<"chemical">>, <<"x-xyz">>, []};
+all_ext(<<"xz">>) -> {<<"application">>, <<"x-xz">>, []};
+all_ext(<<"yang">>) -> {<<"application">>, <<"yang">>, []};
+all_ext(<<"yin">>) -> {<<"application">>, <<"yin+xml">>, []};
+all_ext(<<"z1">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z2">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z3">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z4">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z5">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z6">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z7">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"z8">>) -> {<<"application">>, <<"x-zmachine">>, []};
+all_ext(<<"zaz">>) -> {<<"application">>, <<"vnd.zzazz.deck+xml">>, []};
+all_ext(<<"zip">>) -> {<<"application">>, <<"zip">>, []};
+all_ext(<<"zir">>) -> {<<"application">>, <<"vnd.zul">>, []};
+all_ext(<<"zirz">>) -> {<<"application">>, <<"vnd.zul">>, []};
+all_ext(<<"zmm">>) -> {<<"application">>, <<"vnd.handheld-entertainment+xml">>, []};
+%% GENERATED
+all_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
+
+web_ext(<<"css">>) -> {<<"text">>, <<"css">>, []};
+web_ext(<<"gif">>) -> {<<"image">>, <<"gif">>, []};
+web_ext(<<"html">>) -> {<<"text">>, <<"html">>, []};
+web_ext(<<"htm">>) -> {<<"text">>, <<"html">>, []};
+web_ext(<<"ico">>) -> {<<"image">>, <<"x-icon">>, []};
+web_ext(<<"jpeg">>) -> {<<"image">>, <<"jpeg">>, []};
+web_ext(<<"jpg">>) -> {<<"image">>, <<"jpeg">>, []};
+web_ext(<<"js">>) -> {<<"application">>, <<"javascript">>, []};
+web_ext(<<"mp3">>) -> {<<"audio">>, <<"mpeg">>, []};
+web_ext(<<"mp4">>) -> {<<"video">>, <<"mp4">>, []};
+web_ext(<<"ogg">>) -> {<<"audio">>, <<"ogg">>, []};
+web_ext(<<"ogv">>) -> {<<"video">>, <<"ogg">>, []};
+web_ext(<<"png">>) -> {<<"image">>, <<"png">>, []};
+web_ext(<<"svg">>) -> {<<"image">>, <<"svg+xml">>, []};
+web_ext(<<"wav">>) -> {<<"audio">>, <<"x-wav">>, []};
+web_ext(<<"webm">>) -> {<<"video">>, <<"webm">>, []};
+web_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
diff --git a/server/_build/default/lib/cowlib/src/cow_mimetypes.erl.src b/server/_build/default/lib/cowlib/src/cow_mimetypes.erl.src
new file mode 100644
index 0000000..7cccdd3
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_mimetypes.erl.src
@@ -0,0 +1,61 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_mimetypes).
+
+-export([all/1]).
+-export([web/1]).
+
+%% @doc Return the mimetype for any file by looking at its extension.
+
+-spec all(binary()) -> {binary(), binary(), []}.
+all(Path) ->
+ case filename:extension(Path) of
+ <<>> -> {<<"application">>, <<"octet-stream">>, []};
+ %% @todo Convert to string:lowercase on OTP-20+.
+ << $., Ext/binary >> -> all_ext(list_to_binary(string:to_lower(binary_to_list(Ext))))
+ end.
+
+%% @doc Return the mimetype for a Web related file by looking at its extension.
+
+-spec web(binary()) -> {binary(), binary(), []}.
+web(Path) ->
+ case filename:extension(Path) of
+ <<>> -> {<<"application">>, <<"octet-stream">>, []};
+ %% @todo Convert to string:lowercase on OTP-20+.
+ << $., Ext/binary >> -> web_ext(list_to_binary(string:to_lower(binary_to_list(Ext))))
+ end.
+
+%% Internal.
+
+%% GENERATED
+all_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
+
+web_ext(<<"css">>) -> {<<"text">>, <<"css">>, []};
+web_ext(<<"gif">>) -> {<<"image">>, <<"gif">>, []};
+web_ext(<<"html">>) -> {<<"text">>, <<"html">>, []};
+web_ext(<<"htm">>) -> {<<"text">>, <<"html">>, []};
+web_ext(<<"ico">>) -> {<<"image">>, <<"x-icon">>, []};
+web_ext(<<"jpeg">>) -> {<<"image">>, <<"jpeg">>, []};
+web_ext(<<"jpg">>) -> {<<"image">>, <<"jpeg">>, []};
+web_ext(<<"js">>) -> {<<"application">>, <<"javascript">>, []};
+web_ext(<<"mp3">>) -> {<<"audio">>, <<"mpeg">>, []};
+web_ext(<<"mp4">>) -> {<<"video">>, <<"mp4">>, []};
+web_ext(<<"ogg">>) -> {<<"audio">>, <<"ogg">>, []};
+web_ext(<<"ogv">>) -> {<<"video">>, <<"ogg">>, []};
+web_ext(<<"png">>) -> {<<"image">>, <<"png">>, []};
+web_ext(<<"svg">>) -> {<<"image">>, <<"svg+xml">>, []};
+web_ext(<<"wav">>) -> {<<"audio">>, <<"x-wav">>, []};
+web_ext(<<"webm">>) -> {<<"video">>, <<"webm">>, []};
+web_ext(_) -> {<<"application">>, <<"octet-stream">>, []}.
diff --git a/server/_build/default/lib/cowlib/src/cow_multipart.erl b/server/_build/default/lib/cowlib/src/cow_multipart.erl
new file mode 100644
index 0000000..4d6d574
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_multipart.erl
@@ -0,0 +1,775 @@
+%% Copyright (c) 2014-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_multipart).
+
+%% Parsing.
+-export([parse_headers/2]).
+-export([parse_body/2]).
+
+%% Building.
+-export([boundary/0]).
+-export([first_part/2]).
+-export([part/2]).
+-export([close/1]).
+
+%% Headers.
+-export([form_data/1]).
+-export([parse_content_disposition/1]).
+-export([parse_content_transfer_encoding/1]).
+-export([parse_content_type/1]).
+
+-type headers() :: [{iodata(), iodata()}].
+-export_type([headers/0]).
+
+-include("cow_inline.hrl").
+
+-define(TEST1_MIME, <<
+ "This is a message with multiple parts in MIME format.\r\n"
+ "--frontier\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "This is the body of the message.\r\n"
+ "--frontier\r\n"
+ "Content-Type: application/octet-stream\r\n"
+ "Content-Transfer-Encoding: base64\r\n"
+ "\r\n"
+ "PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
+ "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==\r\n"
+ "--frontier--"
+>>).
+-define(TEST1_BOUNDARY, <<"frontier">>).
+
+-define(TEST2_MIME, <<
+ "--AaB03x\r\n"
+ "Content-Disposition: form-data; name=\"submit-name\"\r\n"
+ "\r\n"
+ "Larry\r\n"
+ "--AaB03x\r\n"
+ "Content-Disposition: form-data; name=\"files\"\r\n"
+ "Content-Type: multipart/mixed; boundary=BbC04y\r\n"
+ "\r\n"
+ "--BbC04y\r\n"
+ "Content-Disposition: file; filename=\"file1.txt\"\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "... contents of file1.txt ...\r\n"
+ "--BbC04y\r\n"
+ "Content-Disposition: file; filename=\"file2.gif\"\r\n"
+ "Content-Type: image/gif\r\n"
+ "Content-Transfer-Encoding: binary\r\n"
+ "\r\n"
+ "...contents of file2.gif...\r\n"
+ "--BbC04y--\r\n"
+ "--AaB03x--"
+>>).
+-define(TEST2_BOUNDARY, <<"AaB03x">>).
+
+-define(TEST3_MIME, <<
+ "This is the preamble.\r\n"
+ "--boundary\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "This is the body of the message.\r\n"
+ "--boundary--"
+ "\r\nThis is the epilogue. Here it includes leading CRLF"
+>>).
+-define(TEST3_BOUNDARY, <<"boundary">>).
+
+-define(TEST4_MIME, <<
+ "This is the preamble.\r\n"
+ "--boundary\r\n"
+ "Content-Type: text/plain\r\n"
+ "\r\n"
+ "This is the body of the message.\r\n"
+ "--boundary--"
+ "\r\n"
+>>).
+-define(TEST4_BOUNDARY, <<"boundary">>).
+
+%% RFC 2046, Section 5.1.1
+-define(TEST5_MIME, <<
+ "This is the preamble. It is to be ignored, though it\r\n"
+ "is a handy place for composition agents to include an\r\n"
+ "explanatory note to non-MIME conformant readers.\r\n"
+ "\r\n"
+ "--simple boundary\r\n",
+ "\r\n"
+ "This is implicitly typed plain US-ASCII text.\r\n"
+ "It does NOT end with a linebreak."
+ "\r\n"
+ "--simple boundary\r\n",
+ "Content-type: text/plain; charset=us-ascii\r\n"
+ "\r\n"
+ "This is explicitly typed plain US-ASCII text.\r\n"
+ "It DOES end with a linebreak.\r\n"
+ "\r\n"
+ "--simple boundary--\r\n"
+ "\r\n"
+ "This is the epilogue. It is also to be ignored."
+>>).
+-define(TEST5_BOUNDARY, <<"simple boundary">>).
+
+%% Parsing.
+%%
+%% The multipart format is defined in RFC 2045.
+
+%% @doc Parse the headers for the next multipart part.
+%%
+%% This function skips any preamble before the boundary.
+%% The preamble may be retrieved using parse_body/2.
+%%
+%% This function will accept input of any size, it is
+%% up to the caller to limit it if needed.
+
+-spec parse_headers(binary(), binary())
+ -> more | {more, binary()}
+ | {ok, headers(), binary()}
+ | {done, binary()}.
+%% If the stream starts with the boundary we can make a few assumptions
+%% and quickly figure out if we got the complete list of headers.
+parse_headers(<< "--", Stream/bits >>, Boundary) ->
+ BoundarySize = byte_size(Boundary),
+ case Stream of
+ %% Last boundary. Return the epilogue.
+ << Boundary:BoundarySize/binary, "--", Stream2/bits >> ->
+ {done, Stream2};
+ << Boundary:BoundarySize/binary, Stream2/bits >> ->
+ %% We have all the headers only if there is a \r\n\r\n
+ %% somewhere in the data after the boundary.
+ case binary:match(Stream2, <<"\r\n\r\n">>) of
+ nomatch ->
+ more;
+ _ ->
+ before_parse_headers(Stream2)
+ end;
+ %% If there isn't enough to represent Boundary \r\n\r\n
+ %% then we definitely don't have all the headers.
+ _ when byte_size(Stream) < byte_size(Boundary) + 4 ->
+ more;
+ %% Otherwise we have preamble data to skip.
+ %% We still got rid of the first two misleading bytes.
+ _ ->
+ skip_preamble(Stream, Boundary)
+ end;
+%% Otherwise we have preamble data to skip.
+parse_headers(Stream, Boundary) ->
+ skip_preamble(Stream, Boundary).
+
+%% We need to find the boundary and a \r\n\r\n after that.
+%% Since the boundary isn't at the start, it must be right
+%% after a \r\n too.
+skip_preamble(Stream, Boundary) ->
+ case binary:match(Stream, <<"\r\n--", Boundary/bits >>) of
+ %% No boundary, need more data.
+ nomatch ->
+ %% We can safely skip the size of the stream
+ %% minus the last 3 bytes which may be a partial boundary.
+ SkipSize = byte_size(Stream) - 3,
+ case SkipSize > 0 of
+ false ->
+ more;
+ true ->
+ << _:SkipSize/binary, Stream2/bits >> = Stream,
+ {more, Stream2}
+ end;
+ {Start, Length} ->
+ Start2 = Start + Length,
+ << _:Start2/binary, Stream2/bits >> = Stream,
+ case Stream2 of
+ %% Last boundary. Return the epilogue.
+ << "--", Stream3/bits >> ->
+ {done, Stream3};
+ _ ->
+ case binary:match(Stream, <<"\r\n\r\n">>) of
+ %% We don't have the full headers.
+ nomatch ->
+ {more, Stream2};
+ _ ->
+ before_parse_headers(Stream2)
+ end
+ end
+ end.
+
+before_parse_headers(<< "\r\n\r\n", Stream/bits >>) ->
+ %% This indicates that there are no headers, so we can abort immediately.
+ {ok, [], Stream};
+before_parse_headers(<< "\r\n", Stream/bits >>) ->
+ %% There is a line break right after the boundary, skip it.
+ parse_hd_name(Stream, [], <<>>).
+
+parse_hd_name(<< C, Rest/bits >>, H, SoFar) ->
+ case C of
+ $: -> parse_hd_before_value(Rest, H, SoFar);
+ $\s -> parse_hd_name_ws(Rest, H, SoFar);
+ $\t -> parse_hd_name_ws(Rest, H, SoFar);
+ _ -> ?LOWER(parse_hd_name, Rest, H, SoFar)
+ end.
+
+parse_hd_name_ws(<< C, Rest/bits >>, H, Name) ->
+ case C of
+ $\s -> parse_hd_name_ws(Rest, H, Name);
+ $\t -> parse_hd_name_ws(Rest, H, Name);
+ $: -> parse_hd_before_value(Rest, H, Name)
+ end.
+
+parse_hd_before_value(<< $\s, Rest/bits >>, H, N) ->
+ parse_hd_before_value(Rest, H, N);
+parse_hd_before_value(<< $\t, Rest/bits >>, H, N) ->
+ parse_hd_before_value(Rest, H, N);
+parse_hd_before_value(Buffer, H, N) ->
+ parse_hd_value(Buffer, H, N, <<>>).
+
+parse_hd_value(<< $\r, Rest/bits >>, Headers, Name, SoFar) ->
+ case Rest of
+ << "\n\r\n", Rest2/bits >> ->
+ {ok, [{Name, SoFar}|Headers], Rest2};
+ << $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t ->
+ parse_hd_value(Rest2, Headers, Name, SoFar);
+ << $\n, Rest2/bits >> ->
+ parse_hd_name(Rest2, [{Name, SoFar}|Headers], <<>>)
+ end;
+parse_hd_value(<< C, Rest/bits >>, H, N, SoFar) ->
+ parse_hd_value(Rest, H, N, << SoFar/binary, C >>).
+
+%% @doc Parse the body of the current multipart part.
+%%
+%% The body is everything until the next boundary.
+
+-spec parse_body(binary(), binary())
+ -> {ok, binary()} | {ok, binary(), binary()}
+ | done | {done, binary()} | {done, binary(), binary()}.
+parse_body(Stream, Boundary) ->
+ BoundarySize = byte_size(Boundary),
+ case Stream of
+ << "--", Boundary:BoundarySize/binary, _/bits >> ->
+ done;
+ _ ->
+ case binary:match(Stream, << "\r\n--", Boundary/bits >>) of
+ %% No boundary, check for a possible partial at the end.
+ %% Return more or less of the body depending on the result.
+ nomatch ->
+ StreamSize = byte_size(Stream),
+ From = StreamSize - BoundarySize - 3,
+ MatchOpts = if
+ %% Binary too small to contain boundary, check it fully.
+ From < 0 -> [];
+ %% Optimize, only check the end of the binary.
+ true -> [{scope, {From, StreamSize - From}}]
+ end,
+ case binary:match(Stream, <<"\r">>, MatchOpts) of
+ nomatch ->
+ {ok, Stream};
+ {Pos, _} ->
+ case Stream of
+ << Body:Pos/binary >> ->
+ {ok, Body};
+ << Body:Pos/binary, Rest/bits >> ->
+ {ok, Body, Rest}
+ end
+ end;
+ %% Boundary found, this is the last chunk of the body.
+ {Pos, _} ->
+ case Stream of
+ << Body:Pos/binary, "\r\n" >> ->
+ {done, Body};
+ << Body:Pos/binary, "\r\n", Rest/bits >> ->
+ {done, Body, Rest};
+ << Body:Pos/binary, Rest/bits >> ->
+ {done, Body, Rest}
+ end
+ end
+ end.
+
+-ifdef(TEST).
+parse_test() ->
+ H1 = [{<<"content-type">>, <<"text/plain">>}],
+ Body1 = <<"This is the body of the message.">>,
+ H2 = lists:sort([{<<"content-type">>, <<"application/octet-stream">>},
+ {<<"content-transfer-encoding">>, <<"base64">>}]),
+ Body2 = <<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
+ "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
+ {ok, H1, Rest} = parse_headers(?TEST1_MIME, ?TEST1_BOUNDARY),
+ {done, Body1, Rest2} = parse_body(Rest, ?TEST1_BOUNDARY),
+ done = parse_body(Rest2, ?TEST1_BOUNDARY),
+ {ok, H2Unsorted, Rest3} = parse_headers(Rest2, ?TEST1_BOUNDARY),
+ H2 = lists:sort(H2Unsorted),
+ {done, Body2, Rest4} = parse_body(Rest3, ?TEST1_BOUNDARY),
+ done = parse_body(Rest4, ?TEST1_BOUNDARY),
+ {done, <<>>} = parse_headers(Rest4, ?TEST1_BOUNDARY),
+ ok.
+
+parse_interleaved_test() ->
+ H1 = [{<<"content-disposition">>, <<"form-data; name=\"submit-name\"">>}],
+ Body1 = <<"Larry">>,
+ H2 = lists:sort([{<<"content-disposition">>, <<"form-data; name=\"files\"">>},
+ {<<"content-type">>, <<"multipart/mixed; boundary=BbC04y">>}]),
+ InH1 = lists:sort([{<<"content-disposition">>, <<"file; filename=\"file1.txt\"">>},
+ {<<"content-type">>, <<"text/plain">>}]),
+ InBody1 = <<"... contents of file1.txt ...">>,
+ InH2 = lists:sort([{<<"content-disposition">>, <<"file; filename=\"file2.gif\"">>},
+ {<<"content-type">>, <<"image/gif">>},
+ {<<"content-transfer-encoding">>, <<"binary">>}]),
+ InBody2 = <<"...contents of file2.gif...">>,
+ {ok, H1, Rest} = parse_headers(?TEST2_MIME, ?TEST2_BOUNDARY),
+ {done, Body1, Rest2} = parse_body(Rest, ?TEST2_BOUNDARY),
+ done = parse_body(Rest2, ?TEST2_BOUNDARY),
+ {ok, H2Unsorted, Rest3} = parse_headers(Rest2, ?TEST2_BOUNDARY),
+ H2 = lists:sort(H2Unsorted),
+ {_, ContentType} = lists:keyfind(<<"content-type">>, 1, H2),
+ {<<"multipart">>, <<"mixed">>, [{<<"boundary">>, InBoundary}]}
+ = parse_content_type(ContentType),
+ {ok, InH1Unsorted, InRest} = parse_headers(Rest3, InBoundary),
+ InH1 = lists:sort(InH1Unsorted),
+ {done, InBody1, InRest2} = parse_body(InRest, InBoundary),
+ done = parse_body(InRest2, InBoundary),
+ {ok, InH2Unsorted, InRest3} = parse_headers(InRest2, InBoundary),
+ InH2 = lists:sort(InH2Unsorted),
+ {done, InBody2, InRest4} = parse_body(InRest3, InBoundary),
+ done = parse_body(InRest4, InBoundary),
+ {done, Rest4} = parse_headers(InRest4, InBoundary),
+ {done, <<>>} = parse_headers(Rest4, ?TEST2_BOUNDARY),
+ ok.
+
+parse_epilogue_test() ->
+ H1 = [{<<"content-type">>, <<"text/plain">>}],
+ Body1 = <<"This is the body of the message.">>,
+ Epilogue = <<"\r\nThis is the epilogue. Here it includes leading CRLF">>,
+ {ok, H1, Rest} = parse_headers(?TEST3_MIME, ?TEST3_BOUNDARY),
+ {done, Body1, Rest2} = parse_body(Rest, ?TEST3_BOUNDARY),
+ done = parse_body(Rest2, ?TEST3_BOUNDARY),
+ {done, Epilogue} = parse_headers(Rest2, ?TEST3_BOUNDARY),
+ ok.
+
+parse_epilogue_crlf_test() ->
+ H1 = [{<<"content-type">>, <<"text/plain">>}],
+ Body1 = <<"This is the body of the message.">>,
+ Epilogue = <<"\r\n">>,
+ {ok, H1, Rest} = parse_headers(?TEST4_MIME, ?TEST4_BOUNDARY),
+ {done, Body1, Rest2} = parse_body(Rest, ?TEST4_BOUNDARY),
+ done = parse_body(Rest2, ?TEST4_BOUNDARY),
+ {done, Epilogue} = parse_headers(Rest2, ?TEST4_BOUNDARY),
+ ok.
+
+parse_rfc2046_test() ->
+ %% The following is an example included in RFC 2046, Section 5.1.1.
+ Body1 = <<"This is implicitly typed plain US-ASCII text.\r\n"
+ "It does NOT end with a linebreak.">>,
+ Body2 = <<"This is explicitly typed plain US-ASCII text.\r\n"
+ "It DOES end with a linebreak.\r\n">>,
+ H2 = [{<<"content-type">>, <<"text/plain; charset=us-ascii">>}],
+ Epilogue = <<"\r\n\r\nThis is the epilogue. It is also to be ignored.">>,
+ {ok, [], Rest} = parse_headers(?TEST5_MIME, ?TEST5_BOUNDARY),
+ {done, Body1, Rest2} = parse_body(Rest, ?TEST5_BOUNDARY),
+ {ok, H2, Rest3} = parse_headers(Rest2, ?TEST5_BOUNDARY),
+ {done, Body2, Rest4} = parse_body(Rest3, ?TEST5_BOUNDARY),
+ {done, Epilogue} = parse_headers(Rest4, ?TEST5_BOUNDARY),
+ ok.
+
+parse_partial_test() ->
+ {ok, <<0:8000, "abcdef">>, <<"\rghij">>}
+ = parse_body(<<0:8000, "abcdef\rghij">>, <<"boundary">>),
+ {ok, <<"abcdef">>, <<"\rghij">>}
+ = parse_body(<<"abcdef\rghij">>, <<"boundary">>),
+ {ok, <<"abc">>, <<"\rdef">>}
+ = parse_body(<<"abc\rdef">>, <<"boundaryboundary">>),
+ {ok, <<0:8000, "abcdef">>, <<"\r\nghij">>}
+ = parse_body(<<0:8000, "abcdef\r\nghij">>, <<"boundary">>),
+ {ok, <<"abcdef">>, <<"\r\nghij">>}
+ = parse_body(<<"abcdef\r\nghij">>, <<"boundary">>),
+ {ok, <<"abc">>, <<"\r\ndef">>}
+ = parse_body(<<"abc\r\ndef">>, <<"boundaryboundary">>),
+ {ok, <<"boundary">>, <<"\r">>}
+ = parse_body(<<"boundary\r">>, <<"boundary">>),
+ {ok, <<"boundary">>, <<"\r\n">>}
+ = parse_body(<<"boundary\r\n">>, <<"boundary">>),
+ {ok, <<"boundary">>, <<"\r\n-">>}
+ = parse_body(<<"boundary\r\n-">>, <<"boundary">>),
+ {ok, <<"boundary">>, <<"\r\n--">>}
+ = parse_body(<<"boundary\r\n--">>, <<"boundary">>),
+ ok.
+
+perf_parse_multipart(Stream, Boundary) ->
+ case parse_headers(Stream, Boundary) of
+ {ok, _, Rest} ->
+ {_, _, Rest2} = parse_body(Rest, Boundary),
+ perf_parse_multipart(Rest2, Boundary);
+ {done, _} ->
+ ok
+ end.
+
+horse_parse() ->
+ horse:repeat(50000,
+ perf_parse_multipart(?TEST1_MIME, ?TEST1_BOUNDARY)
+ ).
+-endif.
+
+%% Building.
+
+%% @doc Generate a new random boundary.
+%%
+%% The boundary generated has a low probability of ever appearing
+%% in the data.
+
+-spec boundary() -> binary().
+boundary() ->
+ cow_base64url:encode(crypto:strong_rand_bytes(48), #{padding => false}).
+
+%% @doc Return the first part's head.
+%%
+%% This works exactly like the part/2 function except there is
+%% no leading \r\n. It's not required to use this function,
+%% just makes the output a little smaller and prettier.
+
+-spec first_part(binary(), headers()) -> iodata().
+first_part(Boundary, Headers) ->
+ [<<"--">>, Boundary, <<"\r\n">>, headers_to_iolist(Headers, [])].
+
+%% @doc Return a part's head.
+
+-spec part(binary(), headers()) -> iodata().
+part(Boundary, Headers) ->
+ [<<"\r\n--">>, Boundary, <<"\r\n">>, headers_to_iolist(Headers, [])].
+
+headers_to_iolist([], Acc) ->
+ lists:reverse([<<"\r\n">>|Acc]);
+headers_to_iolist([{N, V}|Tail], Acc) ->
+ %% We don't want to create a sublist so we list the
+ %% values in reverse order so that it gets reversed properly.
+ headers_to_iolist(Tail, [<<"\r\n">>, V, <<": ">>, N|Acc]).
+
+%% @doc Return the closing delimiter of the multipart message.
+
+-spec close(binary()) -> iodata().
+close(Boundary) ->
+ [<<"\r\n--">>, Boundary, <<"--">>].
+
+-ifdef(TEST).
+build_test() ->
+ Result = string:to_lower(binary_to_list(?TEST1_MIME)),
+ Result = string:to_lower(binary_to_list(iolist_to_binary([
+ <<"This is a message with multiple parts in MIME format.\r\n">>,
+ first_part(?TEST1_BOUNDARY, [{<<"content-type">>, <<"text/plain">>}]),
+ <<"This is the body of the message.">>,
+ part(?TEST1_BOUNDARY, [
+ {<<"content-type">>, <<"application/octet-stream">>},
+ {<<"content-transfer-encoding">>, <<"base64">>}]),
+ <<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
+ "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
+ close(?TEST1_BOUNDARY)
+ ]))),
+ ok.
+
+identity_test() ->
+ B = boundary(),
+ Preamble = <<"This is a message with multiple parts in MIME format.">>,
+ H1 = [{<<"content-type">>, <<"text/plain">>}],
+ Body1 = <<"This is the body of the message.">>,
+ H2 = lists:sort([{<<"content-type">>, <<"application/octet-stream">>},
+ {<<"content-transfer-encoding">>, <<"base64">>}]),
+ Body2 = <<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
+ "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
+ Epilogue = <<"Gotta go fast!">>,
+ M = iolist_to_binary([
+ Preamble,
+ part(B, H1), Body1,
+ part(B, H2), Body2,
+ close(B),
+ Epilogue
+ ]),
+ {done, Preamble, M2} = parse_body(M, B),
+ {ok, H1, M3} = parse_headers(M2, B),
+ {done, Body1, M4} = parse_body(M3, B),
+ {ok, H2Unsorted, M5} = parse_headers(M4, B),
+ H2 = lists:sort(H2Unsorted),
+ {done, Body2, M6} = parse_body(M5, B),
+ {done, Epilogue} = parse_headers(M6, B),
+ ok.
+
+perf_build_multipart() ->
+ B = boundary(),
+ [
+ <<"preamble\r\n">>,
+ first_part(B, [{<<"content-type">>, <<"text/plain">>}]),
+ <<"This is the body of the message.">>,
+ part(B, [
+ {<<"content-type">>, <<"application/octet-stream">>},
+ {<<"content-transfer-encoding">>, <<"base64">>}]),
+ <<"PGh0bWw+CiAgPGhlYWQ+CiAgPC9oZWFkPgogIDxib2R5PgogICAgPHA+VGhpcyBpcyB0aGUg\r\n"
+ "Ym9keSBvZiB0aGUgbWVzc2FnZS48L3A+CiAgPC9ib2R5Pgo8L2h0bWw+Cg==">>,
+ close(B),
+ <<"epilogue">>
+ ].
+
+horse_build() ->
+ horse:repeat(50000,
+ perf_build_multipart()
+ ).
+-endif.
+
+%% Headers.
+
+%% @doc Convenience function for extracting information from headers
+%% when parsing a multipart/form-data stream.
+
+-spec form_data(headers() | #{binary() => binary()})
+ -> {data, binary()}
+ | {file, binary(), binary(), binary()}.
+form_data(Headers) when is_map(Headers) ->
+ form_data(maps:to_list(Headers));
+form_data(Headers) ->
+ {_, DispositionBin} = lists:keyfind(<<"content-disposition">>, 1, Headers),
+ {<<"form-data">>, Params} = parse_content_disposition(DispositionBin),
+ {_, FieldName} = lists:keyfind(<<"name">>, 1, Params),
+ case lists:keyfind(<<"filename">>, 1, Params) of
+ false ->
+ {data, FieldName};
+ {_, Filename} ->
+ Type = case lists:keyfind(<<"content-type">>, 1, Headers) of
+ false -> <<"text/plain">>;
+ {_, T} -> T
+ end,
+ {file, FieldName, Filename, Type}
+ end.
+
+-ifdef(TEST).
+form_data_test_() ->
+ Tests = [
+ {[{<<"content-disposition">>, <<"form-data; name=\"submit-name\"">>}],
+ {data, <<"submit-name">>}},
+ {[{<<"content-disposition">>,
+ <<"form-data; name=\"files\"; filename=\"file1.txt\"">>},
+ {<<"content-type">>, <<"text/x-plain">>}],
+ {file, <<"files">>, <<"file1.txt">>, <<"text/x-plain">>}}
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])),
+ fun() -> R = form_data(V) end} || {V, R} <- Tests].
+-endif.
+
+%% @todo parse_content_description
+%% @todo parse_content_id
+
+%% @doc Parse an RFC 2183 content-disposition value.
+%% @todo Support RFC 2231.
+
+-spec parse_content_disposition(binary())
+ -> {binary(), [{binary(), binary()}]}.
+parse_content_disposition(Bin) ->
+ parse_cd_type(Bin, <<>>).
+
+parse_cd_type(<<>>, Acc) ->
+ {Acc, []};
+parse_cd_type(<< C, Rest/bits >>, Acc) ->
+ case C of
+ $; -> {Acc, parse_before_param(Rest, [])};
+ $\s -> {Acc, parse_before_param(Rest, [])};
+ $\t -> {Acc, parse_before_param(Rest, [])};
+ _ -> ?LOWER(parse_cd_type, Rest, Acc)
+ end.
+
+-ifdef(TEST).
+parse_content_disposition_test_() ->
+ Tests = [
+ {<<"inline">>, {<<"inline">>, []}},
+ {<<"attachment">>, {<<"attachment">>, []}},
+ {<<"attachment; filename=genome.jpeg;"
+ " modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";">>,
+ {<<"attachment">>, [
+ {<<"filename">>, <<"genome.jpeg">>},
+ {<<"modification-date">>, <<"Wed, 12 Feb 1997 16:29:51 -0500">>}
+ ]}},
+ {<<"form-data; name=\"user\"">>,
+ {<<"form-data">>, [{<<"name">>, <<"user">>}]}},
+ {<<"form-data; NAME=\"submit-name\"">>,
+ {<<"form-data">>, [{<<"name">>, <<"submit-name">>}]}},
+ {<<"form-data; name=\"files\"; filename=\"file1.txt\"">>,
+ {<<"form-data">>, [
+ {<<"name">>, <<"files">>},
+ {<<"filename">>, <<"file1.txt">>}
+ ]}},
+ {<<"file; filename=\"file1.txt\"">>,
+ {<<"file">>, [{<<"filename">>, <<"file1.txt">>}]}},
+ {<<"file; filename=\"file2.gif\"">>,
+ {<<"file">>, [{<<"filename">>, <<"file2.gif">>}]}}
+ ],
+ [{V, fun() -> R = parse_content_disposition(V) end} || {V, R} <- Tests].
+
+horse_parse_content_disposition_attachment() ->
+ horse:repeat(100000,
+ parse_content_disposition(<<"attachment; filename=genome.jpeg;"
+ " modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";">>)
+ ).
+
+horse_parse_content_disposition_form_data() ->
+ horse:repeat(100000,
+ parse_content_disposition(
+ <<"form-data; name=\"files\"; filename=\"file1.txt\"">>)
+ ).
+
+horse_parse_content_disposition_inline() ->
+ horse:repeat(100000,
+ parse_content_disposition(<<"inline">>)
+ ).
+-endif.
+
+%% @doc Parse an RFC 2045 content-transfer-encoding header.
+
+-spec parse_content_transfer_encoding(binary()) -> binary().
+parse_content_transfer_encoding(Bin) ->
+ ?LOWER(Bin).
+
+-ifdef(TEST).
+parse_content_transfer_encoding_test_() ->
+ Tests = [
+ {<<"7bit">>, <<"7bit">>},
+ {<<"7BIT">>, <<"7bit">>},
+ {<<"8bit">>, <<"8bit">>},
+ {<<"binary">>, <<"binary">>},
+ {<<"quoted-printable">>, <<"quoted-printable">>},
+ {<<"base64">>, <<"base64">>},
+ {<<"Base64">>, <<"base64">>},
+ {<<"BASE64">>, <<"base64">>},
+ {<<"bAsE64">>, <<"base64">>}
+ ],
+ [{V, fun() -> R = parse_content_transfer_encoding(V) end}
+ || {V, R} <- Tests].
+
+horse_parse_content_transfer_encoding() ->
+ horse:repeat(100000,
+ parse_content_transfer_encoding(<<"QUOTED-PRINTABLE">>)
+ ).
+-endif.
+
+%% @doc Parse an RFC 2045 content-type header.
+
+-spec parse_content_type(binary())
+ -> {binary(), binary(), [{binary(), binary()}]}.
+parse_content_type(Bin) ->
+ parse_ct_type(Bin, <<>>).
+
+parse_ct_type(<< C, Rest/bits >>, Acc) ->
+ case C of
+ $/ -> parse_ct_subtype(Rest, Acc, <<>>);
+ _ -> ?LOWER(parse_ct_type, Rest, Acc)
+ end.
+
+parse_ct_subtype(<<>>, Type, Subtype) when Subtype =/= <<>> ->
+ {Type, Subtype, []};
+parse_ct_subtype(<< C, Rest/bits >>, Type, Acc) ->
+ case C of
+ $; -> {Type, Acc, parse_before_param(Rest, [])};
+ $\s -> {Type, Acc, parse_before_param(Rest, [])};
+ $\t -> {Type, Acc, parse_before_param(Rest, [])};
+ _ -> ?LOWER(parse_ct_subtype, Rest, Type, Acc)
+ end.
+
+-ifdef(TEST).
+parse_content_type_test_() ->
+ Tests = [
+ {<<"image/gif">>,
+ {<<"image">>, <<"gif">>, []}},
+ {<<"text/plain">>,
+ {<<"text">>, <<"plain">>, []}},
+ {<<"text/plain; charset=us-ascii">>,
+ {<<"text">>, <<"plain">>, [{<<"charset">>, <<"us-ascii">>}]}},
+ {<<"text/plain; charset=\"us-ascii\"">>,
+ {<<"text">>, <<"plain">>, [{<<"charset">>, <<"us-ascii">>}]}},
+ {<<"multipart/form-data; boundary=AaB03x">>,
+ {<<"multipart">>, <<"form-data">>,
+ [{<<"boundary">>, <<"AaB03x">>}]}},
+ {<<"multipart/mixed; boundary=BbC04y">>,
+ {<<"multipart">>, <<"mixed">>, [{<<"boundary">>, <<"BbC04y">>}]}},
+ {<<"multipart/mixed; boundary=--------">>,
+ {<<"multipart">>, <<"mixed">>, [{<<"boundary">>, <<"--------">>}]}},
+ {<<"application/x-horse; filename=genome.jpeg;"
+ " some-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";"
+ " charset=us-ascii; empty=; number=12345">>,
+ {<<"application">>, <<"x-horse">>, [
+ {<<"filename">>, <<"genome.jpeg">>},
+ {<<"some-date">>, <<"Wed, 12 Feb 1997 16:29:51 -0500">>},
+ {<<"charset">>, <<"us-ascii">>},
+ {<<"empty">>, <<>>},
+ {<<"number">>, <<"12345">>}
+ ]}}
+ ],
+ [{V, fun() -> R = parse_content_type(V) end}
+ || {V, R} <- Tests].
+
+horse_parse_content_type_zero() ->
+ horse:repeat(100000,
+ parse_content_type(<<"text/plain">>)
+ ).
+
+horse_parse_content_type_one() ->
+ horse:repeat(100000,
+ parse_content_type(<<"text/plain; charset=\"us-ascii\"">>)
+ ).
+
+horse_parse_content_type_five() ->
+ horse:repeat(100000,
+ parse_content_type(<<"application/x-horse; filename=genome.jpeg;"
+ " some-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";"
+ " charset=us-ascii; empty=; number=12345">>)
+ ).
+-endif.
+
+%% @doc Parse RFC 2045 parameters.
+
+parse_before_param(<<>>, Params) ->
+ lists:reverse(Params);
+parse_before_param(<< C, Rest/bits >>, Params) ->
+ case C of
+ $; -> parse_before_param(Rest, Params);
+ $\s -> parse_before_param(Rest, Params);
+ $\t -> parse_before_param(Rest, Params);
+ _ -> ?LOWER(parse_param_name, Rest, Params, <<>>)
+ end.
+
+parse_param_name(<<>>, Params, Acc) ->
+ lists:reverse([{Acc, <<>>}|Params]);
+parse_param_name(<< C, Rest/bits >>, Params, Acc) ->
+ case C of
+ $= -> parse_param_value(Rest, Params, Acc);
+ _ -> ?LOWER(parse_param_name, Rest, Params, Acc)
+ end.
+
+parse_param_value(<<>>, Params, Name) ->
+ lists:reverse([{Name, <<>>}|Params]);
+parse_param_value(<< C, Rest/bits >>, Params, Name) ->
+ case C of
+ $" -> parse_param_quoted_value(Rest, Params, Name, <<>>);
+ $; -> parse_before_param(Rest, [{Name, <<>>}|Params]);
+ $\s -> parse_before_param(Rest, [{Name, <<>>}|Params]);
+ $\t -> parse_before_param(Rest, [{Name, <<>>}|Params]);
+ C -> parse_param_value(Rest, Params, Name, << C >>)
+ end.
+
+parse_param_value(<<>>, Params, Name, Acc) ->
+ lists:reverse([{Name, Acc}|Params]);
+parse_param_value(<< C, Rest/bits >>, Params, Name, Acc) ->
+ case C of
+ $; -> parse_before_param(Rest, [{Name, Acc}|Params]);
+ $\s -> parse_before_param(Rest, [{Name, Acc}|Params]);
+ $\t -> parse_before_param(Rest, [{Name, Acc}|Params]);
+ C -> parse_param_value(Rest, Params, Name, << Acc/binary, C >>)
+ end.
+
+%% We expect a final $" so no need to test for <<>>.
+parse_param_quoted_value(<< $\\, C, Rest/bits >>, Params, Name, Acc) ->
+ parse_param_quoted_value(Rest, Params, Name, << Acc/binary, C >>);
+parse_param_quoted_value(<< $", Rest/bits >>, Params, Name, Acc) ->
+ parse_before_param(Rest, [{Name, Acc}|Params]);
+parse_param_quoted_value(<< C, Rest/bits >>, Params, Name, Acc)
+ when C =/= $\r ->
+ parse_param_quoted_value(Rest, Params, Name, << Acc/binary, C >>).
diff --git a/server/_build/default/lib/cowlib/src/cow_qs.erl b/server/_build/default/lib/cowlib/src/cow_qs.erl
new file mode 100644
index 0000000..442ecc8
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_qs.erl
@@ -0,0 +1,563 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_qs).
+
+-export([parse_qs/1]).
+-export([qs/1]).
+-export([urldecode/1]).
+-export([urlencode/1]).
+
+-type qs_vals() :: [{binary(), binary() | true}].
+
+%% @doc Parse an application/x-www-form-urlencoded string.
+%%
+%% The percent decoding is inlined to greatly improve the performance
+%% by avoiding copying binaries twice (once for extracting, once for
+%% decoding) instead of just extracting the proper representation.
+
+-spec parse_qs(binary()) -> qs_vals().
+parse_qs(B) ->
+ parse_qs_name(B, [], <<>>).
+
+parse_qs_name(<< $%, H, L, Rest/bits >>, Acc, Name) ->
+ C = (unhex(H) bsl 4 bor unhex(L)),
+ parse_qs_name(Rest, Acc, << Name/bits, C >>);
+parse_qs_name(<< $+, Rest/bits >>, Acc, Name) ->
+ parse_qs_name(Rest, Acc, << Name/bits, " " >>);
+parse_qs_name(<< $=, Rest/bits >>, Acc, Name) when Name =/= <<>> ->
+ parse_qs_value(Rest, Acc, Name, <<>>);
+parse_qs_name(<< $&, Rest/bits >>, Acc, Name) ->
+ case Name of
+ <<>> -> parse_qs_name(Rest, Acc, <<>>);
+ _ -> parse_qs_name(Rest, [{Name, true}|Acc], <<>>)
+ end;
+parse_qs_name(<< C, Rest/bits >>, Acc, Name) when C =/= $%, C =/= $= ->
+ parse_qs_name(Rest, Acc, << Name/bits, C >>);
+parse_qs_name(<<>>, Acc, Name) ->
+ case Name of
+ <<>> -> lists:reverse(Acc);
+ _ -> lists:reverse([{Name, true}|Acc])
+ end.
+
+parse_qs_value(<< $%, H, L, Rest/bits >>, Acc, Name, Value) ->
+ C = (unhex(H) bsl 4 bor unhex(L)),
+ parse_qs_value(Rest, Acc, Name, << Value/bits, C >>);
+parse_qs_value(<< $+, Rest/bits >>, Acc, Name, Value) ->
+ parse_qs_value(Rest, Acc, Name, << Value/bits, " " >>);
+parse_qs_value(<< $&, Rest/bits >>, Acc, Name, Value) ->
+ parse_qs_name(Rest, [{Name, Value}|Acc], <<>>);
+parse_qs_value(<< C, Rest/bits >>, Acc, Name, Value) when C =/= $% ->
+ parse_qs_value(Rest, Acc, Name, << Value/bits, C >>);
+parse_qs_value(<<>>, Acc, Name, Value) ->
+ lists:reverse([{Name, Value}|Acc]).
+
+-ifdef(TEST).
+parse_qs_test_() ->
+ Tests = [
+ {<<>>, []},
+ {<<"&">>, []},
+ {<<"a">>, [{<<"a">>, true}]},
+ {<<"a&">>, [{<<"a">>, true}]},
+ {<<"&a">>, [{<<"a">>, true}]},
+ {<<"a&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
+ {<<"a&&b">>, [{<<"a">>, true}, {<<"b">>, true}]},
+ {<<"a&b&">>, [{<<"a">>, true}, {<<"b">>, true}]},
+ {<<"=">>, error},
+ {<<"=b">>, error},
+ {<<"a=">>, [{<<"a">>, <<>>}]},
+ {<<"a=b">>, [{<<"a">>, <<"b">>}]},
+ {<<"a=&b=">>, [{<<"a">>, <<>>}, {<<"b">>, <<>>}]},
+ {<<"a=b&c&d=e">>, [{<<"a">>, <<"b">>},
+ {<<"c">>, true}, {<<"d">>, <<"e">>}]},
+ {<<"a=b=c&d=e=f&g=h=i">>, [{<<"a">>, <<"b=c">>},
+ {<<"d">>, <<"e=f">>}, {<<"g">>, <<"h=i">>}]},
+ {<<"+">>, [{<<" ">>, true}]},
+ {<<"+=+">>, [{<<" ">>, <<" ">>}]},
+ {<<"a+b=c+d">>, [{<<"a b">>, <<"c d">>}]},
+ {<<"+a+=+b+&+c+=+d+">>, [{<<" a ">>, <<" b ">>},
+ {<<" c ">>, <<" d ">>}]},
+ {<<"a%20b=c%20d">>, [{<<"a b">>, <<"c d">>}]},
+ {<<"%25%26%3D=%25%26%3D&_-.=.-_">>, [{<<"%&=">>, <<"%&=">>},
+ {<<"_-.">>, <<".-_">>}]},
+ {<<"for=extend%2Franch">>, [{<<"for">>, <<"extend/ranch">>}]}
+ ],
+ [{Qs, fun() ->
+ E = try parse_qs(Qs) of
+ R -> R
+ catch _:_ ->
+ error
+ end
+ end} || {Qs, E} <- Tests].
+
+parse_qs_identity_test_() ->
+ Tests = [
+ <<"+">>,
+ <<"hl=en&q=erlang+cowboy">>,
+ <<"direction=desc&for=extend%2Franch&sort=updated&state=open">>,
+ <<"i=EWiIXmPj5gl6&v=QowBp0oDLQXdd4x_GwiywA&ip=98.20.31.81&"
+ "la=en&pg=New8.undertonebrandsafe.com%2F698a2525065ee2"
+ "60c0b2f2aaad89ab82&re=&sz=1&fc=1&fr=140&br=3&bv=11.0."
+ "696.16&os=3&ov=&rs=vpl&k=cookies%7Csale%7Cbrowser%7Cm"
+ "ore%7Cprivacy%7Cstatistics%7Cactivities%7Cauction%7Ce"
+ "mail%7Cfree%7Cin...&t=112373&xt=5%7C61%7C0&tz=-1&ev=x"
+ "&tk=&za=1&ortb-za=1&zu=&zl=&ax=U&ay=U&ortb-pid=536454"
+ ".55&ortb-sid=112373.8&seats=999&ortb-xt=IAB24&ortb-ugc=">>,
+ <<"i=9pQNskA&v=0ySQQd1F&ev=12345678&t=12345&sz=3&ip=67.58."
+ "236.89&la=en&pg=http%3A%2F%2Fwww.yahoo.com%2Fpage1.ht"
+ "m&re=http%3A%2F%2Fsearch.google.com&fc=1&fr=1&br=2&bv"
+ "=3.0.14&os=1&ov=XP&k=cars%2Cford&rs=js&xt=5%7C22%7C23"
+ "4&tz=%2B180&tk=key1%3Dvalue1%7Ckey2%3Dvalue2&zl=4%2C5"
+ "%2C6&za=4&zu=competitor.com&ua=Mozilla%2F5.0+%28Windo"
+ "ws%3B+U%3B+Windows+NT+6.1%3B+en-US%29+AppleWebKit%2F5"
+ "34.13+%28KHTML%2C+like+Gecko%29+Chrome%2F9.0.597.98+S"
+ "afari%2F534.13&ortb-za=1%2C6%2C13&ortb-pid=521732&ort"
+ "b-sid=521732&ortb-xt=IAB3&ortb-ugc=">>
+ ],
+ [{V, fun() -> V = qs(parse_qs(V)) end} || V <- Tests].
+
+horse_parse_qs_shorter() ->
+ horse:repeat(20000,
+ parse_qs(<<"hl=en&q=erlang%20cowboy">>)
+ ).
+
+horse_parse_qs_short() ->
+ horse:repeat(20000,
+ parse_qs(
+ <<"direction=desc&for=extend%2Franch&sort=updated&state=open">>)
+ ).
+
+horse_parse_qs_long() ->
+ horse:repeat(20000,
+ parse_qs(<<"i=EWiIXmPj5gl6&v=QowBp0oDLQXdd4x_GwiywA&ip=98.20.31.81&"
+ "la=en&pg=New8.undertonebrandsafe.com%2F698a2525065ee260c0b2f2a"
+ "aad89ab82&re=&sz=1&fc=1&fr=140&br=3&bv=11.0.696.16&os=3&ov=&rs"
+ "=vpl&k=cookies%7Csale%7Cbrowser%7Cmore%7Cprivacy%7Cstatistics%"
+ "7Cactivities%7Cauction%7Cemail%7Cfree%7Cin...&t=112373&xt=5%7C"
+ "61%7C0&tz=-1&ev=x&tk=&za=1&ortb-za=1&zu=&zl=&ax=U&ay=U&ortb-pi"
+ "d=536454.55&ortb-sid=112373.8&seats=999&ortb-xt=IAB24&ortb-ugc"
+ "=">>)
+ ).
+
+horse_parse_qs_longer() ->
+ horse:repeat(20000,
+ parse_qs(<<"i=9pQNskA&v=0ySQQd1F&ev=12345678&t=12345&sz=3&ip=67.58."
+ "236.89&la=en&pg=http%3A%2F%2Fwww.yahoo.com%2Fpage1.htm&re=http"
+ "%3A%2F%2Fsearch.google.com&fc=1&fr=1&br=2&bv=3.0.14&os=1&ov=XP"
+ "&k=cars%2cford&rs=js&xt=5%7c22%7c234&tz=%2b180&tk=key1%3Dvalue"
+ "1%7Ckey2%3Dvalue2&zl=4,5,6&za=4&zu=competitor.com&ua=Mozilla%2"
+ "F5.0%20(Windows%3B%20U%3B%20Windows%20NT%206.1%3B%20en-US)%20A"
+ "ppleWebKit%2F534.13%20(KHTML%2C%20like%20Gecko)%20Chrome%2F9.0"
+ ".597.98%20Safari%2F534.13&ortb-za=1%2C6%2C13&ortb-pid=521732&o"
+ "rtb-sid=521732&ortb-xt=IAB3&ortb-ugc=">>)
+ ).
+-endif.
+
+%% @doc Build an application/x-www-form-urlencoded string.
+
+-spec qs(qs_vals()) -> binary().
+qs([]) ->
+ <<>>;
+qs(L) ->
+ qs(L, <<>>).
+
+qs([], Acc) ->
+ << $&, Qs/bits >> = Acc,
+ Qs;
+qs([{Name, true}|Tail], Acc) ->
+ Acc2 = urlencode(Name, << Acc/bits, $& >>),
+ qs(Tail, Acc2);
+qs([{Name, Value}|Tail], Acc) ->
+ Acc2 = urlencode(Name, << Acc/bits, $& >>),
+ Acc3 = urlencode(Value, << Acc2/bits, $= >>),
+ qs(Tail, Acc3).
+
+-define(QS_SHORTER, [
+ {<<"hl">>, <<"en">>},
+ {<<"q">>, <<"erlang cowboy">>}
+]).
+
+-define(QS_SHORT, [
+ {<<"direction">>, <<"desc">>},
+ {<<"for">>, <<"extend/ranch">>},
+ {<<"sort">>, <<"updated">>},
+ {<<"state">>, <<"open">>}
+]).
+
+-define(QS_LONG, [
+ {<<"i">>, <<"EWiIXmPj5gl6">>},
+ {<<"v">>, <<"QowBp0oDLQXdd4x_GwiywA">>},
+ {<<"ip">>, <<"98.20.31.81">>},
+ {<<"la">>, <<"en">>},
+ {<<"pg">>, <<"New8.undertonebrandsafe.com/"
+ "698a2525065ee260c0b2f2aaad89ab82">>},
+ {<<"re">>, <<>>},
+ {<<"sz">>, <<"1">>},
+ {<<"fc">>, <<"1">>},
+ {<<"fr">>, <<"140">>},
+ {<<"br">>, <<"3">>},
+ {<<"bv">>, <<"11.0.696.16">>},
+ {<<"os">>, <<"3">>},
+ {<<"ov">>, <<>>},
+ {<<"rs">>, <<"vpl">>},
+ {<<"k">>, <<"cookies|sale|browser|more|privacy|statistics|"
+ "activities|auction|email|free|in...">>},
+ {<<"t">>, <<"112373">>},
+ {<<"xt">>, <<"5|61|0">>},
+ {<<"tz">>, <<"-1">>},
+ {<<"ev">>, <<"x">>},
+ {<<"tk">>, <<>>},
+ {<<"za">>, <<"1">>},
+ {<<"ortb-za">>, <<"1">>},
+ {<<"zu">>, <<>>},
+ {<<"zl">>, <<>>},
+ {<<"ax">>, <<"U">>},
+ {<<"ay">>, <<"U">>},
+ {<<"ortb-pid">>, <<"536454.55">>},
+ {<<"ortb-sid">>, <<"112373.8">>},
+ {<<"seats">>, <<"999">>},
+ {<<"ortb-xt">>, <<"IAB24">>},
+ {<<"ortb-ugc">>, <<>>}
+]).
+
+-define(QS_LONGER, [
+ {<<"i">>, <<"9pQNskA">>},
+ {<<"v">>, <<"0ySQQd1F">>},
+ {<<"ev">>, <<"12345678">>},
+ {<<"t">>, <<"12345">>},
+ {<<"sz">>, <<"3">>},
+ {<<"ip">>, <<"67.58.236.89">>},
+ {<<"la">>, <<"en">>},
+ {<<"pg">>, <<"http://www.yahoo.com/page1.htm">>},
+ {<<"re">>, <<"http://search.google.com">>},
+ {<<"fc">>, <<"1">>},
+ {<<"fr">>, <<"1">>},
+ {<<"br">>, <<"2">>},
+ {<<"bv">>, <<"3.0.14">>},
+ {<<"os">>, <<"1">>},
+ {<<"ov">>, <<"XP">>},
+ {<<"k">>, <<"cars,ford">>},
+ {<<"rs">>, <<"js">>},
+ {<<"xt">>, <<"5|22|234">>},
+ {<<"tz">>, <<"+180">>},
+ {<<"tk">>, <<"key1=value1|key2=value2">>},
+ {<<"zl">>, <<"4,5,6">>},
+ {<<"za">>, <<"4">>},
+ {<<"zu">>, <<"competitor.com">>},
+ {<<"ua">>, <<"Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) "
+ "AppleWebKit/534.13 (KHTML, like Gecko) Chrome/9.0.597.98 "
+ "Safari/534.13">>},
+ {<<"ortb-za">>, <<"1,6,13">>},
+ {<<"ortb-pid">>, <<"521732">>},
+ {<<"ortb-sid">>, <<"521732">>},
+ {<<"ortb-xt">>, <<"IAB3">>},
+ {<<"ortb-ugc">>, <<>>}
+]).
+
+-ifdef(TEST).
+qs_test_() ->
+ Tests = [
+ {[<<"a">>], error},
+ {[{<<"a">>, <<"b">>, <<"c">>}], error},
+ {[], <<>>},
+ {[{<<"a">>, true}], <<"a">>},
+ {[{<<"a">>, true}, {<<"b">>, true}], <<"a&b">>},
+ {[{<<"a">>, <<>>}], <<"a=">>},
+ {[{<<"a">>, <<"b">>}], <<"a=b">>},
+ {[{<<"a">>, <<>>}, {<<"b">>, <<>>}], <<"a=&b=">>},
+ {[{<<"a">>, <<"b">>}, {<<"c">>, true}, {<<"d">>, <<"e">>}],
+ <<"a=b&c&d=e">>},
+ {[{<<"a">>, <<"b=c">>}, {<<"d">>, <<"e=f">>}, {<<"g">>, <<"h=i">>}],
+ <<"a=b%3Dc&d=e%3Df&g=h%3Di">>},
+ {[{<<" ">>, true}], <<"+">>},
+ {[{<<" ">>, <<" ">>}], <<"+=+">>},
+ {[{<<"a b">>, <<"c d">>}], <<"a+b=c+d">>},
+ {[{<<" a ">>, <<" b ">>}, {<<" c ">>, <<" d ">>}],
+ <<"+a+=+b+&+c+=+d+">>},
+ {[{<<"%&=">>, <<"%&=">>}, {<<"_-.">>, <<".-_">>}],
+ <<"%25%26%3D=%25%26%3D&_-.=.-_">>},
+ {[{<<"for">>, <<"extend/ranch">>}], <<"for=extend%2Franch">>}
+ ],
+ [{lists:flatten(io_lib:format("~p", [Vals])), fun() ->
+ E = try qs(Vals) of
+ R -> R
+ catch _:_ ->
+ error
+ end
+ end} || {Vals, E} <- Tests].
+
+qs_identity_test_() ->
+ Tests = [
+ [{<<"+">>, true}],
+ ?QS_SHORTER,
+ ?QS_SHORT,
+ ?QS_LONG,
+ ?QS_LONGER
+ ],
+ [{lists:flatten(io_lib:format("~p", [V])), fun() ->
+ V = parse_qs(qs(V))
+ end} || V <- Tests].
+
+horse_qs_shorter() ->
+ horse:repeat(20000, qs(?QS_SHORTER)).
+
+horse_qs_short() ->
+ horse:repeat(20000, qs(?QS_SHORT)).
+
+horse_qs_long() ->
+ horse:repeat(20000, qs(?QS_LONG)).
+
+horse_qs_longer() ->
+ horse:repeat(20000, qs(?QS_LONGER)).
+-endif.
+
+%% @doc Decode a percent encoded string (x-www-form-urlencoded rules).
+
+-spec urldecode(B) -> B when B::binary().
+urldecode(B) ->
+ urldecode(B, <<>>).
+
+urldecode(<< $%, H, L, Rest/bits >>, Acc) ->
+ C = (unhex(H) bsl 4 bor unhex(L)),
+ urldecode(Rest, << Acc/bits, C >>);
+urldecode(<< $+, Rest/bits >>, Acc) ->
+ urldecode(Rest, << Acc/bits, " " >>);
+urldecode(<< C, Rest/bits >>, Acc) when C =/= $% ->
+ urldecode(Rest, << Acc/bits, C >>);
+urldecode(<<>>, Acc) ->
+ Acc.
+
+unhex($0) -> 0;
+unhex($1) -> 1;
+unhex($2) -> 2;
+unhex($3) -> 3;
+unhex($4) -> 4;
+unhex($5) -> 5;
+unhex($6) -> 6;
+unhex($7) -> 7;
+unhex($8) -> 8;
+unhex($9) -> 9;
+unhex($A) -> 10;
+unhex($B) -> 11;
+unhex($C) -> 12;
+unhex($D) -> 13;
+unhex($E) -> 14;
+unhex($F) -> 15;
+unhex($a) -> 10;
+unhex($b) -> 11;
+unhex($c) -> 12;
+unhex($d) -> 13;
+unhex($e) -> 14;
+unhex($f) -> 15.
+
+-ifdef(TEST).
+urldecode_test_() ->
+ Tests = [
+ {<<"%20">>, <<" ">>},
+ {<<"+">>, <<" ">>},
+ {<<"%00">>, <<0>>},
+ {<<"%fF">>, <<255>>},
+ {<<"123">>, <<"123">>},
+ {<<"%i5">>, error},
+ {<<"%5">>, error}
+ ],
+ [{Qs, fun() ->
+ E = try urldecode(Qs) of
+ R -> R
+ catch _:_ ->
+ error
+ end
+ end} || {Qs, E} <- Tests].
+
+urldecode_identity_test_() ->
+ Tests = [
+ <<"+">>,
+ <<"nothingnothingnothingnothing">>,
+ <<"Small+fast+modular+HTTP+server">>,
+ <<"Small%2C+fast%2C+modular+HTTP+server.">>,
+ <<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
+ "%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
+ "%BE%8B%E3%80%9C">>
+ ],
+ [{V, fun() -> V = urlencode(urldecode(V)) end} || V <- Tests].
+
+horse_urldecode() ->
+ horse:repeat(100000,
+ urldecode(<<"nothingnothingnothingnothing">>)
+ ).
+
+horse_urldecode_plus() ->
+ horse:repeat(100000,
+ urldecode(<<"Small+fast+modular+HTTP+server">>)
+ ).
+
+horse_urldecode_hex() ->
+ horse:repeat(100000,
+ urldecode(<<"Small%2C%20fast%2C%20modular%20HTTP%20server.">>)
+ ).
+
+horse_urldecode_jp_hex() ->
+ horse:repeat(100000,
+ urldecode(<<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
+ "%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
+ "%BE%8B%E3%80%9C">>)
+ ).
+
+horse_urldecode_mix() ->
+ horse:repeat(100000,
+ urldecode(<<"Small%2C+fast%2C+modular+HTTP+server.">>)
+ ).
+-endif.
+
+%% @doc Percent encode a string (x-www-form-urlencoded rules).
+
+-spec urlencode(B) -> B when B::binary().
+urlencode(B) ->
+ urlencode(B, <<>>).
+
+urlencode(<< $\s, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $+ >>);
+urlencode(<< $-, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $- >>);
+urlencode(<< $., Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $. >>);
+urlencode(<< $0, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $0 >>);
+urlencode(<< $1, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $1 >>);
+urlencode(<< $2, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $2 >>);
+urlencode(<< $3, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $3 >>);
+urlencode(<< $4, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $4 >>);
+urlencode(<< $5, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $5 >>);
+urlencode(<< $6, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $6 >>);
+urlencode(<< $7, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $7 >>);
+urlencode(<< $8, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $8 >>);
+urlencode(<< $9, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $9 >>);
+urlencode(<< $A, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $A >>);
+urlencode(<< $B, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $B >>);
+urlencode(<< $C, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $C >>);
+urlencode(<< $D, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $D >>);
+urlencode(<< $E, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $E >>);
+urlencode(<< $F, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $F >>);
+urlencode(<< $G, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $G >>);
+urlencode(<< $H, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $H >>);
+urlencode(<< $I, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $I >>);
+urlencode(<< $J, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $J >>);
+urlencode(<< $K, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $K >>);
+urlencode(<< $L, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $L >>);
+urlencode(<< $M, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $M >>);
+urlencode(<< $N, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $N >>);
+urlencode(<< $O, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $O >>);
+urlencode(<< $P, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $P >>);
+urlencode(<< $Q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Q >>);
+urlencode(<< $R, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $R >>);
+urlencode(<< $S, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $S >>);
+urlencode(<< $T, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $T >>);
+urlencode(<< $U, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $U >>);
+urlencode(<< $V, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $V >>);
+urlencode(<< $W, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $W >>);
+urlencode(<< $X, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $X >>);
+urlencode(<< $Y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Y >>);
+urlencode(<< $Z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Z >>);
+urlencode(<< $_, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $_ >>);
+urlencode(<< $a, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $a >>);
+urlencode(<< $b, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $b >>);
+urlencode(<< $c, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $c >>);
+urlencode(<< $d, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $d >>);
+urlencode(<< $e, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $e >>);
+urlencode(<< $f, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $f >>);
+urlencode(<< $g, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $g >>);
+urlencode(<< $h, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $h >>);
+urlencode(<< $i, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $i >>);
+urlencode(<< $j, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $j >>);
+urlencode(<< $k, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $k >>);
+urlencode(<< $l, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $l >>);
+urlencode(<< $m, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $m >>);
+urlencode(<< $n, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $n >>);
+urlencode(<< $o, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $o >>);
+urlencode(<< $p, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $p >>);
+urlencode(<< $q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $q >>);
+urlencode(<< $r, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $r >>);
+urlencode(<< $s, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $s >>);
+urlencode(<< $t, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $t >>);
+urlencode(<< $u, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $u >>);
+urlencode(<< $v, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $v >>);
+urlencode(<< $w, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $w >>);
+urlencode(<< $x, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $x >>);
+urlencode(<< $y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $y >>);
+urlencode(<< $z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $z >>);
+urlencode(<< C, Rest/bits >>, Acc) ->
+ H = hex(C bsr 4),
+ L = hex(C band 16#0f),
+ urlencode(Rest, << Acc/bits, $%, H, L >>);
+urlencode(<<>>, Acc) ->
+ Acc.
+
+hex( 0) -> $0;
+hex( 1) -> $1;
+hex( 2) -> $2;
+hex( 3) -> $3;
+hex( 4) -> $4;
+hex( 5) -> $5;
+hex( 6) -> $6;
+hex( 7) -> $7;
+hex( 8) -> $8;
+hex( 9) -> $9;
+hex(10) -> $A;
+hex(11) -> $B;
+hex(12) -> $C;
+hex(13) -> $D;
+hex(14) -> $E;
+hex(15) -> $F.
+
+-ifdef(TEST).
+urlencode_test_() ->
+ Tests = [
+ {<<255, 0>>, <<"%FF%00">>},
+ {<<255, " ">>, <<"%FF+">>},
+ {<<" ">>, <<"+">>},
+ {<<"aBc123">>, <<"aBc123">>},
+ {<<".-_">>, <<".-_">>}
+ ],
+ [{V, fun() -> E = urlencode(V) end} || {V, E} <- Tests].
+
+urlencode_identity_test_() ->
+ Tests = [
+ <<"+">>,
+ <<"nothingnothingnothingnothing">>,
+ <<"Small fast modular HTTP server">>,
+ <<"Small, fast, modular HTTP server.">>,
+ <<227,131,132,227,130,164,227,131,179,227,130,189,227,
+ 130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
+ 129,153,227,130,139,230,151,139,229,190,139,227,128,156>>
+ ],
+ [{V, fun() -> V = urldecode(urlencode(V)) end} || V <- Tests].
+
+horse_urlencode() ->
+ horse:repeat(100000,
+ urlencode(<<"nothingnothingnothingnothing">>)
+ ).
+
+horse_urlencode_plus() ->
+ horse:repeat(100000,
+ urlencode(<<"Small fast modular HTTP server">>)
+ ).
+
+horse_urlencode_jp() ->
+ horse:repeat(100000,
+ urlencode(<<227,131,132,227,130,164,227,131,179,227,130,189,227,
+ 130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
+ 129,153,227,130,139,230,151,139,229,190,139,227,128,156>>)
+ ).
+
+horse_urlencode_mix() ->
+ horse:repeat(100000,
+ urlencode(<<"Small, fast, modular HTTP server.">>)
+ ).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_spdy.erl b/server/_build/default/lib/cowlib/src/cow_spdy.erl
new file mode 100644
index 0000000..e7b4043
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_spdy.erl
@@ -0,0 +1,313 @@
+%% Copyright (c) 2013-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_spdy).
+
+%% Zstream.
+-export([deflate_init/0]).
+-export([inflate_init/0]).
+
+%% Parse.
+-export([split/1]).
+-export([parse/2]).
+
+%% Build.
+-export([data/3]).
+-export([syn_stream/12]).
+-export([syn_reply/6]).
+-export([rst_stream/2]).
+-export([settings/2]).
+-export([ping/1]).
+-export([goaway/2]).
+%% @todo headers
+%% @todo window_update
+
+-include("cow_spdy.hrl").
+
+%% Zstream.
+
+deflate_init() ->
+ Zdef = zlib:open(),
+ ok = zlib:deflateInit(Zdef),
+ _ = zlib:deflateSetDictionary(Zdef, ?ZDICT),
+ Zdef.
+
+inflate_init() ->
+ Zinf = zlib:open(),
+ ok = zlib:inflateInit(Zinf),
+ Zinf.
+
+%% Parse.
+
+split(Data = << _:40, Length:24, _/bits >>)
+ when byte_size(Data) >= Length + 8 ->
+ Length2 = Length + 8,
+ << Frame:Length2/binary, Rest/bits >> = Data,
+ {true, Frame, Rest};
+split(_) ->
+ false.
+
+parse(<< 0:1, StreamID:31, 0:7, IsFinFlag:1, _:24, Data/bits >>, _) ->
+ {data, StreamID, from_flag(IsFinFlag), Data};
+parse(<< 1:1, 3:15, 1:16, 0:6, IsUnidirectionalFlag:1, IsFinFlag:1,
+ _:25, StreamID:31, _:1, AssocToStreamID:31, Priority:3, _:5,
+ 0:8, Rest/bits >>, Zinf) ->
+ case parse_headers(Rest, Zinf) of
+ {ok, Headers, [{<<":host">>, Host}, {<<":method">>, Method},
+ {<<":path">>, Path}, {<<":scheme">>, Scheme},
+ {<<":version">>, Version}]} ->
+ {syn_stream, StreamID, AssocToStreamID, from_flag(IsFinFlag),
+ from_flag(IsUnidirectionalFlag), Priority, Method,
+ Scheme, Host, Path, Version, Headers};
+ _ ->
+ {error, badprotocol}
+ end;
+parse(<< 1:1, 3:15, 2:16, 0:7, IsFinFlag:1, _:25,
+ StreamID:31, Rest/bits >>, Zinf) ->
+ case parse_headers(Rest, Zinf) of
+ {ok, Headers, [{<<":status">>, Status}, {<<":version">>, Version}]} ->
+ {syn_reply, StreamID, from_flag(IsFinFlag),
+ Status, Version, Headers};
+ _ ->
+ {error, badprotocol}
+ end;
+parse(<< 1:1, 3:15, 3:16, 0:8, _:56, StatusCode:32 >>, _)
+ when StatusCode =:= 0; StatusCode > 11 ->
+ {error, badprotocol};
+parse(<< 1:1, 3:15, 3:16, 0:8, _:25, StreamID:31, StatusCode:32 >>, _) ->
+ Status = case StatusCode of
+ 1 -> protocol_error;
+ 2 -> invalid_stream;
+ 3 -> refused_stream;
+ 4 -> unsupported_version;
+ 5 -> cancel;
+ 6 -> internal_error;
+ 7 -> flow_control_error;
+ 8 -> stream_in_use;
+ 9 -> stream_already_closed;
+ 10 -> invalid_credentials;
+ 11 -> frame_too_large
+ end,
+ {rst_stream, StreamID, Status};
+parse(<< 1:1, 3:15, 4:16, 0:7, ClearSettingsFlag:1, _:24,
+ NbEntries:32, Rest/bits >>, _) ->
+ try
+ Settings = [begin
+ Is0 = 0,
+ Key = case ID of
+ 1 -> upload_bandwidth;
+ 2 -> download_bandwidth;
+ 3 -> round_trip_time;
+ 4 -> max_concurrent_streams;
+ 5 -> current_cwnd;
+ 6 -> download_retrans_rate;
+ 7 -> initial_window_size;
+ 8 -> client_certificate_vector_size
+ end,
+ {Key, Value, from_flag(PersistFlag), from_flag(WasPersistedFlag)}
+ end || << Is0:6, WasPersistedFlag:1, PersistFlag:1,
+ ID:24, Value:32 >> <= Rest],
+ NbEntries = length(Settings),
+ {settings, from_flag(ClearSettingsFlag), Settings}
+ catch _:_ ->
+ {error, badprotocol}
+ end;
+parse(<< 1:1, 3:15, 6:16, 0:8, _:24, PingID:32 >>, _) ->
+ {ping, PingID};
+parse(<< 1:1, 3:15, 7:16, 0:8, _:56, StatusCode:32 >>, _)
+ when StatusCode > 2 ->
+ {error, badprotocol};
+parse(<< 1:1, 3:15, 7:16, 0:8, _:25, LastGoodStreamID:31,
+ StatusCode:32 >>, _) ->
+ Status = case StatusCode of
+ 0 -> ok;
+ 1 -> protocol_error;
+ 2 -> internal_error
+ end,
+ {goaway, LastGoodStreamID, Status};
+parse(<< 1:1, 3:15, 8:16, 0:7, IsFinFlag:1, _:25, StreamID:31,
+ Rest/bits >>, Zinf) ->
+ case parse_headers(Rest, Zinf) of
+ {ok, Headers, []} ->
+ {headers, StreamID, from_flag(IsFinFlag), Headers};
+ _ ->
+ {error, badprotocol}
+ end;
+parse(<< 1:1, 3:15, 9:16, 0:8, _:57, 0:31 >>, _) ->
+ {error, badprotocol};
+parse(<< 1:1, 3:15, 9:16, 0:8, _:25, StreamID:31,
+ _:1, DeltaWindowSize:31 >>, _) ->
+ {window_update, StreamID, DeltaWindowSize};
+parse(_, _) ->
+ {error, badprotocol}.
+
+parse_headers(Data, Zinf) ->
+ [<< NbHeaders:32, Rest/bits >>] = inflate(Zinf, Data),
+ parse_headers(Rest, NbHeaders, [], []).
+
+parse_headers(<<>>, 0, Headers, SpHeaders) ->
+ {ok, lists:reverse(Headers), lists:sort(SpHeaders)};
+parse_headers(<<>>, _, _, _) ->
+ error;
+parse_headers(_, 0, _, _) ->
+ error;
+parse_headers(<< 0:32, _/bits >>, _, _, _) ->
+ error;
+parse_headers(<< L1:32, Key:L1/binary, L2:32, Value:L2/binary, Rest/bits >>,
+ NbHeaders, Acc, SpAcc) ->
+ case Key of
+ << $:, _/bits >> ->
+ parse_headers(Rest, NbHeaders - 1, Acc,
+ lists:keystore(Key, 1, SpAcc, {Key, Value}));
+ _ ->
+ parse_headers(Rest, NbHeaders - 1, [{Key, Value}|Acc], SpAcc)
+ end.
+
+inflate(Zinf, Data) ->
+ try
+ zlib:inflate(Zinf, Data)
+ catch _:_ ->
+ ok = zlib:inflateSetDictionary(Zinf, ?ZDICT),
+ zlib:inflate(Zinf, <<>>)
+ end.
+
+from_flag(0) -> false;
+from_flag(1) -> true.
+
+%% Build.
+
+data(StreamID, IsFin, Data) ->
+ IsFinFlag = to_flag(IsFin),
+ Length = iolist_size(Data),
+ [<< 0:1, StreamID:31, 0:7, IsFinFlag:1, Length:24 >>, Data].
+
+syn_stream(Zdef, StreamID, AssocToStreamID, IsFin, IsUnidirectional,
+ Priority, Method, Scheme, Host, Path, Version, Headers) ->
+ IsFinFlag = to_flag(IsFin),
+ IsUnidirectionalFlag = to_flag(IsUnidirectional),
+ HeaderBlock = build_headers(Zdef, [
+ {<<":method">>, Method},
+ {<<":scheme">>, Scheme},
+ {<<":host">>, Host},
+ {<<":path">>, Path},
+ {<<":version">>, Version}
+ |Headers]),
+ Length = 10 + iolist_size(HeaderBlock),
+ [<< 1:1, 3:15, 1:16, 0:6, IsUnidirectionalFlag:1, IsFinFlag:1,
+ Length:24, 0:1, StreamID:31, 0:1, AssocToStreamID:31,
+ Priority:3, 0:5, 0:8 >>, HeaderBlock].
+
+syn_reply(Zdef, StreamID, IsFin, Status, Version, Headers) ->
+ IsFinFlag = to_flag(IsFin),
+ HeaderBlock = build_headers(Zdef, [
+ {<<":status">>, Status},
+ {<<":version">>, Version}
+ |Headers]),
+ Length = 4 + iolist_size(HeaderBlock),
+ [<< 1:1, 3:15, 2:16, 0:7, IsFinFlag:1, Length:24,
+ 0:1, StreamID:31 >>, HeaderBlock].
+
+rst_stream(StreamID, Status) ->
+ StatusCode = case Status of
+ protocol_error -> 1;
+ invalid_stream -> 2;
+ refused_stream -> 3;
+ unsupported_version -> 4;
+ cancel -> 5;
+ internal_error -> 6;
+ flow_control_error -> 7;
+ stream_in_use -> 8;
+ stream_already_closed -> 9;
+ invalid_credentials -> 10;
+ frame_too_large -> 11
+ end,
+ << 1:1, 3:15, 3:16, 0:8, 8:24,
+ 0:1, StreamID:31, StatusCode:32 >>.
+
+settings(ClearSettingsFlag, Settings) ->
+ IsClearSettingsFlag = to_flag(ClearSettingsFlag),
+ NbEntries = length(Settings),
+ Entries = [begin
+ IsWasPersistedFlag = to_flag(WasPersistedFlag),
+ IsPersistFlag = to_flag(PersistFlag),
+ ID = case Key of
+ upload_bandwidth -> 1;
+ download_bandwidth -> 2;
+ round_trip_time -> 3;
+ max_concurrent_streams -> 4;
+ current_cwnd -> 5;
+ download_retrans_rate -> 6;
+ initial_window_size -> 7;
+ client_certificate_vector_size -> 8
+ end,
+ << 0:6, IsWasPersistedFlag:1, IsPersistFlag:1, ID:24, Value:32 >>
+ end || {Key, Value, WasPersistedFlag, PersistFlag} <- Settings],
+ Length = 4 + iolist_size(Entries),
+ [<< 1:1, 3:15, 4:16, 0:7, IsClearSettingsFlag:1, Length:24,
+ NbEntries:32 >>, Entries].
+
+-ifdef(TEST).
+settings_frame_test() ->
+ ClearSettingsFlag = false,
+ Settings = [{max_concurrent_streams,1000,false,false},
+ {initial_window_size,10485760,false,false}],
+ Bin = list_to_binary(cow_spdy:settings(ClearSettingsFlag, Settings)),
+ P = cow_spdy:parse(Bin, undefined),
+ P = {settings, ClearSettingsFlag, Settings},
+ ok.
+-endif.
+
+ping(PingID) ->
+ << 1:1, 3:15, 6:16, 0:8, 4:24, PingID:32 >>.
+
+goaway(LastGoodStreamID, Status) ->
+ StatusCode = case Status of
+ ok -> 0;
+ protocol_error -> 1;
+ internal_error -> 2
+ end,
+ << 1:1, 3:15, 7:16, 0:8, 8:24,
+ 0:1, LastGoodStreamID:31, StatusCode:32 >>.
+
+%% @todo headers
+%% @todo window_update
+
+build_headers(Zdef, Headers) ->
+ Headers1 = merge_headers(lists:sort(Headers), []),
+ NbHeaders = length(Headers1),
+ Headers2 = [begin
+ L1 = iolist_size(Key),
+ L2 = iolist_size(Value),
+ [<< L1:32 >>, Key, << L2:32 >>, Value]
+ end || {Key, Value} <- Headers1],
+ zlib:deflate(Zdef, [<< NbHeaders:32 >>, Headers2], full).
+
+merge_headers([], Acc) ->
+ lists:reverse(Acc);
+merge_headers([{Name, Value1}, {Name, Value2}|Tail], Acc) ->
+ merge_headers([{Name, [Value1, 0, Value2]}|Tail], Acc);
+merge_headers([Head|Tail], Acc) ->
+ merge_headers(Tail, [Head|Acc]).
+
+-ifdef(TEST).
+merge_headers_test_() ->
+ Tests = [
+ {[{<<"set-cookie">>, <<"session=123">>}, {<<"set-cookie">>, <<"other=456">>}, {<<"content-type">>, <<"text/html">>}],
+ [{<<"set-cookie">>, [<<"session=123">>, 0, <<"other=456">>]}, {<<"content-type">>, <<"text/html">>}]}
+ ],
+ [fun() -> D = merge_headers(R, []) end || {R, D} <- Tests].
+-endif.
+
+to_flag(false) -> 0;
+to_flag(true) -> 1.
diff --git a/server/_build/default/lib/cowlib/src/cow_spdy.hrl b/server/_build/default/lib/cowlib/src/cow_spdy.hrl
new file mode 100644
index 0000000..9637b1c
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_spdy.hrl
@@ -0,0 +1,181 @@
+%% Zlib dictionary.
+
+-define(ZDICT, <<
+ 16#00, 16#00, 16#00, 16#07, 16#6f, 16#70, 16#74, 16#69,
+ 16#6f, 16#6e, 16#73, 16#00, 16#00, 16#00, 16#04, 16#68,
+ 16#65, 16#61, 16#64, 16#00, 16#00, 16#00, 16#04, 16#70,
+ 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#03, 16#70,
+ 16#75, 16#74, 16#00, 16#00, 16#00, 16#06, 16#64, 16#65,
+ 16#6c, 16#65, 16#74, 16#65, 16#00, 16#00, 16#00, 16#05,
+ 16#74, 16#72, 16#61, 16#63, 16#65, 16#00, 16#00, 16#00,
+ 16#06, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#00,
+ 16#00, 16#00, 16#0e, 16#61, 16#63, 16#63, 16#65, 16#70,
+ 16#74, 16#2d, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65,
+ 16#74, 16#00, 16#00, 16#00, 16#0f, 16#61, 16#63, 16#63,
+ 16#65, 16#70, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f,
+ 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#0f,
+ 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#6c,
+ 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65, 16#00,
+ 16#00, 16#00, 16#0d, 16#61, 16#63, 16#63, 16#65, 16#70,
+ 16#74, 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#73,
+ 16#00, 16#00, 16#00, 16#03, 16#61, 16#67, 16#65, 16#00,
+ 16#00, 16#00, 16#05, 16#61, 16#6c, 16#6c, 16#6f, 16#77,
+ 16#00, 16#00, 16#00, 16#0d, 16#61, 16#75, 16#74, 16#68,
+ 16#6f, 16#72, 16#69, 16#7a, 16#61, 16#74, 16#69, 16#6f,
+ 16#6e, 16#00, 16#00, 16#00, 16#0d, 16#63, 16#61, 16#63,
+ 16#68, 16#65, 16#2d, 16#63, 16#6f, 16#6e, 16#74, 16#72,
+ 16#6f, 16#6c, 16#00, 16#00, 16#00, 16#0a, 16#63, 16#6f,
+ 16#6e, 16#6e, 16#65, 16#63, 16#74, 16#69, 16#6f, 16#6e,
+ 16#00, 16#00, 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#62, 16#61, 16#73, 16#65,
+ 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f,
+ 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10,
+ 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d,
+ 16#6c, 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65,
+ 16#00, 16#00, 16#00, 16#0e, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#65, 16#6e, 16#67,
+ 16#74, 16#68, 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f,
+ 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#6f,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00,
+ 16#00, 16#0b, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e,
+ 16#74, 16#2d, 16#6d, 16#64, 16#35, 16#00, 16#00, 16#00,
+ 16#0d, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74,
+ 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00,
+ 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e,
+ 16#74, 16#2d, 16#74, 16#79, 16#70, 16#65, 16#00, 16#00,
+ 16#00, 16#04, 16#64, 16#61, 16#74, 16#65, 16#00, 16#00,
+ 16#00, 16#04, 16#65, 16#74, 16#61, 16#67, 16#00, 16#00,
+ 16#00, 16#06, 16#65, 16#78, 16#70, 16#65, 16#63, 16#74,
+ 16#00, 16#00, 16#00, 16#07, 16#65, 16#78, 16#70, 16#69,
+ 16#72, 16#65, 16#73, 16#00, 16#00, 16#00, 16#04, 16#66,
+ 16#72, 16#6f, 16#6d, 16#00, 16#00, 16#00, 16#04, 16#68,
+ 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#08, 16#69,
+ 16#66, 16#2d, 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00,
+ 16#00, 16#00, 16#11, 16#69, 16#66, 16#2d, 16#6d, 16#6f,
+ 16#64, 16#69, 16#66, 16#69, 16#65, 16#64, 16#2d, 16#73,
+ 16#69, 16#6e, 16#63, 16#65, 16#00, 16#00, 16#00, 16#0d,
+ 16#69, 16#66, 16#2d, 16#6e, 16#6f, 16#6e, 16#65, 16#2d,
+ 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00, 16#00, 16#00,
+ 16#08, 16#69, 16#66, 16#2d, 16#72, 16#61, 16#6e, 16#67,
+ 16#65, 16#00, 16#00, 16#00, 16#13, 16#69, 16#66, 16#2d,
+ 16#75, 16#6e, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69,
+ 16#65, 16#64, 16#2d, 16#73, 16#69, 16#6e, 16#63, 16#65,
+ 16#00, 16#00, 16#00, 16#0d, 16#6c, 16#61, 16#73, 16#74,
+ 16#2d, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69, 16#65,
+ 16#64, 16#00, 16#00, 16#00, 16#08, 16#6c, 16#6f, 16#63,
+ 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00,
+ 16#0c, 16#6d, 16#61, 16#78, 16#2d, 16#66, 16#6f, 16#72,
+ 16#77, 16#61, 16#72, 16#64, 16#73, 16#00, 16#00, 16#00,
+ 16#06, 16#70, 16#72, 16#61, 16#67, 16#6d, 16#61, 16#00,
+ 16#00, 16#00, 16#12, 16#70, 16#72, 16#6f, 16#78, 16#79,
+ 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e, 16#74,
+ 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00, 16#00,
+ 16#13, 16#70, 16#72, 16#6f, 16#78, 16#79, 16#2d, 16#61,
+ 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#7a, 16#61,
+ 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#05,
+ 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00, 16#00,
+ 16#07, 16#72, 16#65, 16#66, 16#65, 16#72, 16#65, 16#72,
+ 16#00, 16#00, 16#00, 16#0b, 16#72, 16#65, 16#74, 16#72,
+ 16#79, 16#2d, 16#61, 16#66, 16#74, 16#65, 16#72, 16#00,
+ 16#00, 16#00, 16#06, 16#73, 16#65, 16#72, 16#76, 16#65,
+ 16#72, 16#00, 16#00, 16#00, 16#02, 16#74, 16#65, 16#00,
+ 16#00, 16#00, 16#07, 16#74, 16#72, 16#61, 16#69, 16#6c,
+ 16#65, 16#72, 16#00, 16#00, 16#00, 16#11, 16#74, 16#72,
+ 16#61, 16#6e, 16#73, 16#66, 16#65, 16#72, 16#2d, 16#65,
+ 16#6e, 16#63, 16#6f, 16#64, 16#69, 16#6e, 16#67, 16#00,
+ 16#00, 16#00, 16#07, 16#75, 16#70, 16#67, 16#72, 16#61,
+ 16#64, 16#65, 16#00, 16#00, 16#00, 16#0a, 16#75, 16#73,
+ 16#65, 16#72, 16#2d, 16#61, 16#67, 16#65, 16#6e, 16#74,
+ 16#00, 16#00, 16#00, 16#04, 16#76, 16#61, 16#72, 16#79,
+ 16#00, 16#00, 16#00, 16#03, 16#76, 16#69, 16#61, 16#00,
+ 16#00, 16#00, 16#07, 16#77, 16#61, 16#72, 16#6e, 16#69,
+ 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10, 16#77, 16#77,
+ 16#77, 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e,
+ 16#74, 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00,
+ 16#00, 16#06, 16#6d, 16#65, 16#74, 16#68, 16#6f, 16#64,
+ 16#00, 16#00, 16#00, 16#03, 16#67, 16#65, 16#74, 16#00,
+ 16#00, 16#00, 16#06, 16#73, 16#74, 16#61, 16#74, 16#75,
+ 16#73, 16#00, 16#00, 16#00, 16#06, 16#32, 16#30, 16#30,
+ 16#20, 16#4f, 16#4b, 16#00, 16#00, 16#00, 16#07, 16#76,
+ 16#65, 16#72, 16#73, 16#69, 16#6f, 16#6e, 16#00, 16#00,
+ 16#00, 16#08, 16#48, 16#54, 16#54, 16#50, 16#2f, 16#31,
+ 16#2e, 16#31, 16#00, 16#00, 16#00, 16#03, 16#75, 16#72,
+ 16#6c, 16#00, 16#00, 16#00, 16#06, 16#70, 16#75, 16#62,
+ 16#6c, 16#69, 16#63, 16#00, 16#00, 16#00, 16#0a, 16#73,
+ 16#65, 16#74, 16#2d, 16#63, 16#6f, 16#6f, 16#6b, 16#69,
+ 16#65, 16#00, 16#00, 16#00, 16#0a, 16#6b, 16#65, 16#65,
+ 16#70, 16#2d, 16#61, 16#6c, 16#69, 16#76, 16#65, 16#00,
+ 16#00, 16#00, 16#06, 16#6f, 16#72, 16#69, 16#67, 16#69,
+ 16#6e, 16#31, 16#30, 16#30, 16#31, 16#30, 16#31, 16#32,
+ 16#30, 16#31, 16#32, 16#30, 16#32, 16#32, 16#30, 16#35,
+ 16#32, 16#30, 16#36, 16#33, 16#30, 16#30, 16#33, 16#30,
+ 16#32, 16#33, 16#30, 16#33, 16#33, 16#30, 16#34, 16#33,
+ 16#30, 16#35, 16#33, 16#30, 16#36, 16#33, 16#30, 16#37,
+ 16#34, 16#30, 16#32, 16#34, 16#30, 16#35, 16#34, 16#30,
+ 16#36, 16#34, 16#30, 16#37, 16#34, 16#30, 16#38, 16#34,
+ 16#30, 16#39, 16#34, 16#31, 16#30, 16#34, 16#31, 16#31,
+ 16#34, 16#31, 16#32, 16#34, 16#31, 16#33, 16#34, 16#31,
+ 16#34, 16#34, 16#31, 16#35, 16#34, 16#31, 16#36, 16#34,
+ 16#31, 16#37, 16#35, 16#30, 16#32, 16#35, 16#30, 16#34,
+ 16#35, 16#30, 16#35, 16#32, 16#30, 16#33, 16#20, 16#4e,
+ 16#6f, 16#6e, 16#2d, 16#41, 16#75, 16#74, 16#68, 16#6f,
+ 16#72, 16#69, 16#74, 16#61, 16#74, 16#69, 16#76, 16#65,
+ 16#20, 16#49, 16#6e, 16#66, 16#6f, 16#72, 16#6d, 16#61,
+ 16#74, 16#69, 16#6f, 16#6e, 16#32, 16#30, 16#34, 16#20,
+ 16#4e, 16#6f, 16#20, 16#43, 16#6f, 16#6e, 16#74, 16#65,
+ 16#6e, 16#74, 16#33, 16#30, 16#31, 16#20, 16#4d, 16#6f,
+ 16#76, 16#65, 16#64, 16#20, 16#50, 16#65, 16#72, 16#6d,
+ 16#61, 16#6e, 16#65, 16#6e, 16#74, 16#6c, 16#79, 16#34,
+ 16#30, 16#30, 16#20, 16#42, 16#61, 16#64, 16#20, 16#52,
+ 16#65, 16#71, 16#75, 16#65, 16#73, 16#74, 16#34, 16#30,
+ 16#31, 16#20, 16#55, 16#6e, 16#61, 16#75, 16#74, 16#68,
+ 16#6f, 16#72, 16#69, 16#7a, 16#65, 16#64, 16#34, 16#30,
+ 16#33, 16#20, 16#46, 16#6f, 16#72, 16#62, 16#69, 16#64,
+ 16#64, 16#65, 16#6e, 16#34, 16#30, 16#34, 16#20, 16#4e,
+ 16#6f, 16#74, 16#20, 16#46, 16#6f, 16#75, 16#6e, 16#64,
+ 16#35, 16#30, 16#30, 16#20, 16#49, 16#6e, 16#74, 16#65,
+ 16#72, 16#6e, 16#61, 16#6c, 16#20, 16#53, 16#65, 16#72,
+ 16#76, 16#65, 16#72, 16#20, 16#45, 16#72, 16#72, 16#6f,
+ 16#72, 16#35, 16#30, 16#31, 16#20, 16#4e, 16#6f, 16#74,
+ 16#20, 16#49, 16#6d, 16#70, 16#6c, 16#65, 16#6d, 16#65,
+ 16#6e, 16#74, 16#65, 16#64, 16#35, 16#30, 16#33, 16#20,
+ 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20,
+ 16#55, 16#6e, 16#61, 16#76, 16#61, 16#69, 16#6c, 16#61,
+ 16#62, 16#6c, 16#65, 16#4a, 16#61, 16#6e, 16#20, 16#46,
+ 16#65, 16#62, 16#20, 16#4d, 16#61, 16#72, 16#20, 16#41,
+ 16#70, 16#72, 16#20, 16#4d, 16#61, 16#79, 16#20, 16#4a,
+ 16#75, 16#6e, 16#20, 16#4a, 16#75, 16#6c, 16#20, 16#41,
+ 16#75, 16#67, 16#20, 16#53, 16#65, 16#70, 16#74, 16#20,
+ 16#4f, 16#63, 16#74, 16#20, 16#4e, 16#6f, 16#76, 16#20,
+ 16#44, 16#65, 16#63, 16#20, 16#30, 16#30, 16#3a, 16#30,
+ 16#30, 16#3a, 16#30, 16#30, 16#20, 16#4d, 16#6f, 16#6e,
+ 16#2c, 16#20, 16#54, 16#75, 16#65, 16#2c, 16#20, 16#57,
+ 16#65, 16#64, 16#2c, 16#20, 16#54, 16#68, 16#75, 16#2c,
+ 16#20, 16#46, 16#72, 16#69, 16#2c, 16#20, 16#53, 16#61,
+ 16#74, 16#2c, 16#20, 16#53, 16#75, 16#6e, 16#2c, 16#20,
+ 16#47, 16#4d, 16#54, 16#63, 16#68, 16#75, 16#6e, 16#6b,
+ 16#65, 16#64, 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f,
+ 16#68, 16#74, 16#6d, 16#6c, 16#2c, 16#69, 16#6d, 16#61,
+ 16#67, 16#65, 16#2f, 16#70, 16#6e, 16#67, 16#2c, 16#69,
+ 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#6a, 16#70, 16#67,
+ 16#2c, 16#69, 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#67,
+ 16#69, 16#66, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78,
+ 16#6d, 16#6c, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78,
+ 16#68, 16#74, 16#6d, 16#6c, 16#2b, 16#78, 16#6d, 16#6c,
+ 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f, 16#70, 16#6c,
+ 16#61, 16#69, 16#6e, 16#2c, 16#74, 16#65, 16#78, 16#74,
+ 16#2f, 16#6a, 16#61, 16#76, 16#61, 16#73, 16#63, 16#72,
+ 16#69, 16#70, 16#74, 16#2c, 16#70, 16#75, 16#62, 16#6c,
+ 16#69, 16#63, 16#70, 16#72, 16#69, 16#76, 16#61, 16#74,
+ 16#65, 16#6d, 16#61, 16#78, 16#2d, 16#61, 16#67, 16#65,
+ 16#3d, 16#67, 16#7a, 16#69, 16#70, 16#2c, 16#64, 16#65,
+ 16#66, 16#6c, 16#61, 16#74, 16#65, 16#2c, 16#73, 16#64,
+ 16#63, 16#68, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65,
+ 16#74, 16#3d, 16#75, 16#74, 16#66, 16#2d, 16#38, 16#63,
+ 16#68, 16#61, 16#72, 16#73, 16#65, 16#74, 16#3d, 16#69,
+ 16#73, 16#6f, 16#2d, 16#38, 16#38, 16#35, 16#39, 16#2d,
+ 16#31, 16#2c, 16#75, 16#74, 16#66, 16#2d, 16#2c, 16#2a,
+ 16#2c, 16#65, 16#6e, 16#71, 16#3d, 16#30, 16#2e >>).
diff --git a/server/_build/default/lib/cowlib/src/cow_sse.erl b/server/_build/default/lib/cowlib/src/cow_sse.erl
new file mode 100644
index 0000000..6e7081f
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_sse.erl
@@ -0,0 +1,349 @@
+%% Copyright (c) 2017-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_sse).
+
+-export([init/0]).
+-export([parse/2]).
+-export([events/1]).
+-export([event/1]).
+
+-record(state, {
+ state_name = bom :: bom | events,
+ buffer = <<>> :: binary(),
+ last_event_id = <<>> :: binary(),
+ last_event_id_set = false :: boolean(),
+ event_type = <<>> :: binary(),
+ data = [] :: iolist(),
+ retry = undefined :: undefined | non_neg_integer()
+}).
+-type state() :: #state{}.
+-export_type([state/0]).
+
+-type parsed_event() :: #{
+ last_event_id := binary(),
+ event_type := binary(),
+ data := iolist()
+}.
+
+-type event() :: #{
+ comment => iodata(),
+ data => iodata(),
+ event => iodata() | atom(),
+ id => iodata(),
+ retry => non_neg_integer()
+}.
+-export_type([event/0]).
+
+-spec init() -> state().
+init() ->
+ #state{}.
+
+%% @todo Add a function to retrieve the retry value from the state.
+
+-spec parse(binary(), State)
+ -> {event, parsed_event(), State} | {more, State}
+ when State::state().
+parse(Data0, State=#state{state_name=bom, buffer=Buffer}) ->
+ Data1 = case Buffer of
+ <<>> -> Data0;
+ _ -> << Buffer/binary, Data0/binary >>
+ end,
+ case Data1 of
+ %% Skip the BOM.
+ << 16#fe, 16#ff, Data/bits >> ->
+ parse_event(Data, State#state{state_name=events, buffer= <<>>});
+ %% Not enough data to know wether we have a BOM.
+ << 16#fe >> ->
+ {more, State#state{buffer=Data1}};
+ <<>> ->
+ {more, State};
+ %% No BOM.
+ _ ->
+ parse_event(Data1, State#state{state_name=events, buffer= <<>>})
+ end;
+%% Try to process data from the buffer if there is no new input.
+parse(<<>>, State=#state{buffer=Buffer}) ->
+ parse_event(Buffer, State#state{buffer= <<>>});
+%% Otherwise process the input data as-is.
+parse(Data0, State=#state{buffer=Buffer}) ->
+ Data = case Buffer of
+ <<>> -> Data0;
+ _ -> << Buffer/binary, Data0/binary >>
+ end,
+ parse_event(Data, State).
+
+parse_event(Data, State0) ->
+ case binary:split(Data, [<<"\r\n">>, <<"\r">>, <<"\n">>]) of
+ [Line, Rest] ->
+ case parse_line(Line, State0) of
+ {ok, State} ->
+ parse_event(Rest, State);
+ {event, Event, State} ->
+ {event, Event, State#state{buffer=Rest}}
+ end;
+ [_] ->
+ {more, State0#state{buffer=Data}}
+ end.
+
+%% Dispatch events on empty line.
+parse_line(<<>>, State) ->
+ dispatch_event(State);
+%% Ignore comments.
+parse_line(<< $:, _/bits >>, State) ->
+ {ok, State};
+%% Normal line.
+parse_line(Line, State) ->
+ case binary:split(Line, [<<":\s">>, <<":">>]) of
+ [Field, Value] ->
+ process_field(Field, Value, State);
+ [Field] ->
+ process_field(Field, <<>>, State)
+ end.
+
+process_field(<<"event">>, Value, State) ->
+ {ok, State#state{event_type=Value}};
+process_field(<<"data">>, Value, State=#state{data=Data}) ->
+ {ok, State#state{data=[<<$\n>>, Value|Data]}};
+process_field(<<"id">>, Value, State) ->
+ {ok, State#state{last_event_id=Value, last_event_id_set=true}};
+process_field(<<"retry">>, Value, State) ->
+ try
+ {ok, State#state{retry=binary_to_integer(Value)}}
+ catch _:_ ->
+ {ok, State}
+ end;
+process_field(_, _, State) ->
+ {ok, State}.
+
+%% Data is an empty string; abort.
+dispatch_event(State=#state{last_event_id_set=false, data=[]}) ->
+ {ok, State#state{event_type= <<>>}};
+%% Data is an empty string but we have a last_event_id:
+%% propagate it on its own so that the caller knows the
+%% most recent ID.
+dispatch_event(State=#state{last_event_id=LastEventID, data=[]}) ->
+ {event, #{
+ last_event_id => LastEventID
+ }, State#state{last_event_id_set=false, event_type= <<>>}};
+%% Dispatch the event.
+%%
+%% Always remove the last linebreak from the data.
+dispatch_event(State=#state{last_event_id=LastEventID,
+ event_type=EventType, data=[_|Data]}) ->
+ {event, #{
+ last_event_id => LastEventID,
+ event_type => case EventType of
+ <<>> -> <<"message">>;
+ _ -> EventType
+ end,
+ data => lists:reverse(Data)
+ }, State#state{last_event_id_set=false, event_type= <<>>, data=[]}}.
+
+-ifdef(TEST).
+parse_example1_test() ->
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<>>,
+ data := Data
+ }, State} = parse(<<
+ "data: YHOO\n"
+ "data: +2\n"
+ "data: 10\n"
+ "\n">>, init()),
+ <<"YHOO\n+2\n10">> = iolist_to_binary(Data),
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_example2_test() ->
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<"1">>,
+ data := Data1
+ }, State0} = parse(<<
+ ": test stream\n"
+ "\n"
+ "data: first event\n"
+ "id: 1\n"
+ "\n"
+ "data:second event\n"
+ "id\n"
+ "\n"
+ "data: third event\n"
+ "\n">>, init()),
+ <<"first event">> = iolist_to_binary(Data1),
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<>>,
+ data := Data2
+ }, State1} = parse(<<>>, State0),
+ <<"second event">> = iolist_to_binary(Data2),
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<>>,
+ data := Data3
+ }, State} = parse(<<>>, State1),
+ <<" third event">> = iolist_to_binary(Data3),
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_example3_test() ->
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<>>,
+ data := Data1
+ }, State0} = parse(<<
+ "data\n"
+ "\n"
+ "data\n"
+ "data\n"
+ "\n"
+ "data:\n">>, init()),
+ <<>> = iolist_to_binary(Data1),
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<>>,
+ data := Data2
+ }, State} = parse(<<>>, State0),
+ <<"\n">> = iolist_to_binary(Data2),
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_example4_test() ->
+ {event, Event, State0} = parse(<<
+ "data:test\n"
+ "\n"
+ "data: test\n"
+ "\n">>, init()),
+ {event, Event, State} = parse(<<>>, State0),
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_id_without_data_test() ->
+ {event, Event1, State0} = parse(<<
+ "id: 1\n"
+ "\n"
+ "data: data\n"
+ "\n"
+ "id: 2\n"
+ "\n">>, init()),
+ 1 = maps:size(Event1),
+ #{last_event_id := <<"1">>} = Event1,
+ {event, #{
+ event_type := <<"message">>,
+ last_event_id := <<"1">>,
+ data := Data
+ }, State1} = parse(<<>>, State0),
+ <<"data">> = iolist_to_binary(Data),
+ {event, Event2, State} = parse(<<>>, State1),
+ 1 = maps:size(Event2),
+ #{last_event_id := <<"2">>} = Event2,
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_repeated_id_without_data_test() ->
+ {event, Event1, State0} = parse(<<
+ "id: 1\n"
+ "\n"
+ "event: message\n" %% This will be ignored since there's no data.
+ "\n"
+ "id: 1\n"
+ "\n"
+ "id: 2\n"
+ "\n">>, init()),
+ {event, Event1, State1} = parse(<<>>, State0),
+ 1 = maps:size(Event1),
+ #{last_event_id := <<"1">>} = Event1,
+ {event, Event2, State} = parse(<<>>, State1),
+ 1 = maps:size(Event2),
+ #{last_event_id := <<"2">>} = Event2,
+ {more, _} = parse(<<>>, State),
+ ok.
+
+parse_split_event_test() ->
+ {more, State} = parse(<<
+ "data: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
+ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA">>, init()),
+ {event, _, _} = parse(<<"==\n\n">>, State),
+ ok.
+-endif.
+
+-spec events([event()]) -> iolist().
+events(Events) ->
+ [event(Event) || Event <- Events].
+
+-spec event(event()) -> iolist().
+event(Event) ->
+ [
+ event_comment(Event),
+ event_id(Event),
+ event_name(Event),
+ event_data(Event),
+ event_retry(Event),
+ $\n
+ ].
+
+event_comment(#{comment := Comment}) ->
+ prefix_lines(Comment, <<>>);
+event_comment(_) ->
+ [].
+
+event_id(#{id := ID}) ->
+ nomatch = binary:match(iolist_to_binary(ID), <<"\n">>),
+ [<<"id: ">>, ID, $\n];
+event_id(_) ->
+ [].
+
+event_name(#{event := Name0}) ->
+ Name = if
+ is_atom(Name0) -> atom_to_binary(Name0, utf8);
+ true -> iolist_to_binary(Name0)
+ end,
+ nomatch = binary:match(Name, <<"\n">>),
+ [<<"event: ">>, Name, $\n];
+event_name(_) ->
+ [].
+
+event_data(#{data := Data}) ->
+ prefix_lines(Data, <<"data">>);
+event_data(_) ->
+ [].
+
+event_retry(#{retry := Retry}) ->
+ [<<"retry: ">>, integer_to_binary(Retry), $\n];
+event_retry(_) ->
+ [].
+
+prefix_lines(IoData, Prefix) ->
+ Lines = binary:split(iolist_to_binary(IoData), <<"\n">>, [global]),
+ [[Prefix, <<": ">>, Line, $\n] || Line <- Lines].
+
+-ifdef(TEST).
+event_test() ->
+ _ = event(#{}),
+ _ = event(#{comment => "test"}),
+ _ = event(#{data => "test"}),
+ _ = event(#{data => "test\ntest\ntest"}),
+ _ = event(#{data => "test\ntest\ntest\n"}),
+ _ = event(#{data => <<"test\ntest\ntest">>}),
+ _ = event(#{data => [<<"test">>, $\n, <<"test">>, [$\n, "test"]]}),
+ _ = event(#{event => test}),
+ _ = event(#{event => "test"}),
+ _ = event(#{id => "test"}),
+ _ = event(#{retry => 5000}),
+ _ = event(#{event => "test", data => "test"}),
+ _ = event(#{id => "test", event => "test", data => "test"}),
+ ok.
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_uri.erl b/server/_build/default/lib/cowlib/src/cow_uri.erl
new file mode 100644
index 0000000..4480d6b
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_uri.erl
@@ -0,0 +1,339 @@
+%% Copyright (c) 2016-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_uri).
+
+-export([urldecode/1]).
+-export([urlencode/1]).
+
+%% @doc Decode a percent encoded string. (RFC3986 2.1)
+
+-spec urldecode(B) -> B when B::binary().
+urldecode(B) ->
+ urldecode(B, <<>>).
+
+urldecode(<< $%, H, L, Rest/bits >>, Acc) ->
+ C = (unhex(H) bsl 4 bor unhex(L)),
+ urldecode(Rest, << Acc/bits, C >>);
+urldecode(<< $!, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $! >>);
+urldecode(<< $$, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $$ >>);
+urldecode(<< $&, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $& >>);
+urldecode(<< $', Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $' >>);
+urldecode(<< $(, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $( >>);
+urldecode(<< $), Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $) >>);
+urldecode(<< $*, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $* >>);
+urldecode(<< $+, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $+ >>);
+urldecode(<< $,, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $, >>);
+urldecode(<< $-, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $- >>);
+urldecode(<< $., Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $. >>);
+urldecode(<< $0, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $0 >>);
+urldecode(<< $1, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $1 >>);
+urldecode(<< $2, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $2 >>);
+urldecode(<< $3, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $3 >>);
+urldecode(<< $4, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $4 >>);
+urldecode(<< $5, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $5 >>);
+urldecode(<< $6, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $6 >>);
+urldecode(<< $7, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $7 >>);
+urldecode(<< $8, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $8 >>);
+urldecode(<< $9, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $9 >>);
+urldecode(<< $:, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $: >>);
+urldecode(<< $;, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $; >>);
+urldecode(<< $=, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $= >>);
+urldecode(<< $@, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $@ >>);
+urldecode(<< $A, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $A >>);
+urldecode(<< $B, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $B >>);
+urldecode(<< $C, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $C >>);
+urldecode(<< $D, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $D >>);
+urldecode(<< $E, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $E >>);
+urldecode(<< $F, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $F >>);
+urldecode(<< $G, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $G >>);
+urldecode(<< $H, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $H >>);
+urldecode(<< $I, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $I >>);
+urldecode(<< $J, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $J >>);
+urldecode(<< $K, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $K >>);
+urldecode(<< $L, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $L >>);
+urldecode(<< $M, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $M >>);
+urldecode(<< $N, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $N >>);
+urldecode(<< $O, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $O >>);
+urldecode(<< $P, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $P >>);
+urldecode(<< $Q, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $Q >>);
+urldecode(<< $R, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $R >>);
+urldecode(<< $S, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $S >>);
+urldecode(<< $T, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $T >>);
+urldecode(<< $U, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $U >>);
+urldecode(<< $V, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $V >>);
+urldecode(<< $W, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $W >>);
+urldecode(<< $X, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $X >>);
+urldecode(<< $Y, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $Y >>);
+urldecode(<< $Z, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $Z >>);
+urldecode(<< $_, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $_ >>);
+urldecode(<< $a, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $a >>);
+urldecode(<< $b, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $b >>);
+urldecode(<< $c, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $c >>);
+urldecode(<< $d, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $d >>);
+urldecode(<< $e, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $e >>);
+urldecode(<< $f, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $f >>);
+urldecode(<< $g, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $g >>);
+urldecode(<< $h, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $h >>);
+urldecode(<< $i, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $i >>);
+urldecode(<< $j, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $j >>);
+urldecode(<< $k, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $k >>);
+urldecode(<< $l, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $l >>);
+urldecode(<< $m, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $m >>);
+urldecode(<< $n, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $n >>);
+urldecode(<< $o, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $o >>);
+urldecode(<< $p, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $p >>);
+urldecode(<< $q, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $q >>);
+urldecode(<< $r, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $r >>);
+urldecode(<< $s, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $s >>);
+urldecode(<< $t, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $t >>);
+urldecode(<< $u, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $u >>);
+urldecode(<< $v, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $v >>);
+urldecode(<< $w, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $w >>);
+urldecode(<< $x, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $x >>);
+urldecode(<< $y, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $y >>);
+urldecode(<< $z, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $z >>);
+urldecode(<< $~, Rest/bits >>, Acc) -> urldecode(Rest, << Acc/bits, $~ >>);
+urldecode(<<>>, Acc) -> Acc.
+
+unhex($0) -> 0;
+unhex($1) -> 1;
+unhex($2) -> 2;
+unhex($3) -> 3;
+unhex($4) -> 4;
+unhex($5) -> 5;
+unhex($6) -> 6;
+unhex($7) -> 7;
+unhex($8) -> 8;
+unhex($9) -> 9;
+unhex($A) -> 10;
+unhex($B) -> 11;
+unhex($C) -> 12;
+unhex($D) -> 13;
+unhex($E) -> 14;
+unhex($F) -> 15;
+unhex($a) -> 10;
+unhex($b) -> 11;
+unhex($c) -> 12;
+unhex($d) -> 13;
+unhex($e) -> 14;
+unhex($f) -> 15.
+
+-ifdef(TEST).
+urldecode_test_() ->
+ Tests = [
+ {<<"%20">>, <<" ">>},
+ {<<"+">>, <<"+">>},
+ {<<"%00">>, <<0>>},
+ {<<"%fF">>, <<255>>},
+ {<<"123">>, <<"123">>},
+ {<<"%i5">>, error},
+ {<<"%5">>, error}
+ ],
+ [{Qs, fun() ->
+ E = try urldecode(Qs) of
+ R -> R
+ catch _:_ ->
+ error
+ end
+ end} || {Qs, E} <- Tests].
+
+urldecode_identity_test_() ->
+ Tests = [
+ <<"%20">>,
+ <<"+">>,
+ <<"nothingnothingnothingnothing">>,
+ <<"Small+fast+modular+HTTP+server">>,
+ <<"Small%20fast%20modular%20HTTP%20server">>,
+ <<"Small%2F+fast%2F+modular+HTTP+server.">>,
+ <<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
+ "%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
+ "%BE%8B%E3%80%9C">>
+ ],
+ [{V, fun() -> V = urlencode(urldecode(V)) end} || V <- Tests].
+
+horse_urldecode() ->
+ horse:repeat(100000,
+ urldecode(<<"nothingnothingnothingnothing">>)
+ ).
+
+horse_urldecode_hex() ->
+ horse:repeat(100000,
+ urldecode(<<"Small%2C%20fast%2C%20modular%20HTTP%20server.">>)
+ ).
+
+horse_urldecode_jp_hex() ->
+ horse:repeat(100000,
+ urldecode(<<"%E3%83%84%E3%82%A4%E3%83%B3%E3%82%BD%E3%82%A6%E3%83"
+ "%AB%E3%80%9C%E8%BC%AA%E5%BB%BB%E3%81%99%E3%82%8B%E6%97%8B%E5"
+ "%BE%8B%E3%80%9C">>)
+ ).
+-endif.
+
+%% @doc Percent encode a string. (RFC3986 2.1)
+%%
+%% This function is meant to be used for path components.
+
+-spec urlencode(B) -> B when B::binary().
+urlencode(B) ->
+ urlencode(B, <<>>).
+
+urlencode(<< $!, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $! >>);
+urlencode(<< $$, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $$ >>);
+urlencode(<< $&, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $& >>);
+urlencode(<< $', Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $' >>);
+urlencode(<< $(, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $( >>);
+urlencode(<< $), Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $) >>);
+urlencode(<< $*, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $* >>);
+urlencode(<< $+, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $+ >>);
+urlencode(<< $,, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $, >>);
+urlencode(<< $-, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $- >>);
+urlencode(<< $., Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $. >>);
+urlencode(<< $0, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $0 >>);
+urlencode(<< $1, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $1 >>);
+urlencode(<< $2, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $2 >>);
+urlencode(<< $3, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $3 >>);
+urlencode(<< $4, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $4 >>);
+urlencode(<< $5, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $5 >>);
+urlencode(<< $6, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $6 >>);
+urlencode(<< $7, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $7 >>);
+urlencode(<< $8, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $8 >>);
+urlencode(<< $9, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $9 >>);
+urlencode(<< $:, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $: >>);
+urlencode(<< $;, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $; >>);
+urlencode(<< $=, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $= >>);
+urlencode(<< $@, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $@ >>);
+urlencode(<< $A, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $A >>);
+urlencode(<< $B, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $B >>);
+urlencode(<< $C, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $C >>);
+urlencode(<< $D, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $D >>);
+urlencode(<< $E, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $E >>);
+urlencode(<< $F, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $F >>);
+urlencode(<< $G, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $G >>);
+urlencode(<< $H, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $H >>);
+urlencode(<< $I, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $I >>);
+urlencode(<< $J, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $J >>);
+urlencode(<< $K, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $K >>);
+urlencode(<< $L, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $L >>);
+urlencode(<< $M, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $M >>);
+urlencode(<< $N, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $N >>);
+urlencode(<< $O, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $O >>);
+urlencode(<< $P, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $P >>);
+urlencode(<< $Q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Q >>);
+urlencode(<< $R, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $R >>);
+urlencode(<< $S, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $S >>);
+urlencode(<< $T, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $T >>);
+urlencode(<< $U, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $U >>);
+urlencode(<< $V, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $V >>);
+urlencode(<< $W, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $W >>);
+urlencode(<< $X, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $X >>);
+urlencode(<< $Y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Y >>);
+urlencode(<< $Z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $Z >>);
+urlencode(<< $_, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $_ >>);
+urlencode(<< $a, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $a >>);
+urlencode(<< $b, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $b >>);
+urlencode(<< $c, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $c >>);
+urlencode(<< $d, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $d >>);
+urlencode(<< $e, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $e >>);
+urlencode(<< $f, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $f >>);
+urlencode(<< $g, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $g >>);
+urlencode(<< $h, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $h >>);
+urlencode(<< $i, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $i >>);
+urlencode(<< $j, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $j >>);
+urlencode(<< $k, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $k >>);
+urlencode(<< $l, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $l >>);
+urlencode(<< $m, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $m >>);
+urlencode(<< $n, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $n >>);
+urlencode(<< $o, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $o >>);
+urlencode(<< $p, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $p >>);
+urlencode(<< $q, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $q >>);
+urlencode(<< $r, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $r >>);
+urlencode(<< $s, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $s >>);
+urlencode(<< $t, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $t >>);
+urlencode(<< $u, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $u >>);
+urlencode(<< $v, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $v >>);
+urlencode(<< $w, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $w >>);
+urlencode(<< $x, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $x >>);
+urlencode(<< $y, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $y >>);
+urlencode(<< $z, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $z >>);
+urlencode(<< $~, Rest/bits >>, Acc) -> urlencode(Rest, << Acc/bits, $~ >>);
+urlencode(<< C, Rest/bits >>, Acc) ->
+ H = hex(C bsr 4),
+ L = hex(C band 16#0f),
+ urlencode(Rest, << Acc/bits, $%, H, L >>);
+urlencode(<<>>, Acc) ->
+ Acc.
+
+hex( 0) -> $0;
+hex( 1) -> $1;
+hex( 2) -> $2;
+hex( 3) -> $3;
+hex( 4) -> $4;
+hex( 5) -> $5;
+hex( 6) -> $6;
+hex( 7) -> $7;
+hex( 8) -> $8;
+hex( 9) -> $9;
+hex(10) -> $A;
+hex(11) -> $B;
+hex(12) -> $C;
+hex(13) -> $D;
+hex(14) -> $E;
+hex(15) -> $F.
+
+-ifdef(TEST).
+urlencode_test_() ->
+ Tests = [
+ {<<255, 0>>, <<"%FF%00">>},
+ {<<255, " ">>, <<"%FF%20">>},
+ {<<"+">>, <<"+">>},
+ {<<"aBc123">>, <<"aBc123">>},
+ {<<"!$&'()*+,:;=@-._~">>, <<"!$&'()*+,:;=@-._~">>}
+ ],
+ [{V, fun() -> E = urlencode(V) end} || {V, E} <- Tests].
+
+urlencode_identity_test_() ->
+ Tests = [
+ <<"+">>,
+ <<"nothingnothingnothingnothing">>,
+ <<"Small fast modular HTTP server">>,
+ <<"Small, fast, modular HTTP server.">>,
+ <<227,131,132,227,130,164,227,131,179,227,130,189,227,
+ 130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
+ 129,153,227,130,139,230,151,139,229,190,139,227,128,156>>
+ ],
+ [{V, fun() -> V = urldecode(urlencode(V)) end} || V <- Tests].
+
+horse_urlencode() ->
+ horse:repeat(100000,
+ urlencode(<<"nothingnothingnothingnothing">>)
+ ).
+
+horse_urlencode_spaces() ->
+ horse:repeat(100000,
+ urlencode(<<"Small fast modular HTTP server">>)
+ ).
+
+horse_urlencode_jp() ->
+ horse:repeat(100000,
+ urlencode(<<227,131,132,227,130,164,227,131,179,227,130,189,227,
+ 130,166,227,131,171,227,128,156,232,188,170,229,187,187,227,
+ 129,153,227,130,139,230,151,139,229,190,139,227,128,156>>)
+ ).
+
+horse_urlencode_mix() ->
+ horse:repeat(100000,
+ urlencode(<<"Small, fast, modular HTTP server.">>)
+ ).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_uri_template.erl b/server/_build/default/lib/cowlib/src/cow_uri_template.erl
new file mode 100644
index 0000000..ccc355d
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_uri_template.erl
@@ -0,0 +1,360 @@
+%% Copyright (c) 2019-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% This is a full level 4 implementation of URI Templates
+%% as defined by RFC6570.
+
+-module(cow_uri_template).
+
+-export([parse/1]).
+-export([expand/2]).
+
+-type op() :: simple_string_expansion
+ | reserved_expansion
+ | fragment_expansion
+ | label_expansion_with_dot_prefix
+ | path_segment_expansion
+ | path_style_parameter_expansion
+ | form_style_query_expansion
+ | form_style_query_continuation.
+
+-type var_list() :: [
+ {no_modifier, binary()}
+ | {{prefix_modifier, pos_integer()}, binary()}
+ | {explode_modifier, binary()}
+].
+
+-type uri_template() :: [
+ binary() | {expr, op(), var_list()}
+].
+-export_type([uri_template/0]).
+
+-type variables() :: #{
+ binary() => binary()
+ | integer()
+ | float()
+ | [binary()]
+ | #{binary() => binary()}
+}.
+
+-include("cow_inline.hrl").
+-include("cow_parse.hrl").
+
+%% Parse a URI template.
+
+-spec parse(binary()) -> uri_template().
+parse(URITemplate) ->
+ parse(URITemplate, <<>>).
+
+parse(<<>>, <<>>) ->
+ [];
+parse(<<>>, Acc) ->
+ [Acc];
+parse(<<${,R/bits>>, <<>>) ->
+ parse_expr(R);
+parse(<<${,R/bits>>, Acc) ->
+ [Acc|parse_expr(R)];
+%% @todo Probably should reject unallowed characters so that
+%% we don't produce invalid URIs.
+parse(<<C,R/bits>>, Acc) when C =/= $} ->
+ parse(R, <<Acc/binary, C>>).
+
+parse_expr(<<$+,R/bits>>) ->
+ parse_var_list(R, reserved_expansion, []);
+parse_expr(<<$#,R/bits>>) ->
+ parse_var_list(R, fragment_expansion, []);
+parse_expr(<<$.,R/bits>>) ->
+ parse_var_list(R, label_expansion_with_dot_prefix, []);
+parse_expr(<<$/,R/bits>>) ->
+ parse_var_list(R, path_segment_expansion, []);
+parse_expr(<<$;,R/bits>>) ->
+ parse_var_list(R, path_style_parameter_expansion, []);
+parse_expr(<<$?,R/bits>>) ->
+ parse_var_list(R, form_style_query_expansion, []);
+parse_expr(<<$&,R/bits>>) ->
+ parse_var_list(R, form_style_query_continuation, []);
+parse_expr(R) ->
+ parse_var_list(R, simple_string_expansion, []).
+
+parse_var_list(<<C,R/bits>>, Op, List)
+ when ?IS_ALPHANUM(C) or (C =:= $_) ->
+ parse_varname(R, Op, List, <<C>>).
+
+parse_varname(<<C,R/bits>>, Op, List, Name)
+ when ?IS_ALPHANUM(C) or (C =:= $_) or (C =:= $.) or (C =:= $%) ->
+ parse_varname(R, Op, List, <<Name/binary,C>>);
+parse_varname(<<$:,C,R/bits>>, Op, List, Name)
+ when (C =:= $1) or (C =:= $2) or (C =:= $3) or (C =:= $4) or (C =:= $5)
+ or (C =:= $6) or (C =:= $7) or (C =:= $8) or (C =:= $9) ->
+ parse_prefix_modifier(R, Op, List, Name, <<C>>);
+parse_varname(<<$*,$,,R/bits>>, Op, List, Name) ->
+ parse_var_list(R, Op, [{explode_modifier, Name}|List]);
+parse_varname(<<$*,$},R/bits>>, Op, List, Name) ->
+ [{expr, Op, lists:reverse([{explode_modifier, Name}|List])}|parse(R, <<>>)];
+parse_varname(<<$,,R/bits>>, Op, List, Name) ->
+ parse_var_list(R, Op, [{no_modifier, Name}|List]);
+parse_varname(<<$},R/bits>>, Op, List, Name) ->
+ [{expr, Op, lists:reverse([{no_modifier, Name}|List])}|parse(R, <<>>)].
+
+parse_prefix_modifier(<<C,R/bits>>, Op, List, Name, Acc)
+ when ?IS_DIGIT(C), byte_size(Acc) < 4 ->
+ parse_prefix_modifier(R, Op, List, Name, <<Acc/binary,C>>);
+parse_prefix_modifier(<<$,,R/bits>>, Op, List, Name, Acc) ->
+ parse_var_list(R, Op, [{{prefix_modifier, binary_to_integer(Acc)}, Name}|List]);
+parse_prefix_modifier(<<$},R/bits>>, Op, List, Name, Acc) ->
+ [{expr, Op, lists:reverse([{{prefix_modifier, binary_to_integer(Acc)}, Name}|List])}|parse(R, <<>>)].
+
+%% Expand a URI template (after parsing it if necessary).
+
+-spec expand(binary() | uri_template(), variables()) -> iodata().
+expand(URITemplate, Vars) when is_binary(URITemplate) ->
+ expand(parse(URITemplate), Vars);
+expand(URITemplate, Vars) ->
+ expand1(URITemplate, Vars).
+
+expand1([], _) ->
+ [];
+expand1([Literal|Tail], Vars) when is_binary(Literal) ->
+ [Literal|expand1(Tail, Vars)];
+expand1([{expr, simple_string_expansion, VarList}|Tail], Vars) ->
+ [simple_string_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, reserved_expansion, VarList}|Tail], Vars) ->
+ [reserved_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, fragment_expansion, VarList}|Tail], Vars) ->
+ [fragment_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, label_expansion_with_dot_prefix, VarList}|Tail], Vars) ->
+ [label_expansion_with_dot_prefix(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, path_segment_expansion, VarList}|Tail], Vars) ->
+ [path_segment_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, path_style_parameter_expansion, VarList}|Tail], Vars) ->
+ [path_style_parameter_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, form_style_query_expansion, VarList}|Tail], Vars) ->
+ [form_style_query_expansion(VarList, Vars)|expand1(Tail, Vars)];
+expand1([{expr, form_style_query_continuation, VarList}|Tail], Vars) ->
+ [form_style_query_continuation(VarList, Vars)|expand1(Tail, Vars)].
+
+simple_string_expansion(VarList, Vars) ->
+ lists:join($,, [
+ apply_modifier(Modifier, unreserved, $,, Value)
+ || {Modifier, _Name, Value} <- lookup_variables(VarList, Vars)]).
+
+reserved_expansion(VarList, Vars) ->
+ lists:join($,, [
+ apply_modifier(Modifier, reserved, $,, Value)
+ || {Modifier, _Name, Value} <- lookup_variables(VarList, Vars)]).
+
+fragment_expansion(VarList, Vars) ->
+ case reserved_expansion(VarList, Vars) of
+ [] -> [];
+ Expanded -> [$#, Expanded]
+ end.
+
+label_expansion_with_dot_prefix(VarList, Vars) ->
+ segment_expansion(VarList, Vars, $.).
+
+path_segment_expansion(VarList, Vars) ->
+ segment_expansion(VarList, Vars, $/).
+
+segment_expansion(VarList, Vars, Sep) ->
+ Expanded = lists:join(Sep, [
+ apply_modifier(Modifier, unreserved, Sep, Value)
+ || {Modifier, _Name, Value} <- lookup_variables(VarList, Vars)]),
+ case Expanded of
+ [] -> [];
+ [[]] -> [];
+ _ -> [Sep, Expanded]
+ end.
+
+path_style_parameter_expansion(VarList, Vars) ->
+ parameter_expansion(VarList, Vars, $;, $;, trim).
+
+form_style_query_expansion(VarList, Vars) ->
+ parameter_expansion(VarList, Vars, $?, $&, no_trim).
+
+form_style_query_continuation(VarList, Vars) ->
+ parameter_expansion(VarList, Vars, $&, $&, no_trim).
+
+parameter_expansion(VarList, Vars, LeadingSep, Sep, Trim) ->
+ Expanded = lists:join(Sep, [
+ apply_parameter_modifier(Modifier, unreserved, Sep, Trim, Name, Value)
+ || {Modifier, Name, Value} <- lookup_variables(VarList, Vars)]),
+ case Expanded of
+ [] -> [];
+ [[]] -> [];
+ _ -> [LeadingSep, Expanded]
+ end.
+
+lookup_variables([], _) ->
+ [];
+lookup_variables([{Modifier, Name}|Tail], Vars) ->
+ case Vars of
+ #{Name := Value} -> [{Modifier, Name, Value}|lookup_variables(Tail, Vars)];
+ _ -> lookup_variables(Tail, Vars)
+ end.
+
+apply_modifier(no_modifier, AllowedChars, _, List) when is_list(List) ->
+ lists:join($,, [urlencode(Value, AllowedChars) || Value <- List]);
+apply_modifier(explode_modifier, AllowedChars, ExplodeSep, List) when is_list(List) ->
+ lists:join(ExplodeSep, [urlencode(Value, AllowedChars) || Value <- List]);
+apply_modifier(Modifier, AllowedChars, ExplodeSep, Map) when is_map(Map) ->
+ {JoinSep, KVSep} = case Modifier of
+ no_modifier -> {$,, $,};
+ explode_modifier -> {ExplodeSep, $=}
+ end,
+ lists:reverse(lists:join(JoinSep,
+ maps:fold(fun(Key, Value, Acc) ->
+ [[
+ urlencode(Key, AllowedChars),
+ KVSep,
+ urlencode(Value, AllowedChars)
+ ]|Acc]
+ end, [], Map)
+ ));
+apply_modifier({prefix_modifier, MaxLen}, AllowedChars, _, Value) ->
+ urlencode(string:slice(binarize(Value), 0, MaxLen), AllowedChars);
+apply_modifier(_, AllowedChars, _, Value) ->
+ urlencode(binarize(Value), AllowedChars).
+
+apply_parameter_modifier(_, _, _, _, _, []) ->
+ [];
+apply_parameter_modifier(_, _, _, _, _, Map) when Map =:= #{} ->
+ [];
+apply_parameter_modifier(no_modifier, AllowedChars, _, _, Name, List) when is_list(List) ->
+ [
+ Name,
+ $=,
+ lists:join($,, [urlencode(Value, AllowedChars) || Value <- List])
+ ];
+apply_parameter_modifier(explode_modifier, AllowedChars, ExplodeSep, _, Name, List) when is_list(List) ->
+ lists:join(ExplodeSep, [[
+ Name,
+ $=,
+ urlencode(Value, AllowedChars)
+ ] || Value <- List]);
+apply_parameter_modifier(Modifier, AllowedChars, ExplodeSep, _, Name, Map) when is_map(Map) ->
+ {JoinSep, KVSep} = case Modifier of
+ no_modifier -> {$,, $,};
+ explode_modifier -> {ExplodeSep, $=}
+ end,
+ [
+ case Modifier of
+ no_modifier ->
+ [
+ Name,
+ $=
+ ];
+ explode_modifier ->
+ []
+ end,
+ lists:reverse(lists:join(JoinSep,
+ maps:fold(fun(Key, Value, Acc) ->
+ [[
+ urlencode(Key, AllowedChars),
+ KVSep,
+ urlencode(Value, AllowedChars)
+ ]|Acc]
+ end, [], Map)
+ ))
+ ];
+apply_parameter_modifier(Modifier, AllowedChars, _, Trim, Name, Value0) ->
+ Value1 = binarize(Value0),
+ Value = case Modifier of
+ {prefix_modifier, MaxLen} ->
+ string:slice(Value1, 0, MaxLen);
+ no_modifier ->
+ Value1
+ end,
+ [
+ Name,
+ case Value of
+ <<>> when Trim =:= trim ->
+ [];
+ <<>> when Trim =:= no_trim ->
+ $=;
+ _ ->
+ [
+ $=,
+ urlencode(Value, AllowedChars)
+ ]
+ end
+ ].
+
+binarize(Value) when is_integer(Value) ->
+ integer_to_binary(Value);
+binarize(Value) when is_float(Value) ->
+ float_to_binary(Value, [{decimals, 10}, compact]);
+binarize(Value) ->
+ Value.
+
+urlencode(Value, unreserved) ->
+ urlencode_unreserved(Value, <<>>);
+urlencode(Value, reserved) ->
+ urlencode_reserved(Value, <<>>).
+
+urlencode_unreserved(<<C,R/bits>>, Acc)
+ when ?IS_URI_UNRESERVED(C) ->
+ urlencode_unreserved(R, <<Acc/binary,C>>);
+urlencode_unreserved(<<C,R/bits>>, Acc) ->
+ urlencode_unreserved(R, <<Acc/binary,$%,?HEX(C)>>);
+urlencode_unreserved(<<>>, Acc) ->
+ Acc.
+
+urlencode_reserved(<<$%,H,L,R/bits>>, Acc)
+ when ?IS_HEX(H), ?IS_HEX(L) ->
+ urlencode_reserved(R, <<Acc/binary,$%,H,L>>);
+urlencode_reserved(<<C,R/bits>>, Acc)
+ when ?IS_URI_UNRESERVED(C) or ?IS_URI_GEN_DELIMS(C) or ?IS_URI_SUB_DELIMS(C) ->
+ urlencode_reserved(R, <<Acc/binary,C>>);
+urlencode_reserved(<<C,R/bits>>, Acc) ->
+ urlencode_reserved(R, <<Acc/binary,$%,?HEX(C)>>);
+urlencode_reserved(<<>>, Acc) ->
+ Acc.
+
+-ifdef(TEST).
+expand_uritemplate_test_() ->
+ Files = filelib:wildcard("deps/uritemplate-tests/*.json"),
+ lists:flatten([begin
+ {ok, JSON} = file:read_file(File),
+ Tests = jsx:decode(JSON, [return_maps]),
+ [begin
+ %% Erlang doesn't have a NULL value.
+ Vars = maps:remove(<<"undef">>, Vars0),
+ [
+ {iolist_to_binary(io_lib:format("~s - ~s: ~s => ~s",
+ [filename:basename(File), Section, URITemplate,
+ if
+ is_list(Expected) -> lists:join(<<" OR ">>, Expected);
+ true -> Expected
+ end
+ ])),
+ fun() ->
+ io:format("expected: ~0p", [Expected]),
+ case Expected of
+ false ->
+ {'EXIT', _} = (catch expand(URITemplate, Vars));
+ [_|_] ->
+ Result = iolist_to_binary(expand(URITemplate, Vars)),
+ io:format("~p", [Result]),
+ true = lists:member(Result, Expected);
+ _ ->
+ Expected = iolist_to_binary(expand(URITemplate, Vars))
+ end
+ end}
+ || [URITemplate, Expected] <- Cases]
+ end || {Section, #{
+ <<"variables">> := Vars0,
+ <<"testcases">> := Cases
+ }} <- maps:to_list(Tests)]
+ end || File <- Files]).
+-endif.
diff --git a/server/_build/default/lib/cowlib/src/cow_ws.erl b/server/_build/default/lib/cowlib/src/cow_ws.erl
new file mode 100644
index 0000000..27c7c87
--- /dev/null
+++ b/server/_build/default/lib/cowlib/src/cow_ws.erl
@@ -0,0 +1,741 @@
+%% Copyright (c) 2015-2023, Loïc Hoguin <essen@ninenines.eu>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+-module(cow_ws).
+
+-export([key/0]).
+-export([encode_key/1]).
+
+-export([negotiate_permessage_deflate/3]).
+-export([negotiate_x_webkit_deflate_frame/3]).
+
+-export([validate_permessage_deflate/3]).
+
+-export([parse_header/3]).
+-export([parse_payload/9]).
+-export([make_frame/4]).
+
+-export([frame/2]).
+-export([masked_frame/2]).
+
+-type close_code() :: 1000..1003 | 1006..1011 | 3000..4999.
+-export_type([close_code/0]).
+
+-type extensions() :: map().
+-export_type([extensions/0]).
+
+-type deflate_opts() :: #{
+ %% Compression parameters.
+ level => zlib:zlevel(),
+ mem_level => zlib:zmemlevel(),
+ strategy => zlib:zstrategy(),
+
+ %% Whether the compression context will carry over between frames.
+ server_context_takeover => takeover | no_takeover,
+ client_context_takeover => takeover | no_takeover,
+
+ %% LZ77 sliding window size limits.
+ server_max_window_bits => 8..15,
+ client_max_window_bits => 8..15
+}.
+-export_type([deflate_opts/0]).
+
+-type frag_state() :: undefined | {fin | nofin, text | binary, rsv()}.
+-export_type([frag_state/0]).
+
+-type frame() :: close | ping | pong
+ | {text | binary | close | ping | pong, iodata()}
+ | {close, close_code(), iodata()}
+ | {fragment, fin | nofin, text | binary | continuation, iodata()}.
+-export_type([frame/0]).
+
+-type frame_type() :: fragment | text | binary | close | ping | pong.
+-export_type([frame_type/0]).
+
+-type mask_key() :: undefined | 0..16#ffffffff.
+-export_type([mask_key/0]).
+
+-type rsv() :: <<_:3>>.
+-export_type([rsv/0]).
+
+-type utf8_state() :: 0..8 | undefined.
+-export_type([utf8_state/0]).
+
+%% @doc Generate a key for the Websocket handshake request.
+
+-spec key() -> binary().
+key() ->
+ base64:encode(crypto:strong_rand_bytes(16)).
+
+%% @doc Encode the key into the accept value for the Websocket handshake response.
+
+-spec encode_key(binary()) -> binary().
+encode_key(Key) ->
+ base64:encode(crypto:hash(sha, [Key, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"])).
+
+%% @doc Negotiate the permessage-deflate extension.
+
+-spec negotiate_permessage_deflate(
+ [binary() | {binary(), binary()}], Exts, deflate_opts())
+ -> ignore | {ok, iolist(), Exts} when Exts::extensions().
+%% Ignore if deflate already negotiated.
+negotiate_permessage_deflate(_, #{deflate := _}, _) ->
+ ignore;
+negotiate_permessage_deflate(Params, Extensions, Opts) ->
+ case lists:usort(Params) of
+ %% Ignore if multiple parameters with the same name.
+ Params2 when length(Params) =/= length(Params2) ->
+ ignore;
+ Params2 ->
+ negotiate_permessage_deflate1(Params2, Extensions, Opts)
+ end.
+
+negotiate_permessage_deflate1(Params, Extensions, Opts) ->
+ %% We are allowed to send back no_takeover even if the client
+ %% accepts takeover. Therefore we use no_takeover if any of
+ %% the inputs have it.
+ ServerTakeover = maps:get(server_context_takeover, Opts, takeover),
+ ClientTakeover = maps:get(client_context_takeover, Opts, takeover),
+ %% We can send back window bits smaller than or equal to what
+ %% the client sends us.
+ ServerMaxWindowBits = maps:get(server_max_window_bits, Opts, 15),
+ ClientMaxWindowBits = maps:get(client_max_window_bits, Opts, 15),
+ %% We may need to send back no_context_takeover depending on configuration.
+ RespParams0 = case ServerTakeover of
+ takeover -> [];
+ no_takeover -> [<<"; server_no_context_takeover">>]
+ end,
+ RespParams1 = case ClientTakeover of
+ takeover -> RespParams0;
+ no_takeover -> [<<"; client_no_context_takeover">>|RespParams0]
+ end,
+ Negotiated0 = #{
+ server_context_takeover => ServerTakeover,
+ client_context_takeover => ClientTakeover,
+ server_max_window_bits => ServerMaxWindowBits,
+ client_max_window_bits => ClientMaxWindowBits
+ },
+ case negotiate_params(Params, Negotiated0, RespParams1) of
+ ignore ->
+ ignore;
+ {#{server_max_window_bits := SB}, _} when SB > ServerMaxWindowBits ->
+ ignore;
+ {#{client_max_window_bits := CB}, _} when CB > ClientMaxWindowBits ->
+ ignore;
+ {Negotiated, RespParams2} ->
+ %% We add the configured max window bits if necessary.
+ RespParams = case Negotiated of
+ #{server_max_window_bits_set := true} -> RespParams2;
+ _ when ServerMaxWindowBits =:= 15 -> RespParams2;
+ _ -> [<<"; server_max_window_bits=">>,
+ integer_to_binary(ServerMaxWindowBits)|RespParams2]
+ end,
+ {Inflate, Deflate} = init_permessage_deflate(
+ maps:get(client_max_window_bits, Negotiated),
+ maps:get(server_max_window_bits, Negotiated), Opts),
+ {ok, [<<"permessage-deflate">>, RespParams], Extensions#{
+ deflate => Deflate,
+ deflate_takeover => maps:get(server_context_takeover, Negotiated),
+ inflate => Inflate,
+ inflate_takeover => maps:get(client_context_takeover, Negotiated)}}
+ end.
+
+negotiate_params([], Negotiated, RespParams) ->
+ {Negotiated, RespParams};
+%% We must only send the client_max_window_bits parameter if the
+%% request explicitly indicated the client supports it.
+negotiate_params([<<"client_max_window_bits">>|Tail], Negotiated, RespParams) ->
+ CB = maps:get(client_max_window_bits, Negotiated),
+ negotiate_params(Tail, Negotiated#{client_max_window_bits_set => true},
+ [<<"; client_max_window_bits=">>, integer_to_binary(CB)|RespParams]);
+negotiate_params([{<<"client_max_window_bits">>, Max}|Tail], Negotiated, RespParams) ->
+ CB0 = maps:get(client_max_window_bits, Negotiated, undefined),
+ case parse_max_window_bits(Max) of
+ error ->
+ ignore;
+ CB when CB =< CB0 ->
+ negotiate_params(Tail, Negotiated#{client_max_window_bits => CB},
+ [<<"; client_max_window_bits=">>, Max|RespParams]);
+ %% When the client sends window bits larger than the server wants
+ %% to use, we use what the server defined.
+ _ ->
+ negotiate_params(Tail, Negotiated,
+ [<<"; client_max_window_bits=">>, integer_to_binary(CB0)|RespParams])
+ end;
+negotiate_params([{<<"server_max_window_bits">>, Max}|Tail], Negotiated, RespParams) ->
+ SB0 = maps:get(server_max_window_bits, Negotiated, undefined),
+ case parse_max_window_bits(Max) of
+ error ->
+ ignore;
+ SB when SB =< SB0 ->
+ negotiate_params(Tail, Negotiated#{
+ server_max_window_bits => SB,
+ server_max_window_bits_set => true},
+ [<<"; server_max_window_bits=">>, Max|RespParams]);
+ %% When the client sends window bits larger than the server wants
+ %% to use, we use what the server defined. The parameter will be
+ %% set only when this function returns.
+ _ ->
+ negotiate_params(Tail, Negotiated, RespParams)
+ end;
+%% We only need to send the no_context_takeover parameter back
+%% here if we didn't already define it via configuration.
+negotiate_params([<<"client_no_context_takeover">>|Tail], Negotiated, RespParams) ->
+ case maps:get(client_context_takeover, Negotiated) of
+ no_takeover ->
+ negotiate_params(Tail, Negotiated, RespParams);
+ takeover ->
+ negotiate_params(Tail, Negotiated#{client_context_takeover => no_takeover},
+ [<<"; client_no_context_takeover">>|RespParams])
+ end;
+negotiate_params([<<"server_no_context_takeover">>|Tail], Negotiated, RespParams) ->
+ case maps:get(server_context_takeover, Negotiated) of
+ no_takeover ->
+ negotiate_params(Tail, Negotiated, RespParams);
+ takeover ->
+ negotiate_params(Tail, Negotiated#{server_context_takeover => no_takeover},
+ [<<"; server_no_context_takeover">>|RespParams])
+ end;
+%% Ignore if unknown parameter; ignore if parameter with invalid or missing value.
+negotiate_params(_, _, _) ->
+ ignore.
+
+parse_max_window_bits(<<"8">>) -> 8;
+parse_max_window_bits(<<"9">>) -> 9;
+parse_max_window_bits(<<"10">>) -> 10;
+parse_max_window_bits(<<"11">>) -> 11;
+parse_max_window_bits(<<"12">>) -> 12;
+parse_max_window_bits(<<"13">>) -> 13;
+parse_max_window_bits(<<"14">>) -> 14;
+parse_max_window_bits(<<"15">>) -> 15;
+parse_max_window_bits(_) -> error.
+
+%% A negative WindowBits value indicates that zlib headers are not used.
+init_permessage_deflate(InflateWindowBits, DeflateWindowBits, Opts) ->
+ Inflate = zlib:open(),
+ ok = zlib:inflateInit(Inflate, -InflateWindowBits),
+ Deflate = zlib:open(),
+ %% zlib 1.2.11+ now rejects -8. It used to transform it to -9.
+ %% We need to use 9 when 8 is requested for interoperability.
+ DeflateWindowBits2 = case DeflateWindowBits of
+ 8 -> 9;
+ _ -> DeflateWindowBits
+ end,
+ ok = zlib:deflateInit(Deflate,
+ maps:get(level, Opts, best_compression),
+ deflated,
+ -DeflateWindowBits2,
+ maps:get(mem_level, Opts, 8),
+ maps:get(strategy, Opts, default)),
+ %% Set the owner pid of the zlib contexts if requested.
+ case Opts of
+ #{owner := Pid} -> set_owner(Pid, Inflate, Deflate);
+ _ -> ok
+ end,
+ {Inflate, Deflate}.
+
+-ifdef(OTP_RELEASE).
+%% Using is_port/1 on a zlib context results in a Dialyzer warning in OTP 21.
+%% This function helps silence that warning while staying compatible
+%% with all supported versions.
+
+set_owner(Pid, Inflate, Deflate) ->
+ zlib:set_controlling_process(Inflate, Pid),
+ zlib:set_controlling_process(Deflate, Pid).
+-else.
+%% The zlib port became a reference in OTP 20.1+. There
+%% was however no way to change the controlling process
+%% until the OTP 20.1.3 patch version. Since we can't
+%% enable compression for 20.1, 20.1.1 and 20.1.2 we
+%% explicitly crash. The caller should ignore this extension.
+
+set_owner(Pid, Inflate, Deflate) when is_port(Inflate) ->
+ true = erlang:port_connect(Inflate, Pid),
+ true = unlink(Inflate),
+ true = erlang:port_connect(Deflate, Pid),
+ true = unlink(Deflate),
+ ok;
+set_owner(Pid, Inflate, Deflate) ->
+ case erlang:function_exported(zlib, set_controlling_process, 2) of
+ true ->
+ zlib:set_controlling_process(Inflate, Pid),
+ zlib:set_controlling_process(Deflate, Pid);
+ false ->
+ exit({error, incompatible_zlib_version,
+ 'OTP 20.1, 20.1.1 and 20.1.2 are missing required functionality.'})
+ end.
+-endif.
+
+%% @doc Negotiate the x-webkit-deflate-frame extension.
+%%
+%% The implementation is very basic and none of the parameters
+%% are currently supported.
+
+-spec negotiate_x_webkit_deflate_frame(
+ [binary() | {binary(), binary()}], Exts, deflate_opts())
+ -> ignore | {ok, binary(), Exts} when Exts::extensions().
+negotiate_x_webkit_deflate_frame(_, #{deflate := _}, _) ->
+ ignore;
+negotiate_x_webkit_deflate_frame(_Params, Extensions, Opts) ->
+ % Since we are negotiating an unconstrained deflate-frame
+ % then we must be willing to accept frames using the
+ % maximum window size which is 2^15.
+ {Inflate, Deflate} = init_permessage_deflate(15, 15, Opts),
+ {ok, <<"x-webkit-deflate-frame">>,
+ Extensions#{
+ deflate => Deflate,
+ deflate_takeover => takeover,
+ inflate => Inflate,
+ inflate_takeover => takeover}}.
+
+%% @doc Validate the negotiated permessage-deflate extension.
+
+%% Error when more than one deflate extension was negotiated.
+validate_permessage_deflate(_, #{deflate := _}, _) ->
+ error;
+validate_permessage_deflate(Params, Extensions, Opts) ->
+ case lists:usort(Params) of
+ %% Error if multiple parameters with the same name.
+ Params2 when length(Params) =/= length(Params2) ->
+ error;
+ Params2 ->
+ case parse_response_permessage_deflate_params(Params2, 15, takeover, 15, takeover) of
+ error ->
+ error;
+ {ClientWindowBits, ClientTakeOver, ServerWindowBits, ServerTakeOver} ->
+ {Inflate, Deflate} = init_permessage_deflate(ServerWindowBits, ClientWindowBits, Opts),
+ {ok, Extensions#{
+ deflate => Deflate,
+ deflate_takeover => ClientTakeOver,
+ inflate => Inflate,
+ inflate_takeover => ServerTakeOver}}
+ end
+ end.
+
+parse_response_permessage_deflate_params([], CB, CTO, SB, STO) ->
+ {CB, CTO, SB, STO};
+parse_response_permessage_deflate_params([{<<"client_max_window_bits">>, Max}|Tail], _, CTO, SB, STO) ->
+ case parse_max_window_bits(Max) of
+ error -> error;
+ CB -> parse_response_permessage_deflate_params(Tail, CB, CTO, SB, STO)
+ end;
+parse_response_permessage_deflate_params([<<"client_no_context_takeover">>|Tail], CB, _, SB, STO) ->
+ parse_response_permessage_deflate_params(Tail, CB, no_takeover, SB, STO);
+parse_response_permessage_deflate_params([{<<"server_max_window_bits">>, Max}|Tail], CB, CTO, _, STO) ->
+ case parse_max_window_bits(Max) of
+ error -> error;
+ SB -> parse_response_permessage_deflate_params(Tail, CB, CTO, SB, STO)
+ end;
+parse_response_permessage_deflate_params([<<"server_no_context_takeover">>|Tail], CB, CTO, SB, _) ->
+ parse_response_permessage_deflate_params(Tail, CB, CTO, SB, no_takeover);
+%% Error if unknown parameter; error if parameter with invalid or missing value.
+parse_response_permessage_deflate_params(_, _, _, _, _) ->
+ error.
+
+%% @doc Parse and validate the Websocket frame header.
+%%
+%% This function also updates the fragmentation state according to
+%% information found in the frame's header.
+
+-spec parse_header(binary(), extensions(), frag_state())
+ -> error | more | {frame_type(), frag_state(), rsv(), non_neg_integer(), mask_key(), binary()}.
+%% RSV bits MUST be 0 unless an extension is negotiated
+%% that defines meanings for non-zero values.
+parse_header(<< _:1, Rsv:3, _/bits >>, Extensions, _) when Extensions =:= #{}, Rsv =/= 0 -> error;
+%% Last 2 RSV bits MUST be 0 if deflate-frame extension is used.
+parse_header(<< _:2, 1:1, _/bits >>, #{deflate := _}, _) -> error;
+parse_header(<< _:3, 1:1, _/bits >>, #{deflate := _}, _) -> error;
+%% Invalid opcode. Note that these opcodes may be used by extensions.
+parse_header(<< _:4, 3:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 4:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 5:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 6:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 7:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 11:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 12:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 13:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 14:4, _/bits >>, _, _) -> error;
+parse_header(<< _:4, 15:4, _/bits >>, _, _) -> error;
+%% Control frames MUST NOT be fragmented.
+parse_header(<< 0:1, _:3, Opcode:4, _/bits >>, _, _) when Opcode >= 8 -> error;
+%% A frame MUST NOT use the zero opcode unless fragmentation was initiated.
+parse_header(<< _:4, 0:4, _/bits >>, _, undefined) -> error;
+%% Non-control opcode when expecting control message or next fragment.
+parse_header(<< _:4, 1:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 2:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 3:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 4:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 5:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 6:4, _/bits >>, _, {_, _, _}) -> error;
+parse_header(<< _:4, 7:4, _/bits >>, _, {_, _, _}) -> error;
+%% Close control frame length MUST be 0 or >= 2.
+parse_header(<< _:4, 8:4, _:1, 1:7, _/bits >>, _, _) -> error;
+%% Close control frame with incomplete close code. Need more data.
+parse_header(Data = << _:4, 8:4, 0:1, Len:7, _/bits >>, _, _) when Len > 1, byte_size(Data) < 4 -> more;
+parse_header(Data = << _:4, 8:4, 1:1, Len:7, _/bits >>, _, _) when Len > 1, byte_size(Data) < 8 -> more;
+%% 7 bits payload length.
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 0:1, Len:7, Rest/bits >>, _, FragState) when Len < 126 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, undefined, Rest);
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 1:1, Len:7, MaskKey:32, Rest/bits >>, _, FragState) when Len < 126 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, MaskKey, Rest);
+%% 16 bits payload length.
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 0:1, 126:7, Len:16, Rest/bits >>, _, FragState) when Len > 125, Opcode < 8 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, undefined, Rest);
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 1:1, 126:7, Len:16, MaskKey:32, Rest/bits >>, _, FragState) when Len > 125, Opcode < 8 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, MaskKey, Rest);
+%% 63 bits payload length.
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 0:1, 127:7, 0:1, Len:63, Rest/bits >>, _, FragState) when Len > 16#ffff, Opcode < 8 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, undefined, Rest);
+parse_header(<< Fin:1, Rsv:3/bits, Opcode:4, 1:1, 127:7, 0:1, Len:63, MaskKey:32, Rest/bits >>, _, FragState) when Len > 16#ffff, Opcode < 8 ->
+ parse_header(Opcode, Fin, FragState, Rsv, Len, MaskKey, Rest);
+%% When payload length is over 63 bits, the most significant bit MUST be 0.
+parse_header(<< _:9, 127:7, 1:1, _/bits >>, _, _) -> error;
+%% For the next two clauses, it can be one of the following:
+%%
+%% * The minimal number of bytes MUST be used to encode the length
+%% * All control frames MUST have a payload length of 125 bytes or less
+parse_header(<< _:8, 0:1, 126:7, _:16, _/bits >>, _, _) -> error;
+parse_header(<< _:8, 1:1, 126:7, _:48, _/bits >>, _, _) -> error;
+parse_header(<< _:8, 0:1, 127:7, _:64, _/bits >>, _, _) -> error;
+parse_header(<< _:8, 1:1, 127:7, _:96, _/bits >>, _, _) -> error;
+%% Need more data.
+parse_header(_, _, _) -> more.
+
+parse_header(Opcode, Fin, FragState, Rsv, Len, MaskKey, Rest) ->
+ Type = opcode_to_frame_type(Opcode),
+ Type2 = case Fin of
+ 0 -> fragment;
+ 1 -> Type
+ end,
+ {Type2, frag_state(Type, Fin, Rsv, FragState), Rsv, Len, MaskKey, Rest}.
+
+opcode_to_frame_type(0) -> fragment;
+opcode_to_frame_type(1) -> text;
+opcode_to_frame_type(2) -> binary;
+opcode_to_frame_type(8) -> close;
+opcode_to_frame_type(9) -> ping;
+opcode_to_frame_type(10) -> pong.
+
+frag_state(Type, 0, Rsv, undefined) -> {nofin, Type, Rsv};
+frag_state(fragment, 0, _, FragState = {nofin, _, _}) -> FragState;
+frag_state(fragment, 1, _, {nofin, Type, Rsv}) -> {fin, Type, Rsv};
+frag_state(_, 1, _, FragState) -> FragState.
+
+%% @doc Parse and validate the frame's payload.
+%%
+%% Validation is only required for text and close frames which feature
+%% a UTF-8 payload.
+
+-spec parse_payload(binary(), mask_key(), utf8_state(), non_neg_integer(),
+ frame_type(), non_neg_integer(), frag_state(), extensions(), rsv())
+ -> {ok, binary(), utf8_state(), binary()}
+ | {ok, close_code(), binary(), utf8_state(), binary()}
+ | {more, binary(), utf8_state()}
+ | {more, close_code(), binary(), utf8_state()}
+ | {error, badframe | badencoding}.
+%% Empty last frame of compressed message.
+parse_payload(Data, _, Utf8State, _, _, 0, {fin, _, << 1:1, 0:2 >>},
+ #{inflate := Inflate, inflate_takeover := TakeOver}, _) ->
+ _ = zlib:inflate(Inflate, << 0, 0, 255, 255 >>),
+ case TakeOver of
+ no_takeover -> zlib:inflateReset(Inflate);
+ takeover -> ok
+ end,
+ {ok, <<>>, Utf8State, Data};
+%% Compressed fragmented frame.
+parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState = {_, _, << 1:1, 0:2 >>},
+ #{inflate := Inflate, inflate_takeover := TakeOver}, _) ->
+ {Data2, Rest, Eof} = split_payload(Data, Len),
+ Payload = inflate_frame(unmask(Data2, MaskKey, ParsedLen), Inflate, TakeOver, FragState, Eof),
+ validate_payload(Payload, Rest, Utf8State, ParsedLen, Type, FragState, Eof);
+%% Compressed frame.
+parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState,
+ #{inflate := Inflate, inflate_takeover := TakeOver}, << 1:1, 0:2 >>) when Type =:= text; Type =:= binary ->
+ {Data2, Rest, Eof} = split_payload(Data, Len),
+ Payload = inflate_frame(unmask(Data2, MaskKey, ParsedLen), Inflate, TakeOver, FragState, Eof),
+ validate_payload(Payload, Rest, Utf8State, ParsedLen, Type, FragState, Eof);
+%% Empty frame.
+parse_payload(Data, _, Utf8State, 0, _, 0, _, _, _)
+ when Utf8State =:= 0; Utf8State =:= undefined ->
+ {ok, <<>>, Utf8State, Data};
+%% Start of close frame.
+parse_payload(Data, MaskKey, Utf8State, 0, Type = close, Len, FragState, _, << 0:3 >>) ->
+ {<< MaskedCode:2/binary, Data2/bits >>, Rest, Eof} = split_payload(Data, Len),
+ << CloseCode:16 >> = unmask(MaskedCode, MaskKey, 0),
+ case validate_close_code(CloseCode) of
+ ok ->
+ Payload = unmask(Data2, MaskKey, 2),
+ case validate_payload(Payload, Rest, Utf8State, 2, Type, FragState, Eof) of
+ {ok, _, Utf8State2, _} -> {ok, CloseCode, Payload, Utf8State2, Rest};
+ {more, _, Utf8State2} -> {more, CloseCode, Payload, Utf8State2};
+ Error -> Error
+ end;
+ error ->
+ {error, badframe}
+ end;
+%% Normal frame.
+parse_payload(Data, MaskKey, Utf8State, ParsedLen, Type, Len, FragState, _, << 0:3 >>) ->
+ {Data2, Rest, Eof} = split_payload(Data, Len),
+ Payload = unmask(Data2, MaskKey, ParsedLen),
+ validate_payload(Payload, Rest, Utf8State, ParsedLen, Type, FragState, Eof).
+
+split_payload(Data, Len) ->
+ case byte_size(Data) of
+ Len ->
+ {Data, <<>>, true};
+ DataLen when DataLen < Len ->
+ {Data, <<>>, false};
+ _ ->
+ << Data2:Len/binary, Rest/bits >> = Data,
+ {Data2, Rest, true}
+ end.
+
+validate_close_code(Code) ->
+ if
+ Code < 1000 -> error;
+ Code =:= 1004 -> error;
+ Code =:= 1005 -> error;
+ Code =:= 1006 -> error;
+ Code > 1011, Code < 3000 -> error;
+ Code > 4999 -> error;
+ true -> ok
+ end.
+
+unmask(Data, undefined, _) ->
+ Data;
+unmask(Data, MaskKey, 0) ->
+ mask(Data, MaskKey, <<>>);
+%% We unmask on the fly so we need to continue from the right mask byte.
+unmask(Data, MaskKey, UnmaskedLen) ->
+ Left = UnmaskedLen rem 4,
+ Right = 4 - Left,
+ MaskKey2 = (MaskKey bsl (Left * 8)) + (MaskKey bsr (Right * 8)),
+ mask(Data, MaskKey2, <<>>).
+
+mask(<<>>, _, Unmasked) ->
+ Unmasked;
+mask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
+ T = O bxor MaskKey,
+ mask(Rest, MaskKey, << Acc/binary, T:32 >>);
+mask(<< O:24 >>, MaskKey, Acc) ->
+ << MaskKey2:24, _:8 >> = << MaskKey:32 >>,
+ T = O bxor MaskKey2,
+ << Acc/binary, T:24 >>;
+mask(<< O:16 >>, MaskKey, Acc) ->
+ << MaskKey2:16, _:16 >> = << MaskKey:32 >>,
+ T = O bxor MaskKey2,
+ << Acc/binary, T:16 >>;
+mask(<< O:8 >>, MaskKey, Acc) ->
+ << MaskKey2:8, _:24 >> = << MaskKey:32 >>,
+ T = O bxor MaskKey2,
+ << Acc/binary, T:8 >>.
+
+inflate_frame(Data, Inflate, TakeOver, FragState, true)
+ when FragState =:= undefined; element(1, FragState) =:= fin ->
+ Data2 = zlib:inflate(Inflate, << Data/binary, 0, 0, 255, 255 >>),
+ case TakeOver of
+ no_takeover -> zlib:inflateReset(Inflate);
+ takeover -> ok
+ end,
+ iolist_to_binary(Data2);
+inflate_frame(Data, Inflate, _T, _F, _E) ->
+ iolist_to_binary(zlib:inflate(Inflate, Data)).
+
+%% The Utf8State variable can be set to 'undefined' to disable the validation.
+validate_payload(Payload, _, undefined, _, _, _, false) ->
+ {more, Payload, undefined};
+validate_payload(Payload, Rest, undefined, _, _, _, true) ->
+ {ok, Payload, undefined, Rest};
+%% Text frames and close control frames MUST have a payload that is valid UTF-8.
+validate_payload(Payload, Rest, Utf8State, _, Type, _, Eof) when Type =:= text; Type =:= close ->
+ case validate_utf8(Payload, Utf8State) of
+ 1 -> {error, badencoding};
+ Utf8State2 when not Eof -> {more, Payload, Utf8State2};
+ 0 when Eof -> {ok, Payload, 0, Rest};
+ _ -> {error, badencoding}
+ end;
+validate_payload(Payload, Rest, Utf8State, _, fragment, {Fin, text, _}, Eof) ->
+ case validate_utf8(Payload, Utf8State) of
+ 1 -> {error, badencoding};
+ 0 when Eof -> {ok, Payload, 0, Rest};
+ Utf8State2 when Eof, Fin =:= nofin -> {ok, Payload, Utf8State2, Rest};
+ Utf8State2 when not Eof -> {more, Payload, Utf8State2};
+ _ -> {error, badencoding}
+ end;
+validate_payload(Payload, _, Utf8State, _, _, _, false) ->
+ {more, Payload, Utf8State};
+validate_payload(Payload, Rest, Utf8State, _, _, _, true) ->
+ {ok, Payload, Utf8State, Rest}.
+
+%% Based on the Flexible and Economical UTF-8 Decoder algorithm by
+%% Bjoern Hoehrmann <bjoern@hoehrmann.de> (http://bjoern.hoehrmann.de/utf-8/decoder/dfa/).
+%%
+%% The original algorithm has been unrolled into all combinations of values for C and State
+%% each with a clause. The common clauses were then grouped together.
+%%
+%% This function returns 0 on success, 1 on error, and 2..8 on incomplete data.
+validate_utf8(<<>>, State) -> State;
+validate_utf8(<< C, Rest/bits >>, 0) when C < 128 -> validate_utf8(Rest, 0);
+validate_utf8(<< C, Rest/bits >>, 2) when C >= 128, C < 144 -> validate_utf8(Rest, 0);
+validate_utf8(<< C, Rest/bits >>, 3) when C >= 128, C < 144 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 5) when C >= 128, C < 144 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 7) when C >= 128, C < 144 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 8) when C >= 128, C < 144 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 2) when C >= 144, C < 160 -> validate_utf8(Rest, 0);
+validate_utf8(<< C, Rest/bits >>, 3) when C >= 144, C < 160 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 5) when C >= 144, C < 160 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 6) when C >= 144, C < 160 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 7) when C >= 144, C < 160 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 2) when C >= 160, C < 192 -> validate_utf8(Rest, 0);
+validate_utf8(<< C, Rest/bits >>, 3) when C >= 160, C < 192 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 4) when C >= 160, C < 192 -> validate_utf8(Rest, 2);
+validate_utf8(<< C, Rest/bits >>, 6) when C >= 160, C < 192 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 7) when C >= 160, C < 192 -> validate_utf8(Rest, 3);
+validate_utf8(<< C, Rest/bits >>, 0) when C >= 194, C < 224 -> validate_utf8(Rest, 2);
+validate_utf8(<< 224, Rest/bits >>, 0) -> validate_utf8(Rest, 4);
+validate_utf8(<< C, Rest/bits >>, 0) when C >= 225, C < 237 -> validate_utf8(Rest, 3);
+validate_utf8(<< 237, Rest/bits >>, 0) -> validate_utf8(Rest, 5);
+validate_utf8(<< C, Rest/bits >>, 0) when C =:= 238; C =:= 239 -> validate_utf8(Rest, 3);
+validate_utf8(<< 240, Rest/bits >>, 0) -> validate_utf8(Rest, 6);
+validate_utf8(<< C, Rest/bits >>, 0) when C =:= 241; C =:= 242; C =:= 243 -> validate_utf8(Rest, 7);
+validate_utf8(<< 244, Rest/bits >>, 0) -> validate_utf8(Rest, 8);
+validate_utf8(_, _) -> 1.
+
+%% @doc Return a frame tuple from parsed state and data.
+
+-spec make_frame(frame_type(), binary(), close_code(), frag_state()) -> frame().
+%% Fragmented frame.
+make_frame(fragment, Payload, _, {Fin, Type, _}) -> {fragment, Fin, Type, Payload};
+make_frame(text, Payload, _, _) -> {text, Payload};
+make_frame(binary, Payload, _, _) -> {binary, Payload};
+make_frame(close, <<>>, undefined, _) -> close;
+make_frame(close, Payload, CloseCode, _) -> {close, CloseCode, Payload};
+make_frame(ping, <<>>, _, _) -> ping;
+make_frame(ping, Payload, _, _) -> {ping, Payload};
+make_frame(pong, <<>>, _, _) -> pong;
+make_frame(pong, Payload, _, _) -> {pong, Payload}.
+
+%% @doc Construct an unmasked Websocket frame.
+
+-spec frame(frame(), extensions()) -> iodata().
+%% Control frames. Control packets must not be > 125 in length.
+frame(close, _) ->
+ << 1:1, 0:3, 8:4, 0:8 >>;
+frame(ping, _) ->
+ << 1:1, 0:3, 9:4, 0:8 >>;
+frame(pong, _) ->
+ << 1:1, 0:3, 10:4, 0:8 >>;
+frame({close, Payload}, Extensions) ->
+ frame({close, 1000, Payload}, Extensions);
+frame({close, StatusCode, Payload}, _) ->
+ Len = 2 + iolist_size(Payload),
+ true = Len =< 125,
+ [<< 1:1, 0:3, 8:4, 0:1, Len:7, StatusCode:16 >>, Payload];
+frame({ping, Payload}, _) ->
+ Len = iolist_size(Payload),
+ true = Len =< 125,
+ [<< 1:1, 0:3, 9:4, 0:1, Len:7 >>, Payload];
+frame({pong, Payload}, _) ->
+ Len = iolist_size(Payload),
+ true = Len =< 125,
+ [<< 1:1, 0:3, 10:4, 0:1, Len:7 >>, Payload];
+%% Data frames, deflate-frame extension.
+frame({text, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver})
+ when Deflate =/= false ->
+ Payload2 = deflate_frame(Payload, Deflate, TakeOver),
+ Len = payload_length(Payload2),
+ [<< 1:1, 1:1, 0:2, 1:4, 0:1, Len/bits >>, Payload2];
+frame({binary, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver})
+ when Deflate =/= false ->
+ Payload2 = deflate_frame(Payload, Deflate, TakeOver),
+ Len = payload_length(Payload2),
+ [<< 1:1, 1:1, 0:2, 2:4, 0:1, Len/bits >>, Payload2];
+%% Data frames.
+frame({text, Payload}, _) ->
+ Len = payload_length(Payload),
+ [<< 1:1, 0:3, 1:4, 0:1, Len/bits >>, Payload];
+frame({binary, Payload}, _) ->
+ Len = payload_length(Payload),
+ [<< 1:1, 0:3, 2:4, 0:1, Len/bits >>, Payload].
+
+%% @doc Construct a masked Websocket frame.
+%%
+%% We use a mask key of 0 if there is no payload for close, ping and pong frames.
+
+-spec masked_frame(frame(), extensions()) -> iodata().
+%% Control frames. Control packets must not be > 125 in length.
+masked_frame(close, _) ->
+ << 1:1, 0:3, 8:4, 1:1, 0:39 >>;
+masked_frame(ping, _) ->
+ << 1:1, 0:3, 9:4, 1:1, 0:39 >>;
+masked_frame(pong, _) ->
+ << 1:1, 0:3, 10:4, 1:1, 0:39 >>;
+masked_frame({close, Payload}, Extensions) ->
+ frame({close, 1000, Payload}, Extensions);
+masked_frame({close, StatusCode, Payload}, _) ->
+ Len = 2 + iolist_size(Payload),
+ true = Len =< 125,
+ MaskKeyBin = << MaskKey:32 >> = crypto:strong_rand_bytes(4),
+ [<< 1:1, 0:3, 8:4, 1:1, Len:7 >>, MaskKeyBin, mask(iolist_to_binary([<< StatusCode:16 >>, Payload]), MaskKey, <<>>)];
+masked_frame({ping, Payload}, _) ->
+ Len = iolist_size(Payload),
+ true = Len =< 125,
+ MaskKeyBin = << MaskKey:32 >> = crypto:strong_rand_bytes(4),
+ [<< 1:1, 0:3, 9:4, 1:1, Len:7 >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)];
+masked_frame({pong, Payload}, _) ->
+ Len = iolist_size(Payload),
+ true = Len =< 125,
+ MaskKeyBin = << MaskKey:32 >> = crypto:strong_rand_bytes(4),
+ [<< 1:1, 0:3, 10:4, 1:1, Len:7 >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)];
+%% Data frames, deflate-frame extension.
+masked_frame({text, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver})
+ when Deflate =/= false ->
+ MaskKeyBin = << MaskKey:32 >> = crypto:strong_rand_bytes(4),
+ Payload2 = mask(deflate_frame(Payload, Deflate, TakeOver), MaskKey, <<>>),
+ Len = payload_length(Payload2),
+ [<< 1:1, 1:1, 0:2, 1:4, 1:1, Len/bits >>, MaskKeyBin, Payload2];
+masked_frame({binary, Payload}, #{deflate := Deflate, deflate_takeover := TakeOver})
+ when Deflate =/= false ->
+ MaskKeyBin = << MaskKey:32 >> = crypto:strong_rand_bytes(4),
+ Payload2 = mask(deflate_frame(Payload, Deflate, TakeOver), MaskKey, <<>>),
+ Len = payload_length(Payload2),
+ [<< 1:1, 1:1, 0:2, 2:4, 1:1, Len/bits >>, MaskKeyBin, Payload2];
+%% Data frames.
+masked_frame({text, Payload}, _) ->
+ MaskKeyBin = << MaskKey:32 >> = crypto:strong_rand_bytes(4),
+ Len = payload_length(Payload),
+ [<< 1:1, 0:3, 1:4, 1:1, Len/bits >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)];
+masked_frame({binary, Payload}, _) ->
+ MaskKeyBin = << MaskKey:32 >> = crypto:strong_rand_bytes(4),
+ Len = payload_length(Payload),
+ [<< 1:1, 0:3, 2:4, 1:1, Len/bits >>, MaskKeyBin, mask(iolist_to_binary(Payload), MaskKey, <<>>)].
+
+payload_length(Payload) ->
+ case iolist_size(Payload) of
+ N when N =< 125 -> << N:7 >>;
+ N when N =< 16#ffff -> << 126:7, N:16 >>;
+ N when N =< 16#7fffffffffffffff -> << 127:7, N:64 >>
+ end.
+
+deflate_frame(Payload, Deflate, TakeOver) ->
+ Deflated = iolist_to_binary(zlib:deflate(Deflate, Payload, sync)),
+ case TakeOver of
+ no_takeover -> zlib:deflateReset(Deflate);
+ takeover -> ok
+ end,
+ Len = byte_size(Deflated) - 4,
+ case Deflated of
+ << Body:Len/binary, 0:8, 0:8, 255:8, 255:8 >> -> Body;
+ _ -> Deflated
+ end.
diff --git a/server/_build/default/lib/jchat/ebin/jchat.app b/server/_build/default/lib/jchat/ebin/jchat.app
new file mode 100644
index 0000000..5b0453b
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat.app
@@ -0,0 +1,16 @@
+{application,jchat,
+ [{description,"JMAP-based Chat Server"},
+ {vsn,"0.1.0"},
+ {registered,[]},
+ {mod,{jchat_app,[]}},
+ {applications,[kernel,stdlib,crypto,inets,jsx,cowboy,mnesia,
+ bcrypt]},
+ {env,[]},
+ {modules,[jchat_app,jchat_auth,jchat_config,jchat_db,jchat_dev,
+ jchat_http,jchat_http_404,jchat_http_auth,
+ jchat_http_download,jchat_http_eventsource,
+ jchat_http_health,jchat_http_redirect,
+ jchat_http_static,jchat_http_upload,jchat_methods,
+ jchat_presence,jchat_push,jchat_sup,jchat_utils]},
+ {licenses,["Apache-2.0"]},
+ {links,[]}]}.
diff --git a/server/_build/default/lib/jchat/ebin/jchat_app.beam b/server/_build/default/lib/jchat/ebin/jchat_app.beam
new file mode 100644
index 0000000..4f3fe0e
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_app.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_auth.beam b/server/_build/default/lib/jchat/ebin/jchat_auth.beam
new file mode 100644
index 0000000..383a551
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_auth.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_config.beam b/server/_build/default/lib/jchat/ebin/jchat_config.beam
new file mode 100644
index 0000000..1493818
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_config.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_db.beam b/server/_build/default/lib/jchat/ebin/jchat_db.beam
new file mode 100644
index 0000000..81a3ceb
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_db.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_dev.beam b/server/_build/default/lib/jchat/ebin/jchat_dev.beam
new file mode 100644
index 0000000..27ff2bf
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_dev.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_http.beam b/server/_build/default/lib/jchat/ebin/jchat_http.beam
new file mode 100644
index 0000000..8a51785
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_http.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_http_404.beam b/server/_build/default/lib/jchat/ebin/jchat_http_404.beam
new file mode 100644
index 0000000..958cf93
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_http_404.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_http_auth.beam b/server/_build/default/lib/jchat/ebin/jchat_http_auth.beam
new file mode 100644
index 0000000..730b03e
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_http_auth.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_http_download.beam b/server/_build/default/lib/jchat/ebin/jchat_http_download.beam
new file mode 100644
index 0000000..30f8866
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_http_download.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_http_eventsource.beam b/server/_build/default/lib/jchat/ebin/jchat_http_eventsource.beam
new file mode 100644
index 0000000..d94ae23
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_http_eventsource.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_http_health.beam b/server/_build/default/lib/jchat/ebin/jchat_http_health.beam
new file mode 100644
index 0000000..be4c2ac
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_http_health.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_http_redirect.beam b/server/_build/default/lib/jchat/ebin/jchat_http_redirect.beam
new file mode 100644
index 0000000..a50f505
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_http_redirect.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_http_static.beam b/server/_build/default/lib/jchat/ebin/jchat_http_static.beam
new file mode 100644
index 0000000..861ab1e
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_http_static.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_http_upload.beam b/server/_build/default/lib/jchat/ebin/jchat_http_upload.beam
new file mode 100644
index 0000000..c322e41
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_http_upload.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_methods.beam b/server/_build/default/lib/jchat/ebin/jchat_methods.beam
new file mode 100644
index 0000000..86770ad
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_methods.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_presence.beam b/server/_build/default/lib/jchat/ebin/jchat_presence.beam
new file mode 100644
index 0000000..d58fd42
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_presence.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_push.beam b/server/_build/default/lib/jchat/ebin/jchat_push.beam
new file mode 100644
index 0000000..67c3b50
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_push.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_sup.beam b/server/_build/default/lib/jchat/ebin/jchat_sup.beam
new file mode 100644
index 0000000..0e8751b
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_sup.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/ebin/jchat_utils.beam b/server/_build/default/lib/jchat/ebin/jchat_utils.beam
new file mode 100644
index 0000000..c0c39a9
--- /dev/null
+++ b/server/_build/default/lib/jchat/ebin/jchat_utils.beam
Binary files differ
diff --git a/server/_build/default/lib/jchat/include b/server/_build/default/lib/jchat/include
new file mode 120000
index 0000000..0f0436a
--- /dev/null
+++ b/server/_build/default/lib/jchat/include
@@ -0,0 +1 @@
+../../../../include \ No newline at end of file
diff --git a/server/_build/default/lib/jchat/priv b/server/_build/default/lib/jchat/priv
new file mode 120000
index 0000000..d100101
--- /dev/null
+++ b/server/_build/default/lib/jchat/priv
@@ -0,0 +1 @@
+../../../../priv \ No newline at end of file
diff --git a/server/_build/default/lib/jchat/src b/server/_build/default/lib/jchat/src
new file mode 120000
index 0000000..b3e266f
--- /dev/null
+++ b/server/_build/default/lib/jchat/src
@@ -0,0 +1 @@
+../../../../src \ No newline at end of file
diff --git a/server/_build/default/lib/jsx/LICENSE b/server/_build/default/lib/jsx/LICENSE
new file mode 100644
index 0000000..de1b470
--- /dev/null
+++ b/server/_build/default/lib/jsx/LICENSE
@@ -0,0 +1,21 @@
+The MIT License
+
+Copyright (c) 2010-2013 alisdair sullivan <alisdairsullivan@yahoo.ca>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/server/_build/default/lib/jsx/README.md b/server/_build/default/lib/jsx/README.md
new file mode 100644
index 0000000..f4e27ce
--- /dev/null
+++ b/server/_build/default/lib/jsx/README.md
@@ -0,0 +1,696 @@
+# jsx (v3.0.0) #
+
+
+an erlang application for consuming, producing and manipulating [json][json].
+inspired by [yajl][yajl]
+
+**jsx** is built via [rebar3][rebar3]
+
+current status: ![](https://github.com/talentdeficit/jsx/workflows/EUnit/badge.svg)
+
+**jsx** is released under the terms of the [MIT][MIT] license
+
+copyright 2010-2016 alisdair sullivan
+
+## index ##
+
+* [quickstart](#quickstart)
+* [description](#description)
+ - [migrating from 1.x](#migrating)
+ - [json <-> erlang mapping](#json---erlang-mapping)
+ - [incomplete input](#incomplete-input)
+* [data types](#data-types)
+ - [`json_term()`](#json_term)
+ - [`json_text()`](#json_text)
+ - [`event()`](#event)
+ - [`option()`](#option)
+* [exports](#exports)
+ - [`encoder/3`, `decoder/3` & `parser/3`](#encoder3-decoder3--parser3)
+ - [`decode/1,2`](#decode12)
+ - [`encode/1,2`](#encode12)
+ - [`format/1,2`](#format12)
+ - [`minify/1`](#minify1)
+ - [`prettify/1`](#prettify1)
+ - [`is_json/1,2`](#is_json12)
+ - [`is_term/1,2`](#is_term12)
+* [callback exports](#callback_exports)
+ - [`Module:init/1`](#moduleinit1)
+ - [`Module:handle_event/2`](#modulehandle_event2)
+* [acknowledgements](#acknowledgements)
+
+
+## quickstart ##
+
+#### to add to a rebar3 project ####
+Add to `rebar.config`
+```erlang
+...
+{erl_opts, [debug_info]}.
+{deps, [
+ ...
+ {jsx, "~> 3.0"}
+]}.
+...
+```
+
+#### to build the library and run tests ####
+
+```bash
+$ rebar3 compile
+$ rebar3 eunit
+```
+
+#### to convert a utf8 binary containing a json string into an erlang term ####
+
+```erlang
+1> jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, []).
+#{<<"awesome">> => true,<<"library">> => <<"jsx">>}
+2> jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, [{return_maps, false}]).
+[{<<"library">>,<<"jsx">>},{<<"awesome">>,true}]
+3> jsx:decode(<<"[\"a\",\"list\",\"of\",\"words\"]">>).
+[<<"a">>, <<"list">>, <<"of">>, <<"words">>]
+```
+
+#### to convert an erlang term into a utf8 binary containing a json string ####
+
+```erlang
+1> jsx:encode(#{<<"library">> => <<"jsx">>, <<"awesome">> => true}).
+<<"{\"awesome\":true,\"library\":\"jsx\"}">>
+2> jsx:encode([{<<"library">>,<<"jsx">>},{<<"awesome">>,true}]).
+<<"{\"library\": \"jsx\", \"awesome\": true}">>
+3> jsx:encode([<<"a">>, <<"list">>, <<"of">>, <<"words">>]).
+<<"[\"a\",\"list\",\"of\",\"words\"]">>
+```
+
+#### to check if a binary or a term is valid json ####
+
+```erlang
+1> jsx:is_json(<<"[\"this is json\"]">>).
+true
+2> jsx:is_json("[\"this is not\"]").
+false
+3> jsx:is_term([<<"this is a term">>]).
+true
+4> jsx:is_term([this, is, not]).
+false
+```
+
+#### to minify some json ####
+
+```erlang
+1> jsx:minify(<<"{
+ \"a list\": [
+ 1,
+ 2,
+ 3
+ ]
+}">>).
+<<"{\"a list\":[1,2,3]}">>
+```
+
+#### to prettify some json ####
+
+```erlang
+1> jsx:prettify(<<"{\"a list\":[1,2,3]}">>).
+<<"{
+ \"a list\": [
+ 1,
+ 2,
+ 3
+ ]
+}">>
+```
+
+## description ##
+
+
+**jsx** is an erlang application for consuming, producing and manipulating
+[json][json]
+
+**jsx** follows the json [spec][rfc4627] as closely as possible with allowances for
+real world usage
+
+**jsx** is pragmatic. the json spec allows extensions so **jsx** extends the spec in a
+number of ways. see the section on `strict` in [options](#option) below though
+
+json has no official comments but this parser allows c/c++ style comments.
+anywhere whitespace is allowed you can insert comments (both `// ...` and `/* ... */`)
+
+some particularly irresponsible json emitters leave trailing commas at the end of
+objects or arrays. **jsx** allows a single trailing comma in input. multiple commas
+in any posistion or a preceding comma are still errors
+
+all **jsx** decoder input should be `utf8` encoded binaries. sometimes you get binaries
+that are almost but not quite valid utf8 whether due to improper escaping or poor
+encoding. **jsx** replaces invalid codepoints and poorly formed sequences with the
+unicode replacement character (`u+FFFD`) but does it's best to return something
+comprehensible
+
+json only allows keys and strings to be delimited by double quotes (`u+0022`) but
+javascript allows them to be delimited by single quotes (`u+0027`) as well. **jsx**
+follows javascript in this. strings that start with single quotes can contain double
+quotes but must end with single quotes and must escape any single quotes they contain
+
+json and **jsx** only recognize escape sequences as outlined in the json spec. it just
+ignores bad escape sequences leaving them in strings unaltered
+
+### json &lt;-> erlang mapping ###
+
+**json** | **erlang**
+--------------------------------|--------------------------------
+`number` | `integer()` and `float()`
+`string` | `binary()` and `atom()`
+`true`, `false` and `null` | `true`, `false` and `null`
+`array` | `[]` and `[JSON]`
+`object` | `#{}`, `[{}]` and `[{binary() OR atom() OR integer(), JSON}]`
+see below | `datetime()`
+
+* numbers
+
+ javascript and thus json represent all numeric values with floats. there's no
+ reason for erlang -- a language that supports arbitrarily large integers -- to
+ restrict all numbers to the ieee754 range
+
+ whenever possible, **jsx** will interpret json numbers that look like integers as
+ integers. other numbers will be converted to erlang's floating point type, which
+ is nearly but not quite iee754. negative zero is not representable in erlang (zero
+ is unsigned in erlang and `0` is equivalent to `-0`) and will be interpreted as
+ regular zero. numbers not representable are beyond the concern of this implementation,
+ and will result in parsing errors
+
+ when converting from erlang to json, floats are represented with their
+ shortest representation that will round trip without loss of precision. this
+ means that some floats may be superficially dissimilar (although
+ functionally equivalent). for example, `1.0000000000000001` will be
+ represented by `1.0`
+
+* strings
+
+ json strings must be unicode encoded binaries or erlang atoms. in practice,
+ because **jsx** only accepts `utf8` binaries all binary strings must be `utf8`.
+ in addition to being unicode json strings restrict a number of codepoints and
+ define a number of escape sequences
+
+ json string escapes of the form `\uXXXX` will be converted to their
+ equivalent codepoints during parsing. this means control characters and
+ other codepoints disallowed by the json spec may be encountered in resulting
+ strings. the utf8 restriction means the surrogates are explicitly disallowed.
+ if a string contains escaped surrogates (`u+d800` to `u+dfff`) they are
+ interpreted but only when they form valid surrogate pairs. surrogates
+ encountered otherwise are replaced with the replacement codepoint (`u+fffd`)
+
+ all erlang strings are represented by **valid** `utf8` encoded binaries. the
+ encoder will check strings for conformance. badly formed `utf8` sequences may
+ be replaced with the replacement codepoint (`u+fffd`) according to the unicode
+ spec
+
+ this implementation performs no normalization on strings beyond that
+ detailed here. be careful when comparing strings as equivalent strings
+ may have different `utf8` encodings
+
+* true, false and null
+
+ the json primitives `true`, `false` and `null` are represented by the
+ erlang atoms `true`, `false` and `null`. surprise
+
+* arrays
+
+ json arrays are represented with erlang lists of json values as described
+ in this section
+
+* objects
+
+ json objects are represented by erlang maps.
+
+* datetime
+
+ erlang datetime tuples (`{{Year, Month, Day}, {Hour, Min, Sec}}`) as returned
+ from `erlang:localtime/0` are automatically encoded as [iso8601][iso8601]
+ strings and are assumed to be UTC time. no conversion is attempted of json [iso8601][iso8601] strings in decoded json
+
+
+### incomplete input ###
+
+**jsx** can handle incomplete json texts. if the option `stream` is passed to the decoder
+or parser and if a partial json text is parsed, rather than returning a term from
+your callback handler, **jsx** returns `{incomplete, F}` where `F` is a function with
+an identical API to the anonymous fun returned from `decoder/3`, `encoder/3` or
+`parser/3`. it retains the internal state of the parser at the point where input
+was exhausted. this allows you to parse as you stream json over a socket or file
+descriptor, or to parse large json texts without needing to keep them entirely in
+memory
+
+however, it is important to recognize that **jsx** is conservative by default. **jsx** will
+not consider the parsing complete even when input is exhausted and the json text is
+unambiguously incomplete. to end parsing call the `incomplete` function with the
+argument `end_stream` (or `end_json`) like:
+
+```erlang
+1> {incomplete, F} = jsx:decode(<<"[">>, [stream]).
+{incomplete,#Fun<jsx_decoder.1.122947756>}
+2> F(end_stream). % can also be `F(end_json)`
+** exception error: bad argument
+3> {incomplete, G} = F(<<"]">>).
+{incomplete,#Fun<jsx_decoder.1.122947756>}
+4> G(end_stream). % can also be `G(end_json)`
+[]
+```
+
+
+## data types ##
+
+#### `json_term()` ####
+
+```erlang
+json_term() = [json_term()]
+ | [{binary() | atom() | integer(), json_term()}]
+ | #{} % map of any size, not just the empty map
+ | true
+ | false
+ | null
+ | integer()
+ | float()
+ | binary()
+ | atom()
+ | datetime()
+```
+
+the erlang representation of json. binaries should be `utf8` encoded, or close
+at least
+
+#### `json_text()` ####
+
+```erlang
+json_text() = binary()
+```
+
+a utf8 encoded binary containing a json string
+
+#### `event()` ####
+
+```erlang
+event() = start_object
+ | end_object
+ | start_array
+ | end_array
+ | {key, binary()}
+ | {string, binary()}
+ | {integer, integer()}
+ | {float, float()}
+ | {literal, true}
+ | {literal, false}
+ | {literal, null}
+ | end_json
+```
+
+the subset of [`token()`](#token) emitted by the decoder and encoder to handlers
+
+#### `option()` ####
+
+```erlang
+option() = dirty_strings
+ | escaped_forward_slashes
+ | escaped_strings
+ | repeat_keys
+ | stream
+ | strict
+ | {strict, [strict_option()]}
+ | return_tail
+ | uescape
+ | unescaped_jsonp
+
+strict_option() = comments
+ | trailing_commas
+ | utf8
+ | single_quotes
+ | escapes
+```
+
+**jsx** functions all take a common set of options. not all flags have meaning
+in all contexts, but they are always valid options. functions may have
+additional options beyond these. see
+[individual function documentation](#exports) for details
+
+- `dirty_strings`
+
+ json escaping is lossy; it mutates the json string and repeated application
+ can result in unwanted behaviour. if your strings are already escaped (or
+ you'd like to force invalid strings into "json" you monster) use this flag
+ to bypass escaping. this can also be used to read in **really** invalid json
+ strings. everything between unescaped quotes are passed as is to the resulting
+ string term. note that this takes precedence over any other options
+
+- `escaped_forward_slashes`
+
+ json strings are escaped according to the json spec. this means forward
+ slashes (solidus) are only escaped when this flag is present. otherwise they
+ are left unescaped. you may want to use this if you are embedding json
+ directly into a html or xml document
+
+- `escaped_strings`
+
+ by default both the encoder and decoder return strings as utf8 binaries
+ appropriate for use in erlang. escape sequences that were present in decoded
+ terms are converted into the appropriate codepoint while encoded terms are
+ unaltered. this flag escapes strings as if for output in json, removing
+ control codes and problematic codepoints and replacing them with the
+ appropriate escapes
+
+- `stream`
+
+ see [incomplete input](#incomplete-input)
+
+- `strict`
+
+ as mentioned [earlier](#description), **jsx** is pragmatic. if you're more of a
+ json purist or you're really into bdsm stricter adherence to the spec is
+ possible. the following restrictions are available
+
+ * `comments`
+
+ comments are disabled and result in a `badarg` error
+
+ * `trailing_commas`
+
+ trailing commas in an object or list result in `badarg` errors
+
+ * `utf8`
+
+ invalid codepoints and malformed unicode result in `badarg` errors
+
+ * `single_quotes`
+
+ only keys and strings delimited by double quotes (`u+0022`) are allowed. the
+ single quote (`u+0027`) results in a `badarg` error
+
+ * `escapes`
+
+ escape sequences not adhering to the json spec result in a `badarg` error
+
+ * `control_codes`
+
+ control codes in strings result in `badarg` errors
+
+ any combination of these can be passed to **jsx** by using `{strict, [strict_option()]}`.
+ `strict` is equivalent to `{strict, [comments, trailing_commas, utf8, single_quotes, escapes, control_codes]}`
+
+- `return_tail`
+
+ upon reaching the end of a valid json term in an input stream return the term and any
+ remaining bytes in the input stream as `{with_tail, term(), binary()}` where the second
+ member of the tuple is the json term and the third is any remaining bytes. note that
+ leading whitespace will be stripped from the tail
+
+- `uescape`
+
+ escape all codepoints outside the ascii range for 7 bit clean output. note
+ this escaping takes place even if no other string escaping is requested (via
+ `escaped_strings`)
+
+- `unescaped_jsonp`
+
+ javascript interpreters treat the codepoints `u+2028` and `u+2029` as
+ significant whitespace. json strings that contain either of these codepoints
+ will be parsed incorrectly by some javascript interpreters. by default,
+ these codepoints are escaped (to `\u2028` and `\u2029`, respectively) to
+ retain compatibility. this option simply removes that escaping
+
+
+## exports ##
+
+
+#### `encoder/3`, `decoder/3` & `parser/3` ####
+
+```erlang
+decoder(Module, Args, Opts) -> Fun((JSONText) -> any())
+encoder(Module, Args, Opts) -> Fun((JSONTerm) -> any())
+parser(Module, Args, Opts) -> Fun((Tokens) -> any())
+
+ Module = atom()
+ Args = any()
+ Opts = [option()]
+ JSONText = json_text()
+ JSONTerm = json_term()
+ Tokens = event() | [event()]
+```
+
+**jsx** is a json compiler with interleaved tokenizing, syntactic analysis and
+semantic analysis stages. included are two tokenizers; one that handles json
+texts (`decoder/3`) and one that handles erlang terms (`encoder/3`). there is
+also an entry point to the syntactic analysis stage for use with user-defined
+tokenizers (`parser/3`)
+
+all three functions return an anonymous function that takes the appropriate type
+of input and returns the result of performing semantic analysis, the tuple
+`{incomplete, F}` where `F` is a new anonymous function (see
+[incomplete input](#incomplete_input)) or a `badarg` error exception if
+syntactic analysis fails
+
+`Module` is the name of the callback module
+
+`Args` is any term that will be passed to `Module:init/1` prior to syntactic
+analysis to produce an initial state
+
+`Opts` are detailed [here](#option)
+
+check out [callback module documentation](#callback_exports) for details of
+the callback module interface
+
+#### `decode/1,2` ####
+
+```erlang
+decode(JSON) -> Term
+decode(JSON, Opts) -> Term
+
+ JSON = json_text()
+ Term = json_term()
+ Opts = [option() | labels | {labels, Label} | return_maps]
+ Label = binary | atom | existing_atom | attempt_atom
+ F = fun((any()) -> any())
+```
+
+`decode` parses a json text (a `utf8` encoded binary) and produces an erlang
+term
+
+the option `labels` controls how keys are converted from json to
+erlang terms. `binary` (the default behavior) does no conversion
+beyond normal escaping. `atom` converts keys to erlang atoms and
+results in a `badarg` error if the keys fall outside the range of erlang
+atoms. `existing_atom` is identical to `atom` except it will not add
+new atoms to the atom table and will result in a `badarg` error if the atom
+does not exist. `attempt_atom` will convert keys to atoms when they exist,
+and leave them as binary otherwise
+
+the option `{return_maps, false}` will return objects as proplists instead
+of maps.
+
+raises a `badarg` error exception if input is not valid json
+
+
+#### `encode/1,2` ####
+
+```erlang
+encode(Term) -> JSON
+encode(Term, Opts) -> JSON
+
+ Term = json_term()
+ JSON = json_text()
+ Opts = [option() | space | {space, N} | indent | {indent, N}]
+ N = pos_integer()
+```
+
+`encode` converts an erlang term into json text (a `utf8` encoded binary)
+
+the option `{space, N}` inserts `N` spaces after every comma and colon in your
+json output. `space` is an alias for `{space, 1}`. the default is `{space, 0}`
+
+the option `{indent, N}` inserts a newline and `N` spaces for each level of
+indentation in your json output. note that this overrides spaces inserted after
+a comma. `indent` is an alias for `{indent, 1}`. the default is `{indent, 0}`
+
+raises a `badarg` error exception if input is not a valid
+[erlang representation of json](#json---erlang-mapping)
+
+
+#### `format/1,2` ####
+
+```erlang
+format(JSON) -> JSON
+format(JSON, Opts) -> JSON
+
+ JSON = json_text()
+ Opts = [option() | space | {space, N} | indent | {indent, N} | {newline, LF}]
+ N = pos_integer()
+ LF = binary()
+```
+
+`format` parses a json text (a `utf8` encoded binary) and produces a new json
+text according to the format rules specified by `Opts`
+
+the option `{space, N}` inserts `N` spaces after every comma and colon in your
+json output. `space` is an alias for `{space, 1}`. the default is `{space, 0}`
+
+the option `{indent, N}` inserts a newline and `N` spaces for each level of
+indentation in your json output. note that this overrides spaces inserted after
+a comma. `indent` is an alias for `{indent, 1}`. the default is `{indent, 0}`
+
+the option `{newline, LF}` defines a custom newline symbol(s).
+the default is `{newline, <<$\n>>}`
+
+raises a `badarg` error exception if input is not valid json
+
+
+#### `minify/1` ####
+
+```erlang
+minify(JSON) -> JSON
+
+ JSON = json_text()
+```
+
+`minify` parses a json text (a `utf8` encoded binary) and produces a new json
+text stripped of whitespace
+
+raises a `badarg` error exception if input is not valid json
+
+
+#### `prettify/1` ####
+
+```erlang
+prettify(JSON) -> JSON
+
+ JSON = json_text()
+```
+
+`prettify` parses a json text (a `utf8` encoded binary) and produces a new json
+text equivalent to `format(JSON, [{space, 1}, {indent, 2}])`
+
+raises a `badarg` error exception if input is not valid json
+
+
+#### `is_json/1,2` ####
+
+```erlang
+is_json(MaybeJSON) -> true | false
+is_json(MaybeJSON, Opts) -> true | false
+
+ MaybeJSON = any()
+ Opts = options()
+```
+
+returns true if input is a valid json text, false if not
+
+what exactly constitutes valid json may be [altered](#option)
+
+
+#### `is_term/1,2` ####
+
+```erlang
+is_term(MaybeJSON) -> true | false
+is_term(MaybeJSON, Opts) -> true | false
+
+ MaybeJSON = any()
+ Opts = options()
+```
+
+returns true if input is a valid erlang representation of json, false if not
+
+what exactly constitutes valid json may be altered via [options](#option)
+
+## callback exports ##
+
+the following functions should be exported from a **jsx** callback module
+
+#### `Module:init/1` ####
+
+```erlang
+Module:init(Args) -> InitialState
+
+ Args = any()
+ InitialState = any()
+```
+
+whenever any of `encoder/3`, `decoder/3` or `parser/3` are called, this function
+is called with the `Args` argument provided in the calling function to obtain
+`InitialState`
+
+#### `Module:handle_event/2` ####
+
+```erlang
+Module:handle_event(Event, State) -> NewState
+
+ Event = [event()]
+ State = any()
+ NewState = any()
+```
+
+semantic analysis is performed by repeatedly calling `handle_event/2` with a
+stream of events emitted by the tokenizer and the current state. the new state
+returned is used as the input to the next call to `handle_event/2`. the
+following events must be handled:
+
+- `start_object`
+
+ the start of a json object
+
+- '{key, binary()}'
+
+ the key of an entry in a json object
+
+- `end_object`
+
+ the end of a json object
+
+- `start_array`
+
+ the start of a json array
+
+- `end_array`
+
+ the end of a json array
+
+- `{string, binary()}`
+
+ a json string. it will usually be a `utf8` encoded binary. see the
+ [options](#option) for possible exceptions. note that keys are also
+ json strings
+
+- `{integer, integer()}`
+
+ an erlang integer (bignum)
+
+- `{float, float()}`
+
+ an erlang float
+
+- `{literal, true}`
+
+ the atom `true`
+
+- `{literal, false}`
+
+ the atom `false`
+
+- `{literal, null}`
+
+ the atom `null`
+
+- `end_json`
+
+ this event is emitted when syntactic analysis is completed. you should
+ do any cleanup and return the result of your semantic analysis
+
+
+## acknowledgements ##
+
+jsx wouldn't be what it is without the contributions of [Paul J. Davis](https://github.com/davisp), [Lloyd Hilaiel](https://github.com/lloyd), [John Engelhart](https://github.com/johnezang), [Bob Ippolito](https://github.com/etrepum), [Brujo Benavides](https://github.com/elbrujohalcon), [Alex Kropivny](https://github.com/amtal), [Steve Strong](https://github.com/srstrong), [Michael Truog](https://github.com/okeuday), [Devin Torres](https://github.com/devinus), [fogfish](https://github.com/fogfish), [emptytea](https://github.com/emptytea), [John Daily](https://github.com/macintux), [Ola Bäckström](https://github.com/olabackstrom), [Joseph Crowe](https://github.com/JosephCrowe), [Patrick Gombert](https://github.com/patrickgombert), [Eshengazin S. Kuat](https://github.com/eskuat), [Max Lapshin](https://github.com/maxlapshin), [Bikram Chatterjee](https://github.com/c-bik), [Michael Uvarov](https://github.com/arcusfelis), [Led](https://github.com/Ledest) and [tvv](https://github.com/tvv)
+
+[json]: http://json.org
+[yajl]: http://lloyd.github.com/yajl
+[MIT]: http://www.opensource.org/licenses/mit-license.html
+[rebar3]: https://rebar3.org
+[meck]: https://github.com/eproxus/meck
+[rfc4627]: http://tools.ietf.org/html/rfc4627
+[travis]: https://travis-ci.org/
+[jsxn]: https://github.com/talentdeficit/jsxn
+[iso8601]: http://www.iso.org/iso/iso8601
diff --git a/server/_build/default/lib/jsx/ebin/jsx.app b/server/_build/default/lib/jsx/ebin/jsx.app
new file mode 100644
index 0000000..24927b2
--- /dev/null
+++ b/server/_build/default/lib/jsx/ebin/jsx.app
@@ -0,0 +1,10 @@
+{application,jsx,
+ [{description,"a streaming, evented json parsing toolkit"},
+ {vsn,"3.1.0"},
+ {modules,[jsx,jsx_config,jsx_consult,jsx_decoder,jsx_encoder,
+ jsx_parser,jsx_to_json,jsx_to_term,jsx_verify]},
+ {registered,[]},
+ {applications,[kernel,stdlib]},
+ {env,[]},
+ {licenses,["MIT"]},
+ {links,[{"Github","https://github.com/talentdeficit/jsx"}]}]}.
diff --git a/server/_build/default/lib/jsx/ebin/jsx.beam b/server/_build/default/lib/jsx/ebin/jsx.beam
new file mode 100644
index 0000000..adefbd0
--- /dev/null
+++ b/server/_build/default/lib/jsx/ebin/jsx.beam
Binary files differ
diff --git a/server/_build/default/lib/jsx/ebin/jsx_config.beam b/server/_build/default/lib/jsx/ebin/jsx_config.beam
new file mode 100644
index 0000000..b5b2c50
--- /dev/null
+++ b/server/_build/default/lib/jsx/ebin/jsx_config.beam
Binary files differ
diff --git a/server/_build/default/lib/jsx/ebin/jsx_consult.beam b/server/_build/default/lib/jsx/ebin/jsx_consult.beam
new file mode 100644
index 0000000..24d4164
--- /dev/null
+++ b/server/_build/default/lib/jsx/ebin/jsx_consult.beam
Binary files differ
diff --git a/server/_build/default/lib/jsx/ebin/jsx_decoder.beam b/server/_build/default/lib/jsx/ebin/jsx_decoder.beam
new file mode 100644
index 0000000..6df0019
--- /dev/null
+++ b/server/_build/default/lib/jsx/ebin/jsx_decoder.beam
Binary files differ
diff --git a/server/_build/default/lib/jsx/ebin/jsx_encoder.beam b/server/_build/default/lib/jsx/ebin/jsx_encoder.beam
new file mode 100644
index 0000000..65676e2
--- /dev/null
+++ b/server/_build/default/lib/jsx/ebin/jsx_encoder.beam
Binary files differ
diff --git a/server/_build/default/lib/jsx/ebin/jsx_parser.beam b/server/_build/default/lib/jsx/ebin/jsx_parser.beam
new file mode 100644
index 0000000..ee47da6
--- /dev/null
+++ b/server/_build/default/lib/jsx/ebin/jsx_parser.beam
Binary files differ
diff --git a/server/_build/default/lib/jsx/ebin/jsx_to_json.beam b/server/_build/default/lib/jsx/ebin/jsx_to_json.beam
new file mode 100644
index 0000000..ca9781a
--- /dev/null
+++ b/server/_build/default/lib/jsx/ebin/jsx_to_json.beam
Binary files differ
diff --git a/server/_build/default/lib/jsx/ebin/jsx_to_term.beam b/server/_build/default/lib/jsx/ebin/jsx_to_term.beam
new file mode 100644
index 0000000..25f3ef4
--- /dev/null
+++ b/server/_build/default/lib/jsx/ebin/jsx_to_term.beam
Binary files differ
diff --git a/server/_build/default/lib/jsx/ebin/jsx_verify.beam b/server/_build/default/lib/jsx/ebin/jsx_verify.beam
new file mode 100644
index 0000000..9da1cf2
--- /dev/null
+++ b/server/_build/default/lib/jsx/ebin/jsx_verify.beam
Binary files differ
diff --git a/server/_build/default/lib/jsx/hex_metadata.config b/server/_build/default/lib/jsx/hex_metadata.config
new file mode 100644
index 0000000..63f6e66
--- /dev/null
+++ b/server/_build/default/lib/jsx/hex_metadata.config
@@ -0,0 +1,15 @@
+{<<"app">>,<<"jsx">>}.
+{<<"build_tools">>,[<<"rebar3">>]}.
+{<<"description">>,<<"a streaming, evented json parsing toolkit">>}.
+{<<"files">>,
+ [<<"LICENSE">>,<<"README.md">>,<<"rebar.config">>,<<"rebar.lock">>,
+ <<"src/jsx.app.src">>,<<"src/jsx.erl">>,<<"src/jsx_config.erl">>,
+ <<"src/jsx_config.hrl">>,<<"src/jsx_consult.erl">>,
+ <<"src/jsx_decoder.erl">>,<<"src/jsx_encoder.erl">>,
+ <<"src/jsx_parser.erl">>,<<"src/jsx_to_json.erl">>,
+ <<"src/jsx_to_term.erl">>,<<"src/jsx_verify.erl">>]}.
+{<<"licenses">>,[<<"MIT">>]}.
+{<<"links">>,[{<<"Github">>,<<"https://github.com/talentdeficit/jsx">>}]}.
+{<<"name">>,<<"jsx">>}.
+{<<"requirements">>,[]}.
+{<<"version">>,<<"3.1.0">>}.
diff --git a/server/_build/default/lib/jsx/rebar.config b/server/_build/default/lib/jsx/rebar.config
new file mode 100644
index 0000000..1c71a9c
--- /dev/null
+++ b/server/_build/default/lib/jsx/rebar.config
@@ -0,0 +1,17 @@
+{edoc_opts, [{preprocess, true}]}.
+{erl_opts, [debug_info]}.
+{dialyzer, [
+ {warnings, [
+ unknown,
+ unmatched_returns,
+ error_handling,
+ underspecs
+ ]}
+]}.
+{profiles, [
+ {test, [
+ {dialyzer, [
+ {plt_extra_apps, [eunit]}
+ ]}
+ ]}
+]}.
diff --git a/server/_build/default/lib/jsx/rebar.lock b/server/_build/default/lib/jsx/rebar.lock
new file mode 100644
index 0000000..57afcca
--- /dev/null
+++ b/server/_build/default/lib/jsx/rebar.lock
@@ -0,0 +1 @@
+[].
diff --git a/server/_build/default/lib/jsx/src/jsx.app.src b/server/_build/default/lib/jsx/src/jsx.app.src
new file mode 100644
index 0000000..0aff494
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx.app.src
@@ -0,0 +1,10 @@
+{application,jsx,
+ [{description,"a streaming, evented json parsing toolkit"},
+ {vsn,"3.1.0"},
+ {modules,[jsx,jsx_encoder,jsx_decoder,jsx_parser,jsx_to_json,
+ jsx_to_term,jsx_config,jsx_verify]},
+ {registered,[]},
+ {applications,[kernel,stdlib]},
+ {env,[]},
+ {licenses,["MIT"]},
+ {links,[{"Github","https://github.com/talentdeficit/jsx"}]}]}.
diff --git a/server/_build/default/lib/jsx/src/jsx.erl b/server/_build/default/lib/jsx/src/jsx.erl
new file mode 100644
index 0000000..d02d33b
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx.erl
@@ -0,0 +1,506 @@
+%% The MIT License
+
+%% Copyright (c) 2010-2013 alisdair sullivan <alisdairsullivan@yahoo.ca>
+
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+
+
+-module(jsx).
+
+-export([encode/1, encode/2, decode/1, decode/2]).
+-export([is_json/1, is_json/2, is_term/1, is_term/2]).
+-export([format/1, format/2, minify/1, prettify/1]).
+-export([consult/1, consult/2]).
+-export([encoder/3, decoder/3, parser/3]).
+-export([resume/3]).
+
+-export_type([json_term/0, json_text/0, token/0]).
+-export_type([encoder/0, decoder/0, parser/0, internal_state/0]).
+-export_type([config/0]).
+
+
+-ifdef(TEST).
+%% data and helper functions for tests
+-export([test_cases/0, special_test_cases/0]).
+-export([init/1, handle_event/2]).
+-endif.
+
+-type json_term() :: [{binary() | atom(), json_term()}] | [{},...]
+ | [json_term()] | []
+ | {with_tail, json_term(), binary()}
+ | #{ binary() | atom() => json_term() }
+ | true | false | null
+ | integer() | float()
+ | binary() | atom()
+ | calendar:datetime().
+
+-type json_text() :: binary().
+
+-type config() :: jsx_config:config().
+
+-spec encode(Source::json_term()) -> json_text() | {incomplete, encoder()}.
+
+encode(Source) -> encode(Source, []).
+
+-spec encode(Source::json_term(), Config::jsx_config:options()) -> json_text() | {incomplete, encoder()}.
+
+encode(Source, Config) -> jsx_to_json:to_json(Source, Config).
+
+
+-spec decode(Source::json_text()) -> json_term() | {incomplete, decoder()}.
+
+decode(Source) -> decode(Source, []).
+
+-spec decode(Source::json_text(), Config::jsx_config:options()) -> json_term() | {incomplete, decoder()}.
+
+decode(Source, Config) -> jsx_to_term:to_term(Source, Config).
+
+
+-spec format(Source::json_text()) -> json_text().
+
+format(Source) -> format(Source, []).
+
+-spec format(Source::json_text(), Config::jsx_config:options()) -> json_text().
+
+format(Source, Config) -> jsx_to_json:format(Source, Config).
+
+
+-spec minify(Source::json_text()) -> json_text().
+
+minify(Source) -> format(Source, []).
+
+
+-spec prettify(Source::json_text()) -> json_text().
+
+prettify(Source) -> format(Source, [space, {indent, 2}]).
+
+
+-spec is_json(Source::binary()) -> boolean() | {incomplete, decoder()}.
+
+is_json(Source) -> is_json(Source, []).
+
+-spec is_json(Source::binary(), Config::jsx_config:options()) -> boolean() | {incomplete, decoder()}.
+
+is_json(Source, Config) -> jsx_verify:is_json(Source, Config).
+
+
+-spec is_term(Source::json_term() | end_stream | end_json) -> boolean() | {incomplete, encoder()}.
+
+is_term(Source) -> is_term(Source, []).
+
+-spec is_term(Source::json_term() | end_stream | end_json,
+ Config::jsx_config:options()) -> boolean() | {incomplete, encoder()}.
+
+is_term(Source, Config) -> jsx_verify:is_term(Source, Config).
+
+
+-spec consult(File::file:name_all()) -> list(jsx_consult:json_value()).
+
+consult(File) -> consult(File, []).
+
+-spec consult(File::file:name_all(), Config::jsx_consult:config()) -> list(jsx_consult:json_value()).
+
+consult(File, Config) -> jsx_consult:consult(File, Config).
+
+
+-type decoder() :: fun((json_text() | end_stream | end_json) -> any()).
+
+-spec decoder(Handler::module(), State::any(), Config::jsx_config:options()) -> decoder().
+
+decoder(Handler, State, Config) -> jsx_decoder:decoder(Handler, State, Config).
+
+
+-type encoder() :: fun((json_term() | end_stream | end_json) -> any()).
+
+-spec encoder(Handler::module(), State::any(), Config::jsx_config:options()) -> encoder().
+
+encoder(Handler, State, Config) -> jsx_encoder:encoder(Handler, State, Config).
+
+
+-type token() :: [token()]
+ | start_object
+ | end_object
+ | start_array
+ | end_array
+ | {key, binary()}
+ | {string, binary()}
+ | binary()
+ | {number, integer() | float()}
+ | {integer, integer()}
+ | {float, float()}
+ | integer()
+ | float()
+ | {literal, true}
+ | {literal, false}
+ | {literal, null}
+ | true
+ | false
+ | null
+ | end_json.
+
+
+-type parser() :: fun((token() | end_stream) -> any()).
+
+-spec parser(Handler::module(), State::any(), Config::jsx_config:options()) -> parser().
+
+parser(Handler, State, Config) -> jsx_parser:parser(Handler, State, Config).
+
+-opaque internal_state() :: tuple().
+
+-spec resume(Term::json_text() | token(), InternalState::internal_state(),
+ Config::jsx_config:options()) -> jsx:decoder() | {incomplete, jsx:decoder()}.
+
+resume(Term, {decoder, State, Handler, Acc, Stack}, Config) ->
+ jsx_decoder:resume(Term, State, Handler, Acc, Stack, jsx_config:parse_config(Config));
+resume(Term, {parser, State, Handler, Stack}, Config) ->
+ jsx_parser:resume(Term, State, Handler, Stack, jsx_config:parse_config(Config)).
+
+-ifdef(TEST).
+
+-include_lib("eunit/include/eunit.hrl").
+
+
+%% test handler
+init([]) -> [].
+
+handle_event(end_json, State) -> lists:reverse([end_json] ++ State);
+handle_event(Event, State) -> [Event] ++ State.
+
+
+test_cases() ->
+ empty_array()
+ ++ nested_array()
+ ++ empty_object()
+ ++ nested_object()
+ ++ strings()
+ ++ literals()
+ ++ integers()
+ ++ floats()
+ ++ compound_object().
+
+%% segregate these so we can skip them in `jsx_to_term`
+special_test_cases() -> special_objects() ++ special_array().
+
+
+empty_array() -> [{"[]", <<"[]">>, [], [start_array, end_array]}].
+
+
+nested_array() ->
+ [{
+ "[[[]]]",
+ <<"[[[]]]">>,
+ [[[]]],
+ [start_array, start_array, start_array, end_array, end_array, end_array]
+ }].
+
+
+empty_object() -> [{"{}", <<"{}">>, [{}], [start_object, end_object]}].
+
+
+nested_object() ->
+ [{
+ "{\"key\":{\"key\":{}}}",
+ <<"{\"key\":{\"key\":{}}}">>,
+ [{<<"key">>, [{<<"key">>, [{}]}]}],
+ [
+ start_object,
+ {key, <<"key">>},
+ start_object,
+ {key, <<"key">>},
+ start_object,
+ end_object,
+ end_object,
+ end_object
+ ]
+ }].
+
+
+naked_strings() ->
+ Raw = [
+ "",
+ "hello world"
+ ],
+ [
+ {
+ String,
+ <<"\"", (list_to_binary(String))/binary, "\"">>,
+ list_to_binary(String),
+ [{string, list_to_binary(String)}]
+ }
+ || String <- Raw
+ ].
+
+
+strings() ->
+ naked_strings()
+ ++ [ wrap_with_array(Test) || Test <- naked_strings() ]
+ ++ [ wrap_with_object(Test) || Test <- naked_strings() ].
+
+
+naked_integers() ->
+ Raw = [
+ 1, 2, 3,
+ 127, 128, 129,
+ 255, 256, 257,
+ 65534, 65535, 65536,
+ 18446744073709551616,
+ 18446744073709551617
+ ],
+ [
+ {
+ integer_to_list(X),
+ list_to_binary(integer_to_list(X)),
+ X,
+ [{integer, X}]
+ }
+ || X <- Raw ++ [ -1 * Y || Y <- Raw ] ++ [0]
+ ].
+
+
+integers() ->
+ naked_integers()
+ ++ [ wrap_with_array(Test) || Test <- naked_integers() ]
+ ++ [ wrap_with_object(Test) || Test <- naked_integers() ].
+
+
+naked_floats() ->
+ Raw = [
+ 0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9,
+ 1.0, 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9,
+ 1234567890.0987654321,
+ 0.0e0,
+ 1234567890.0987654321e16,
+ 0.1e0, 0.1e1, 0.1e2, 0.1e4, 0.1e8, 0.1e16, 0.1e308,
+ 1.0e0, 1.0e1, 1.0e2, 1.0e4, 1.0e8, 1.0e16, 1.0e308,
+ 2.2250738585072014e-308, %% min normalized float
+ 1.7976931348623157e308, %% max normalized float
+ 5.0e-324, %% min denormalized float
+ 2.225073858507201e-308 %% max denormalized float
+ ],
+ [
+ {
+ sane_float_to_list(X),
+ list_to_binary(sane_float_to_list(X)),
+ X,
+ [{float, X}]
+ }
+ || X <- Raw ++ [ -1 * Y || Y <- Raw ]
+ ].
+
+
+floats() ->
+ naked_floats()
+ ++ [ wrap_with_array(Test) || Test <- naked_floats() ]
+ ++ [ wrap_with_object(Test) || Test <- naked_floats() ].
+
+
+naked_literals() ->
+ [
+ {
+ atom_to_list(Literal),
+ atom_to_binary(Literal, unicode),
+ Literal,
+ [{literal, Literal}]
+ }
+ || Literal <- [true, false, null]
+ ].
+
+
+literals() ->
+ naked_literals()
+ ++ [ wrap_with_array(Test) || Test <- naked_literals() ]
+ ++ [ wrap_with_object(Test) || Test <- naked_literals() ].
+
+
+compound_object() ->
+ [{
+ "[{\"alpha\":[1,2,3],\"beta\":{\"alpha\":[1.0,2.0,3.0],\"beta\":[true,false]}},[{}]]",
+ <<"[{\"alpha\":[1,2,3],\"beta\":{\"alpha\":[1.0,2.0,3.0],\"beta\":[true,false]}},[{}]]">>,
+ [[{<<"alpha">>, [1, 2, 3]}, {<<"beta">>, [{<<"alpha">>, [1.0, 2.0, 3.0]}, {<<"beta">>, [true, false]}]}], [[{}]]],
+ [
+ start_array,
+ start_object,
+ {key, <<"alpha">>},
+ start_array,
+ {integer, 1},
+ {integer, 2},
+ {integer, 3},
+ end_array,
+ {key, <<"beta">>},
+ start_object,
+ {key, <<"alpha">>},
+ start_array,
+ {float, 1.0},
+ {float, 2.0},
+ {float, 3.0},
+ end_array,
+ {key, <<"beta">>},
+ start_array,
+ {literal, true},
+ {literal, false},
+ end_array,
+ end_object,
+ end_object,
+ start_array,
+ start_object,
+ end_object,
+ end_array,
+ end_array
+ ]
+ }].
+
+
+special_objects() ->
+ [
+ {
+ "[{key, atom}]",
+ <<"{\"key\":\"atom\"}">>,
+ [{key, atom}],
+ [start_object, {key, <<"key">>}, {string, <<"atom">>}, end_object]
+ },
+ {
+ "[{1, true}]",
+ <<"{\"1\":true}">>,
+ [{1, true}],
+ [start_object, {key, <<"1">>}, {literal, true}, end_object]
+ }
+ ].
+
+
+special_array() ->
+ [
+ {
+ "[foo, bar]",
+ <<"[\"foo\",\"bar\"]">>,
+ [foo, bar],
+ [start_array, {string, <<"foo">>}, {string, <<"bar">>}, end_array]
+ }
+ ].
+
+
+wrap_with_array({Title, JSON, Term, Events}) ->
+ {
+ "[" ++ Title ++ "]",
+ <<"[", JSON/binary, "]">>,
+ [Term],
+ [start_array] ++ Events ++ [end_array]
+ }.
+
+
+wrap_with_object({Title, JSON, Term, Events}) ->
+ {
+ "{\"key\":" ++ Title ++ "}",
+ <<"{\"key\":", JSON/binary, "}">>,
+ [{<<"key">>, Term}],
+ [start_object, {key, <<"key">>}] ++ Events ++ [end_object]
+ }.
+
+
+sane_float_to_list(X) ->
+ [Output] = io_lib:format("~p", [X]),
+ Output.
+
+
+incremental_decode(JSON) ->
+ Final = lists:foldl(
+ fun(Byte, Decoder) -> {incomplete, F} = Decoder(Byte), F end,
+ decoder(jsx, [], [stream]),
+ json_to_bytes(JSON)
+ ),
+ Final(end_stream).
+
+
+incremental_parse(Events) ->
+ Final = lists:foldl(
+ fun(Event, Parser) -> {incomplete, F} = Parser(Event), F end,
+ parser(?MODULE, [], [stream]),
+ lists:map(fun(X) -> [X] end, Events)
+ ),
+ Final(end_stream).
+
+
+%% used to convert a json text into a list of codepoints to be incrementally
+%% parsed
+json_to_bytes(JSON) -> json_to_bytes(JSON, []).
+
+json_to_bytes(<<>>, Acc) -> [<<>>] ++ lists:reverse(Acc);
+json_to_bytes(<<X, Rest/binary>>, Acc) -> json_to_bytes(Rest, [<<X>>] ++ Acc).
+
+
+%% actual tests!
+decode_test_() ->
+ Data = test_cases(),
+ [{Title, ?_assertEqual(Events ++ [end_json], (decoder(?MODULE, [], []))(JSON))}
+ || {Title, JSON, _, Events} <- Data
+ ] ++
+ [{Title ++ " (incremental)", ?_assertEqual(Events ++ [end_json], incremental_decode(JSON))}
+ || {Title, JSON, _, Events} <- Data
+ ].
+
+
+parse_test_() ->
+ Data = test_cases(),
+ [{Title, ?_assertEqual(Events ++ [end_json], (parser(?MODULE, [], []))(Events ++ [end_json]))}
+ || {Title, _, _, Events} <- Data
+ ] ++
+ [{Title ++ " (incremental)", ?_assertEqual(Events ++ [end_json], incremental_parse(Events))}
+ || {Title, _, _, Events} <- Data
+ ].
+
+
+encode_test_() ->
+ Data = test_cases(),
+ [
+ {
+ Title, ?_assertEqual(
+ Events ++ [end_json],
+ (jsx:encoder(jsx, [], []))(Term)
+ )
+ } || {Title, _, Term, Events} <- Data
+ ].
+
+end_stream_test_() ->
+ Tokens = [start_object, end_object, end_json],
+ [
+ {"encoder end_stream", ?_assertEqual(
+ Tokens,
+ begin
+ {incomplete, F} = (jsx:parser(jsx, [], [stream]))([start_object, end_object]),
+ F(end_stream)
+ end
+ )},
+ {"encoder end_json", ?_assertEqual(
+ Tokens,
+ begin
+ {incomplete, F} = (jsx:parser(jsx, [], [stream]))([start_object, end_object]),
+ F(end_json)
+ end
+ )},
+ {"decoder end_stream", ?_assertEqual(
+ Tokens,
+ begin {incomplete, F} = (jsx:decoder(jsx, [], [stream]))(<<"{}">>), F(end_stream) end
+ )},
+ {"decoder end_json", ?_assertEqual(
+ Tokens,
+ begin {incomplete, F} = (jsx:decoder(jsx, [], [stream]))(<<"{}">>), F(end_json) end
+ )}
+ ].
+
+
+-endif.
diff --git a/server/_build/default/lib/jsx/src/jsx_config.erl b/server/_build/default/lib/jsx/src/jsx_config.erl
new file mode 100644
index 0000000..ba1d872
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx_config.erl
@@ -0,0 +1,393 @@
+%% The MIT License
+
+%% Copyright (c) 2010-2013 alisdair sullivan <alisdairsullivan@yahoo.ca>
+
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+
+
+-module(jsx_config).
+
+-export([parse_config/1]).
+-export([config_to_list/1]).
+-export([extract_config/1, valid_flags/0]).
+
+-ifdef(TEST).
+-export([fake_error_handler/3]).
+-endif.
+
+-include("jsx_config.hrl").
+
+-type handler_type(Handler) ::
+ fun((jsx:json_text() | end_stream |
+ jsx:json_term(),
+ {decoder, any(), module(), null | list(), list()} |
+ {parser, any(), module(), list()} |
+ {encoder, any(), module()},
+ list({pre_encode, fun((any()) -> any())} |
+ {error_handler, Handler} |
+ {incomplete_handler, Handler} |
+ atom())) -> any()).
+-type handler() :: handler_type(handler()).
+-export_type([handler/0]).
+
+-type config() :: #config{}.
+-export_type([config/0]).
+
+-type option() :: valid_flag()
+ | {valid_flag(), boolean()}
+ | {strict, [strict_option()]}
+ | {error_handler, fun((any(), any(), any()) -> ok)}
+ | {incomplete_handler, fun((any(), any(), any()) -> ok)}
+ | {return_maps, boolean()}
+ | {labels, label_option()}
+ | {space, non_neg_integer()}
+ | {indent, non_neg_integer()}
+ | {depth, non_neg_integer()}
+ | {newline, binary()}
+ | legacy_option()
+ | {legacy_option(), boolean()}.
+-type legacy_option() :: strict_comments
+ | strict_commas
+ | strict_utf8
+ | strict_single_quotes
+ | strict_escapes
+ | strict_control_codes.
+
+-type options() :: [option()].
+-export_type([options/0]).
+
+-type strict_option() :: comments
+ | trailing_commas
+ | utf8
+ | single_quotes
+ | escapes
+ | control_codes.
+-type label_option() :: binary
+ | atom
+ | existing_atom
+ | attempt_atom.
+
+-type valid_flag() :: escaped_forward_slashes
+ | escaped_strings
+ | unescaped_jsonp
+ | dirty_strings
+ | multi_term
+ | return_tail
+ | repeat_keys
+ | strict
+ | stream
+ | uescape
+ | error_handler
+ | incomplete_handler.
+
+%% parsing of jsx config
+-spec parse_config(Config::options()) -> config().
+
+parse_config(Config) -> parse_config(Config, #config{}).
+
+parse_config([], Config) -> Config;
+parse_config([escaped_forward_slashes|Rest], Config) ->
+ parse_config(Rest, Config#config{escaped_forward_slashes=true});
+parse_config([escaped_strings|Rest], Config) ->
+ parse_config(Rest, Config#config{escaped_strings=true});
+parse_config([unescaped_jsonp|Rest], Config) ->
+ parse_config(Rest, Config#config{unescaped_jsonp=true});
+parse_config([dirty_strings|Rest], Config) ->
+ parse_config(Rest, Config#config{dirty_strings=true});
+parse_config([multi_term|Rest], Config) ->
+ parse_config(Rest, Config#config{multi_term=true});
+parse_config([return_tail|Rest], Config) ->
+ parse_config(Rest, Config#config{return_tail=true});
+%% retained for backwards compat, now does nothing however
+parse_config([repeat_keys|Rest], Config) ->
+ parse_config(Rest, Config);
+parse_config([uescape|Rest], Config) ->
+ parse_config(Rest, Config#config{uescape=true});
+parse_config([strict|Rest], Config) ->
+ parse_config(Rest, Config#config{
+ strict_comments=true,
+ strict_commas=true,
+ strict_utf8=true,
+ strict_single_quotes=true,
+ strict_escapes=true,
+ strict_control_codes=true
+ });
+parse_config([{strict, Strict}|Rest], Config) ->
+ parse_strict(Strict, Rest, Config);
+parse_config([stream|Rest], Config) ->
+ parse_config(Rest, Config#config{stream=true});
+parse_config([{error_handler, ErrorHandler}|Rest] = Options, Config) when is_function(ErrorHandler, 3) ->
+ case Config#config.error_handler of
+ false -> parse_config(Rest, Config#config{error_handler=ErrorHandler})
+ ; _ -> erlang:error(badarg, [Options, Config])
+ end;
+parse_config([{incomplete_handler, IncompleteHandler}|Rest] = Options, Config) when is_function(IncompleteHandler, 3) ->
+ case Config#config.incomplete_handler of
+ false -> parse_config(Rest, Config#config{incomplete_handler=IncompleteHandler})
+ ; _ -> erlang:error(badarg, [Options, Config])
+ end;
+parse_config(_Options, _Config) -> erlang:error(badarg).
+
+
+parse_strict([], Rest, Config) -> parse_config(Rest, Config);
+parse_strict([comments|Strict], Rest, Config) ->
+ parse_strict(Strict, Rest, Config#config{strict_comments=true});
+parse_strict([trailing_commas|Strict], Rest, Config) ->
+ parse_strict(Strict, Rest, Config#config{strict_commas=true});
+parse_strict([utf8|Strict], Rest, Config) ->
+ parse_strict(Strict, Rest, Config#config{strict_utf8=true});
+parse_strict([single_quotes|Strict], Rest, Config) ->
+ parse_strict(Strict, Rest, Config#config{strict_single_quotes=true});
+parse_strict([escapes|Strict], Rest, Config) ->
+ parse_strict(Strict, Rest, Config#config{strict_escapes=true});
+parse_strict([control_codes|Strict], Rest, Config) ->
+ parse_strict(Strict, Rest, Config#config{strict_control_codes=true});
+parse_strict(_Strict, _Rest, _Config) ->
+ erlang:error(badarg).
+
+
+
+-spec config_to_list(Config::config()) -> options().
+
+config_to_list(Config) ->
+ reduce_config(lists:map(
+ fun ({error_handler, F}) -> {error_handler, F};
+ ({incomplete_handler, F}) -> {incomplete_handler, F};
+ ({Key, true}) -> Key
+ end,
+ lists:filter(
+ fun({_, false}) -> false; (_) -> true end,
+ lists:zip(record_info(fields, config), tl(tuple_to_list(Config)))
+ )
+ )).
+
+
+reduce_config(Input) -> reduce_config(Input, [], []).
+
+reduce_config([], Output, Strict) ->
+ case length(Strict) of
+ 0 -> lists:reverse(Output);
+ 5 -> lists:reverse(Output) ++ [strict];
+ _ -> lists:reverse(Output) ++ [{strict, lists:reverse(Strict)}]
+ end;
+reduce_config([strict_comments|Input], Output, Strict) ->
+ reduce_config(Input, Output, [comments] ++ Strict);
+reduce_config([strict_utf8|Input], Output, Strict) ->
+ reduce_config(Input, Output, [utf8] ++ Strict);
+reduce_config([strict_single_quotes|Input], Output, Strict) ->
+ reduce_config(Input, Output, [single_quotes] ++ Strict);
+reduce_config([strict_escapes|Input], Output, Strict) ->
+ reduce_config(Input, Output, [escapes] ++ Strict);
+reduce_config([strict_control_codes|Input], Output, Strict) ->
+ reduce_config(Input, Output, [control_codes] ++ Strict);
+reduce_config([Else|Input], Output, Strict) ->
+ reduce_config(Input, [Else] ++ Output, Strict).
+
+
+-spec valid_flags() -> [valid_flag(), ...].
+
+valid_flags() ->
+ [
+ escaped_forward_slashes,
+ escaped_strings,
+ unescaped_jsonp,
+ dirty_strings,
+ multi_term,
+ return_tail,
+ repeat_keys,
+ strict,
+ stream,
+ uescape,
+ error_handler,
+ incomplete_handler
+ ].
+
+
+-spec extract_config(Config::options()) -> options().
+
+extract_config(Config) ->
+ extract_parser_config(Config, []).
+
+extract_parser_config([], Acc) -> Acc;
+extract_parser_config([{K,V}|Rest], Acc) ->
+ case lists:member(K, valid_flags()) of
+ true -> extract_parser_config(Rest, [{K,V}] ++ Acc)
+ ; false -> extract_parser_config(Rest, Acc)
+ end;
+extract_parser_config([K|Rest], Acc) ->
+ case lists:member(K, valid_flags()) of
+ true -> extract_parser_config(Rest, [K] ++ Acc)
+ ; false -> extract_parser_config(Rest, Acc)
+ end.
+
+
+%% eunit tests
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+
+config_test_() ->
+ [
+ {"all flags",
+ ?_assertEqual(
+ #config{escaped_forward_slashes = true,
+ escaped_strings = true,
+ unescaped_jsonp = true,
+ dirty_strings = true,
+ multi_term = true,
+ return_tail = true,
+ strict_comments = true,
+ strict_commas = true,
+ strict_utf8 = true,
+ strict_single_quotes = true,
+ strict_escapes = true,
+ strict_control_codes = true,
+ stream = true,
+ uescape = true
+ },
+ parse_config([dirty_strings,
+ escaped_forward_slashes,
+ escaped_strings,
+ unescaped_jsonp,
+ multi_term,
+ return_tail,
+ repeat_keys,
+ strict,
+ stream,
+ uescape
+ ])
+ )
+ },
+ {"strict flag",
+ ?_assertEqual(
+ #config{strict_comments = true,
+ strict_commas = true,
+ strict_utf8 = true,
+ strict_single_quotes = true,
+ strict_escapes = true,
+ strict_control_codes = true
+ },
+ parse_config([strict])
+ )
+ },
+ {"strict selective",
+ ?_assertEqual(
+ #config{strict_comments = true},
+ parse_config([{strict, [comments]}])
+ )
+ },
+ {"strict expanded",
+ ?_assertEqual(
+ #config{strict_comments = true,
+ strict_utf8 = true,
+ strict_single_quotes = true,
+ strict_escapes = true
+ },
+ parse_config([{strict, [comments, utf8, single_quotes, escapes]}])
+ )
+ },
+ {"error_handler flag", ?_assertEqual(
+ #config{error_handler=fun ?MODULE:fake_error_handler/3},
+ parse_config([{error_handler, fun ?MODULE:fake_error_handler/3}])
+ )},
+ {"two error_handlers defined", ?_assertError(
+ badarg,
+ parse_config([
+ {error_handler, fun(_, _, _) -> true end},
+ {error_handler, fun(_, _, _) -> false end}
+ ])
+ )},
+ {"incomplete_handler flag", ?_assertEqual(
+ #config{incomplete_handler=fun ?MODULE:fake_error_handler/3},
+ parse_config([{incomplete_handler, fun ?MODULE:fake_error_handler/3}])
+ )},
+ {"two incomplete_handlers defined", ?_assertError(
+ badarg,
+ parse_config([
+ {incomplete_handler, fun(_, _, _) -> true end},
+ {incomplete_handler, fun(_, _, _) -> false end}
+ ])
+ )},
+ {"bad option flag", ?_assertError(badarg, parse_config([this_flag_does_not_exist]))}
+ ].
+
+
+config_to_list_test_() ->
+ [
+ {"empty config", ?_assertEqual(
+ [],
+ config_to_list(#config{})
+ )},
+ {"all flags", ?_assertEqual(
+ [dirty_strings,
+ escaped_forward_slashes,
+ escaped_strings,
+ multi_term,
+ stream,
+ uescape,
+ unescaped_jsonp,
+ strict
+ ],
+ config_to_list(
+ #config{escaped_forward_slashes = true,
+ escaped_strings = true,
+ unescaped_jsonp = true,
+ dirty_strings = true,
+ multi_term = true,
+ strict_comments = true,
+ strict_utf8 = true,
+ strict_single_quotes = true,
+ strict_escapes = true,
+ strict_control_codes = true,
+ stream = true,
+ uescape = true
+ }
+ )
+ )},
+ {"single strict", ?_assertEqual(
+ [{strict, [comments]}],
+ config_to_list(#config{strict_comments = true})
+ )},
+ {"multiple strict", ?_assertEqual(
+ [{strict, [utf8, single_quotes, escapes]}],
+ config_to_list(#config{strict_utf8 = true, strict_single_quotes = true, strict_escapes = true})
+ )},
+ {"all strict", ?_assertEqual(
+ [strict],
+ config_to_list(#config{strict_comments = true,
+ strict_utf8 = true,
+ strict_single_quotes = true,
+ strict_escapes = true,
+ strict_control_codes = true})
+ )},
+ {"error handler", ?_assertEqual(
+ [{error_handler, fun ?MODULE:fake_error_handler/3}],
+ config_to_list(#config{error_handler=fun ?MODULE:fake_error_handler/3})
+ )},
+ {"incomplete handler", ?_assertEqual(
+ [{incomplete_handler, fun ?MODULE:fake_error_handler/3}],
+ config_to_list(#config{incomplete_handler=fun ?MODULE:fake_error_handler/3})
+ )}
+ ].
+
+
+fake_error_handler(_, _, _) -> ok.
+
+
+-endif.
diff --git a/server/_build/default/lib/jsx/src/jsx_config.hrl b/server/_build/default/lib/jsx/src/jsx_config.hrl
new file mode 100644
index 0000000..c89963c
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx_config.hrl
@@ -0,0 +1,18 @@
+-record(config, {
+ dirty_strings = false :: boolean(),
+ escaped_forward_slashes = false :: boolean(),
+ escaped_strings = false :: boolean(),
+ multi_term = false :: boolean(),
+ strict_comments = false :: boolean(),
+ strict_commas = false :: boolean(),
+ strict_utf8 = false :: boolean(),
+ strict_single_quotes = false :: boolean(),
+ strict_escapes = false :: boolean(),
+ strict_control_codes = false :: boolean(),
+ stream = false :: boolean(),
+ return_tail = false :: boolean(),
+ uescape = false :: boolean(),
+ unescaped_jsonp = false :: boolean(),
+ error_handler = false :: false | jsx_config:handler(),
+ incomplete_handler = false :: false | jsx_config:handler()
+}).
diff --git a/server/_build/default/lib/jsx/src/jsx_consult.erl b/server/_build/default/lib/jsx/src/jsx_consult.erl
new file mode 100644
index 0000000..e0e73c9
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx_consult.erl
@@ -0,0 +1,81 @@
+%% The MIT License
+
+%% Copyright (c) 2010-2015 Alisdair Sullivan <alisdairsullivan@yahoo.ca>
+
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+
+
+-module(jsx_consult).
+
+-export([consult/2]).
+-export([init/1, reset/1, handle_event/2]).
+
+
+-record(config, {
+ labels = binary,
+ return_maps = false
+}).
+
+-type config() :: proplists:proplist().
+-export_type([config/0]).
+
+-type json_value() :: list(json_value())
+ | map()
+ | true
+ | false
+ | null
+ | integer()
+ | float()
+ | binary().
+-export_type([json_value/0]).
+
+opts(Opts) -> [return_maps, multi_term] ++ Opts.
+
+-spec consult(File::file:name_all(), Config::config()) -> [json_value()].
+
+consult(File, Config) when is_list(Config) ->
+ case file:read_file(File) of
+ {ok, Bin} ->
+ {Final, _, _} = (jsx:decoder(
+ ?MODULE,
+ opts(Config),
+ jsx_config:extract_config(opts(Config))
+ ))(Bin),
+ lists:reverse(Final);
+ {error, _} -> erlang:error(badarg)
+ end.
+
+
+-type state() :: {[], config(), {list(), #config{}}}.
+-spec init(Config::config()) -> state().
+
+init(Config) -> {[], Config, jsx_to_term:start_term(Config)}.
+
+
+-spec reset(State::state()) -> state().
+
+reset({Acc, Config, _}) -> {Acc, Config, jsx_to_term:start_term(Config)}.
+
+
+-spec handle_event(Event::any(), State::state()) -> state().
+
+handle_event(end_json, {Acc, Config, State}) ->
+ {[jsx_to_term:get_value(State)] ++ Acc, Config, State};
+handle_event(Event, {Acc, Config, State}) ->
+ {Acc, Config, jsx_to_term:handle_event(Event, State)}.
diff --git a/server/_build/default/lib/jsx/src/jsx_decoder.erl b/server/_build/default/lib/jsx/src/jsx_decoder.erl
new file mode 100644
index 0000000..a706c89
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx_decoder.erl
@@ -0,0 +1,1909 @@
+%% The MIT License
+
+%% Copyright (c) 2010-2013 alisdair sullivan <alisdairsullivan@yahoo.ca>
+
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+
+
+-module(jsx_decoder).
+
+%% inline handle_event, format_number and maybe_replace
+-compile({inline, [handle_event/3]}).
+-compile({inline, [format_number/1]}).
+-compile({inline, [maybe_replace/2]}).
+-compile({inline, [doublequote/5, singlequote/5]}).
+
+-export([decoder/3, resume/6]).
+
+
+-spec decoder(Handler::module(), State::any(), Config::jsx_config:options()) -> jsx:decoder().
+
+decoder(Handler, State, Config) ->
+ fun(JSON) -> start(JSON, {Handler, Handler:init(State)}, [], jsx_config:parse_config(Config)) end.
+
+
+%% resume allows continuation from interrupted decoding without having to explicitly export
+%% all states
+-spec resume(
+ Rest::binary(),
+ State::atom(),
+ Handler::module(),
+ Acc::any(),
+ Stack::list(atom()),
+ Config::jsx:config()
+ ) -> jsx:decoder() | {incomplete, jsx:decoder()}.
+
+resume(Rest, State, Handler, Acc, Stack, Config) ->
+ case State of
+ start -> start(Rest, Handler, Stack, Config);
+ value -> value(Rest, Handler, Stack, Config);
+ object -> object(Rest, Handler, Stack, Config);
+ array -> array(Rest, Handler, Stack, Config);
+ colon -> colon(Rest, Handler, Stack, Config);
+ key -> key(Rest, Handler, Stack, Config);
+ string -> string(Rest, Handler, Acc, Stack, Config);
+ number -> number(Rest, Handler, Acc, Stack, Config);
+ true -> true(Rest, Handler, Stack, Config);
+ false -> false(Rest, Handler, Stack, Config);
+ null -> null(Rest, Handler, Stack, Config);
+ comment -> comment(Rest, Handler, Acc, Stack, Config);
+ maybe_done -> maybe_done(Rest, Handler, Stack, Config);
+ done -> done(Rest, Handler, Stack, Config)
+ end.
+
+
+-include("jsx_config.hrl").
+
+
+%% whitespace
+-define(space, 16#20).
+-define(tab, 16#09).
+-define(cr, 16#0D).
+-define(newline, 16#0A).
+
+%% object delimiters
+-define(start_object, 16#7B).
+-define(end_object, 16#7D).
+
+%% array delimiters
+-define(start_array, 16#5B).
+-define(end_array, 16#5D).
+
+%% kv seperator
+-define(comma, 16#2C).
+-define(doublequote, 16#22).
+-define(singlequote, 16#27).
+-define(colon, 16#3A).
+
+%% string escape sequences
+-define(rsolidus, 16#5C).
+-define(solidus, 16#2F).
+
+%% math
+-define(zero, 16#30).
+-define(decimalpoint, 16#2E).
+-define(negative, 16#2D).
+-define(positive, 16#2B).
+
+%% comments
+-define(star, 16#2A).
+
+
+%% some useful guards
+-define(is_hex(Symbol),
+ (Symbol >= $a andalso Symbol =< $f) orelse
+ (Symbol >= $A andalso Symbol =< $F) orelse
+ (Symbol >= $0 andalso Symbol =< $9)
+).
+
+-define(is_nonzero(Symbol),
+ Symbol >= $1 andalso Symbol =< $9
+).
+
+
+%% error is a macro so the stack trace shows the error site when possible
+-ifndef(error).
+-define(error(State, Bin, Handler, Acc, Stack, Config),
+ case Config#config.error_handler of
+ false -> erlang:error(badarg);
+ F -> F(Bin, {decoder, State, Handler, Acc, Stack}, jsx_config:config_to_list(Config))
+ end
+).
+-define(error(State, Bin, Handler, Stack, Config),
+ ?error(State, Bin, Handler, null, Stack, Config)
+).
+-endif.
+
+
+incomplete(State, Rest, Handler, Stack, Config = #config{stream=false}) ->
+ ?error(State, Rest, Handler, Stack, Config);
+incomplete(State, Rest, Handler, Stack, Config) ->
+ incomplete(State, Rest, Handler, unused, Stack, Config).
+
+
+incomplete(State, Rest, Handler, Acc, Stack, Config = #config{stream=false}) ->
+ ?error(State, Rest, Handler, Acc, Stack, Config);
+incomplete(State, Rest, Handler, Acc, Stack, Config = #config{incomplete_handler=false}) ->
+ {incomplete, fun(Stream) when is_binary(Stream) ->
+ resume(<<Rest/binary, Stream/binary>>, State, Handler, Acc, Stack, Config);
+ (End) when End == end_stream; End == end_json ->
+ case resume(<<Rest/binary, ?space/utf8>>, State, Handler, Acc, Stack, Config#config{stream=false}) of
+ {incomplete, _} -> ?error(State, Rest, Handler, Acc, Stack, Config);
+ Else -> Else
+ end
+ end
+ };
+incomplete(State, Rest, Handler, Acc, Stack, Config = #config{incomplete_handler=F}) ->
+ F(Rest, {decoder, State, Handler, Acc, Stack}, jsx_config:config_to_list(Config)).
+
+
+handle_event(Event, {Handler, State}, _Config) -> {Handler, Handler:handle_event(Event, State)}.
+
+
+start(<<16#ef, 16#bb, 16#bf, Rest/binary>>, Handler, Stack, Config) ->
+ value(Rest, Handler, Stack, Config);
+start(<<16#ef, 16#bb>>, Handler, Stack, Config) ->
+ incomplete(start, <<16#ef, 16#bb>>, Handler, Stack, Config);
+start(<<16#ef>>, Handler, Stack, Config) ->
+ incomplete(start, <<16#ef>>, Handler, Stack, Config);
+start(<<>>, Handler, Stack, Config) ->
+ incomplete(start, <<>>, Handler, Stack, Config);
+start(Bin, Handler, Stack, Config) ->
+ value(Bin, Handler, Stack, Config).
+
+
+value(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
+ string(Rest, Handler, Stack, Config);
+value(<<?space, Rest/binary>>, Handler, Stack, Config) ->
+ value(Rest, Handler, Stack, Config);
+value(<<?start_object, Rest/binary>>, Handler, Stack, Config) ->
+ object(Rest, handle_event(start_object, Handler, Config), [key|Stack], Config);
+value(<<?start_array, Rest/binary>>, Handler, Stack, Config) ->
+ array(Rest, handle_event(start_array, Handler, Config), [array|Stack], Config);
+value(<<$t, $r, $u, $e, Rest/binary>>, Handler, Stack, Config) ->
+ maybe_done(Rest, handle_event({literal, true}, Handler, Config), Stack, Config);
+value(<<$f, $a, $l, $s, $e, Rest/binary>>, Handler, Stack, Config) ->
+ maybe_done(Rest, handle_event({literal, false}, Handler, Config), Stack, Config);
+value(<<$n, $u, $l, $l, Rest/binary>>, Handler, Stack, Config) ->
+ maybe_done(Rest, handle_event({literal, null}, Handler, Config), Stack, Config);
+value(<<?zero, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [?zero], [zero|Stack], Config);
+value(<<$1, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [$1], [integer|Stack], Config);
+value(<<$2, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [$2], [integer|Stack], Config);
+value(<<$3, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [$3], [integer|Stack], Config);
+value(<<$4, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [$4], [integer|Stack], Config);
+value(<<$5, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [$5], [integer|Stack], Config);
+value(<<$6, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [$6], [integer|Stack], Config);
+value(<<$7, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [$7], [integer|Stack], Config);
+value(<<$8, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [$8], [integer|Stack], Config);
+value(<<$9, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [$9], [integer|Stack], Config);
+value(<<?negative, Rest/binary>>, Handler, Stack, Config) ->
+ number(Rest, Handler, [$-], [negative|Stack], Config);
+value(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
+ value(Rest, Handler, Stack, Config);
+value(<<$t, Rest/binary>>, Handler, Stack, Config) ->
+ true(Rest, Handler, Stack, Config);
+value(<<$f, Rest/binary>>, Handler, Stack, Config) ->
+ false(Rest, Handler, Stack, Config);
+value(<<$n, Rest/binary>>, Handler, Stack, Config) ->
+ null(Rest, Handler, Stack, Config);
+value(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
+ value(Rest, Handler, Stack, Config);
+value(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
+ value(Rest, Handler, Stack, Config);
+value(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
+ string(Rest, Handler, [singlequote|Stack], Config);
+value(<<?end_array, _/binary>> = Rest, Handler, Stack, Config=#config{strict_commas=false}) ->
+ maybe_done(Rest, Handler, Stack, Config);
+value(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
+ ?error(value, <<?solidus, Rest/binary>>, Handler, Stack, Config);
+value(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, value, [comment|Stack], Config);
+value(<<?solidus, ?star, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, value, [multicomment|Stack], Config);
+value(<<?solidus>>, Handler, Stack, Config) ->
+ incomplete(value, <<?solidus>>, Handler, Stack, Config);
+value(<<>>, Handler, Stack, Config) ->
+ incomplete(value, <<>>, Handler, Stack, Config);
+value(Bin, Handler, Stack, Config) ->
+ ?error(value, Bin, Handler, Stack, Config).
+
+
+object(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
+ string(Rest, Handler, Stack, Config);
+object(<<?space, Rest/binary>>, Handler, Stack, Config) ->
+ object(Rest, Handler, Stack, Config);
+object(<<?end_object, Rest/binary>>, Handler, [key|Stack], Config) ->
+ maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config);
+object(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
+ object(Rest, Handler, Stack, Config);
+object(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
+ object(Rest, Handler, Stack, Config);
+object(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
+ object(Rest, Handler, Stack, Config);
+object(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
+ string(Rest, Handler, [singlequote|Stack], Config);
+object(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
+ ?error(object, <<?solidus, Rest/binary>>, Handler, Stack, Config);
+object(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, object, [comment|Stack], Config);
+object(<<?solidus, ?star, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, object, [multicomment|Stack], Config);
+object(<<?solidus>>, Handler, Stack, Config) ->
+ incomplete(object, <<?solidus>>, Handler, Stack, Config);
+object(<<>>, Handler, Stack, Config) ->
+ incomplete(object, <<>>, Handler, Stack, Config);
+object(Bin, Handler, Stack, Config) ->
+ ?error(object, Bin, Handler, Stack, Config).
+
+
+array(<<?end_array, Rest/binary>>, Handler, [array|Stack], Config) ->
+ maybe_done(Rest, handle_event(end_array, Handler, Config), Stack, Config);
+array(<<?space, Rest/binary>>, Handler, Stack, Config) ->
+ array(Rest, Handler, Stack, Config);
+array(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
+ array(Rest, Handler, Stack, Config);
+array(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
+ array(Rest, Handler, Stack, Config);
+array(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
+ array(Rest, Handler, Stack, Config);
+array(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
+ value(<<?solidus, Rest/binary>>, Handler, Stack, Config);
+array(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, array, [comment|Stack], Config);
+array(<<?solidus, ?star, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, array, [multicomment|Stack], Config);
+array(<<?solidus>>, Handler, Stack, Config) ->
+ incomplete(array, <<?solidus>>, Handler, Stack, Config);
+array(<<>>, Handler, Stack, Config) ->
+ incomplete(array, <<>>, Handler, Stack, Config);
+array(Bin, Handler, Stack, Config) ->
+ value(Bin, Handler, Stack, Config).
+
+
+colon(<<?colon, Rest/binary>>, Handler, [key|Stack], Config) ->
+ value(Rest, Handler, [object|Stack], Config);
+colon(<<?space, Rest/binary>>, Handler, Stack, Config) ->
+ colon(Rest, Handler, Stack, Config);
+colon(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
+ colon(Rest, Handler, Stack, Config);
+colon(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
+ colon(Rest, Handler, Stack, Config);
+colon(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
+ colon(Rest, Handler, Stack, Config);
+colon(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
+ ?error(colon, <<?solidus, Rest/binary>>, Handler, Stack, Config);
+colon(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, colon, [comment|Stack], Config);
+colon(<<?solidus, ?star, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, colon, [multicomment|Stack], Config);
+colon(<<?solidus>>, Handler, Stack, Config) ->
+ incomplete(colon, <<?solidus>>, Handler, Stack, Config);
+colon(<<>>, Handler, Stack, Config) ->
+ incomplete(colon, <<>>, Handler, Stack, Config);
+colon(Bin, Handler, Stack, Config) ->
+ ?error(colon, Bin, Handler, Stack, Config).
+
+
+key(<<?doublequote, Rest/binary>>, Handler, Stack, Config) ->
+ string(Rest, Handler, Stack, Config);
+key(<<?space, Rest/binary>>, Handler, Stack, Config) ->
+ key(Rest, Handler, Stack, Config);
+key(<<?end_object, Rest/binary>>, Handler, [key|Stack], Config=#config{strict_commas=false}) ->
+ maybe_done(<<?end_object, Rest/binary>>, Handler, [object|Stack], Config);
+key(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
+ key(Rest, Handler, Stack, Config);
+key(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
+ key(Rest, Handler, Stack, Config);
+key(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
+ key(Rest, Handler, Stack, Config);
+key(<<?singlequote, Rest/binary>>, Handler, Stack, Config=#config{strict_single_quotes=false}) ->
+ string(Rest, Handler, [singlequote|Stack], Config);
+key(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
+ ?error(key, <<?solidus, Rest/binary>>, Handler, Stack, Config);
+key(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, key, [comment|Stack], Config);
+key(<<?solidus, ?star, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, key, [multicomment|Stack], Config);
+key(<<?solidus>>, Handler, Stack, Config) ->
+ incomplete(key, <<?solidus>>, Handler, Stack, Config);
+key(<<>>, Handler, Stack, Config) ->
+ incomplete(key, <<>>, Handler, Stack, Config);
+key(Bin, Handler, Stack, Config) ->
+ ?error(key, Bin, Handler, Stack, Config).
+
+
+%% note that if you encounter an error from string and you can't find the clause that
+%% caused it here, it might be in unescape below
+string(Bin, Handler, Stack, Config) ->
+ string(Bin, Handler, [], Stack, Config).
+
+
+string(<<?doublequote, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ doublequote(Rest, Handler, Acc, Stack, Config);
+string(<<?singlequote, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ singlequote(Rest, Handler, Acc, Stack, Config);
+string(<<?solidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace(?solidus, Config)], Stack, Config);
+string(<<?rsolidus/utf8, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ unescape(Rest, Handler, Acc, Stack, Config);
+%% TODO this is pretty gross and i don't like it
+string(<<X/utf8, Rest/binary>> = Bin, Handler, Acc, Stack, Config=#config{uescape=true}) ->
+ case X of
+ X when X < 16#80 -> count(Bin, Handler, Acc, Stack, Config);
+ X -> string(Rest, Handler, [Acc, json_escape_sequence(X)], Stack, Config)
+ end;
+%% u+2028
+string(<<226, 128, 168, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace(16#2028, Config)], Stack, Config);
+%% u+2029
+string(<<226, 128, 169, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace(16#2029, Config)], Stack, Config);
+string(<<X/utf8, _/binary>> = Bin, Handler, Acc, Stack, Config=#config{strict_control_codes=true}) when X > 16#1f ->
+ count(Bin, Handler, Acc, Stack, Config);
+string(<<_/utf8, _/binary>> = Bin, Handler, Acc, Stack, Config=#config{strict_control_codes=false}) ->
+ count(Bin, Handler, Acc, Stack, Config);
+%% necessary for bytes that are badly formed utf8 that won't match in `count`
+string(<<X, Rest/binary>>, Handler, Acc, Stack, Config=#config{dirty_strings=true}) ->
+ string(Rest, Handler, [Acc, X], Stack, Config);
+%% u+fffe and u+ffff for R14BXX (subsequent runtimes will happily match with /utf8
+string(<<239, 191, 190, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, <<16#fffe/utf8>>], Stack, Config);
+string(<<239, 191, 191, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, <<16#ffff/utf8>>], Stack, Config);
+string(<<>>, Handler, Acc, Stack, Config) ->
+ incomplete(string, <<>>, Handler, Acc, Stack, Config);
+string(<<X>>, Handler, Acc, Stack, Config) when X >= 2#11000000 ->
+ incomplete(string, <<X>>, Handler, Acc, Stack, Config);
+string(<<X, Y>>, Handler, Acc, Stack, Config) when X >= 2#11100000, Y >= 2#10000000 ->
+ incomplete(string, <<X, Y>>, Handler, Acc, Stack, Config);
+string(<<X, Y, Z>>, Handler, Acc, Stack, Config)
+ when X >= 2#11100000, Y >= 2#10000000, Z >= 2#10000000 ->
+ incomplete(string, <<X, Y, Z>>, Handler, Acc, Stack, Config);
+%% surrogates
+string(<<237, X, _, Rest/binary>>, Handler, Acc, Stack, Config=#config{strict_utf8=false})
+ when X >= 160 ->
+ string(Rest, Handler, [Acc, <<16#fffd/utf8>>], Stack, Config);
+%% overlong encodings and missing continuations of a 2 byte sequence
+string(<<X, Rest/binary>>, Handler, Acc, Stack, Config=#config{strict_utf8=false})
+ when X >= 192, X =< 223 ->
+ strip_continuations(Rest, Handler, Acc, Stack, Config, 1);
+%% overlong encodings and missing continuations of a 3 byte sequence
+string(<<X, Rest/binary>>, Handler, Acc, Stack, Config=#config{strict_utf8=false})
+ when X >= 224, X =< 239 ->
+ strip_continuations(Rest, Handler, Acc, Stack, Config, 2);
+%% overlong encodings and missing continuations of a 4 byte sequence
+string(<<X, Rest/binary>>, Handler, Acc, Stack, Config=#config{strict_utf8=false})
+ when X >= 240, X =< 247 ->
+ strip_continuations(Rest, Handler, Acc, Stack, Config, 3);
+%% incompletes and unexpected bytes, including orphan continuations
+string(<<_, Rest/binary>>, Handler, Acc, Stack, Config=#config{strict_utf8=false}) ->
+ string(Rest, Handler, [Acc, <<16#fffd/utf8>>], Stack, Config);
+string(Bin, Handler, Acc, Stack, Config) -> ?error(string, Bin, Handler, Acc, Stack, Config).
+
+
+count(Bin, Handler, Acc, Stack, Config) ->
+ Size = count(Bin, 0, Config),
+ <<Clean:Size/binary, Rest/binary>> = Bin,
+ string(Rest, Handler, [Acc, Clean], Stack, Config).
+
+
+%% explicitly whitelist ascii set for faster parsing. really? really. someone should
+%% submit a patch that unrolls simple guards
+count(<<32, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<33, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<?doublequote, _/binary>>, N, _) -> N;
+count(<<35, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<36, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<37, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<38, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<?singlequote, _/binary>>, N, _) -> N;
+count(<<40, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<41, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<42, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<43, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<44, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<45, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<46, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<?solidus, _/binary>>, N, _) -> N;
+count(<<48, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<49, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<50, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<51, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<52, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<53, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<54, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<55, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<56, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<57, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<58, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<59, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<60, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<61, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<62, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<63, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<64, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<65, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<66, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<67, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<68, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<69, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<70, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<71, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<72, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<73, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<74, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<75, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<76, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<77, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<78, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<79, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<80, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<81, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<82, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<83, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<84, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<85, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<86, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<87, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<88, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<89, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<90, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<91, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<?rsolidus, _/binary>>, N, _) -> N;
+count(<<93, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<94, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<95, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<96, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<97, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<98, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<99, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<100, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<101, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<102, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<103, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<104, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<105, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<106, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<107, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<108, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<109, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<110, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<111, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<112, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<113, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<114, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<115, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<116, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<117, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<118, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<119, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<120, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<121, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<122, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<123, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<124, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<125, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<126, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<127, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<_, Rest/binary>>, N, Config=#config{dirty_strings=true}) ->
+ count(Rest, N + 1, Config);
+count(<<_/utf8, _/binary>>, N, #config{uescape=true}) -> N;
+count(<<X/utf8, Rest/binary>>, N, Config=#config{strict_control_codes=false}) when X < 32 ->
+ count(Rest, N + 1, Config);
+count(<<X/utf8, _/binary>>, N, #config{strict_control_codes=true}) when X < 32 -> N;
+count(<<X/utf8, Rest/binary>>, N, Config) ->
+ case X of
+ X when X < 16#800 -> count(Rest, N + 2, Config);
+ %% jsonp escaping
+ 16#2028 -> N;
+ 16#2029 -> N;
+ X when X < 16#10000 -> count(Rest, N + 3, Config);
+ _ -> count(Rest, N + 4, Config)
+ end;
+count(_, N, _) -> N.
+
+
+doublequote(Rest, Handler, Acc, [key|_] = Stack, Config) ->
+ colon(Rest, handle_event({key, iolist_to_binary(Acc)}, Handler, Config), Stack, Config);
+doublequote(Rest, Handler, Acc, [singlequote|_] = Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace(?doublequote, Config)], Stack, Config);
+doublequote(<<>>, Handler, Acc, [singlequote|_] = Stack, Config) ->
+ incomplete(string, <<?doublequote>>, Handler, Acc, Stack, Config);
+doublequote(Rest, Handler, Acc, Stack, Config) ->
+ maybe_done(Rest, handle_event({string, iolist_to_binary(Acc)}, Handler, Config), Stack, Config).
+
+
+singlequote(Rest, Handler, Acc, [singlequote, key|Stack], Config) ->
+ colon(Rest, handle_event({key, iolist_to_binary(Acc)}, Handler, Config), [key|Stack], Config);
+singlequote(Rest, Handler, Acc, [singlequote|Stack], Config) ->
+ maybe_done(Rest, handle_event({string, iolist_to_binary(Acc)}, Handler, Config), Stack, Config);
+singlequote(Rest, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, ?singlequote], Stack, Config).
+
+
+%% strips continuation bytes after bad utf bytes, guards against both too short
+%% and overlong sequences. N is the maximum number of bytes to strip
+strip_continuations(<<Rest/binary>>, Handler, Acc, Stack, Config, 0) ->
+ string(Rest, Handler, [Acc, <<16#fffd/utf8>>], Stack, Config);
+strip_continuations(<<X, Rest/binary>>, Handler, Acc, Stack, Config, N) when X >= 128, X =< 191 ->
+ strip_continuations(Rest, Handler, Acc, Stack, Config, N - 1);
+%% if end of input is reached before stripping the max number of continuations
+%% possible magic numbers are reinserted into the stream that get us back to
+%% the same state without complicated machinery
+strip_continuations(<<>>, Handler, Acc, Stack, Config, N) ->
+ case N of
+ 1 -> incomplete(string, <<192>>, Handler, Acc, Stack, Config);
+ 2 -> incomplete(string, <<224>>, Handler, Acc, Stack, Config);
+ 3 -> incomplete(string, <<240>>, Handler, Acc, Stack, Config)
+ end;
+%% not a continuation byte, insert a replacement character for sequence thus
+%% far and dispatch back to string
+strip_continuations(<<Rest/binary>>, Handler, Acc, Stack, Config, _) ->
+ string(Rest, Handler, [Acc, <<16#fffd/utf8>>], Stack, Config).
+
+
+%% this all gets really gross and should probably eventually be folded into
+%% but for now it fakes being part of string on incompletes and errors
+unescape(<<?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config=#config{dirty_strings=true}) ->
+ string(<<?rsolidus, Rest/binary>>, Handler, [Acc, <<?rsolidus>>], Stack, Config);
+unescape(<<C, Rest/binary>>, Handler, Acc, Stack, Config=#config{dirty_strings=true}) ->
+ string(Rest, Handler, [Acc, <<?rsolidus, C>>], Stack, Config);
+unescape(<<$b, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace($\b, Config)], Stack, Config);
+unescape(<<$f, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace($\f, Config)], Stack, Config);
+unescape(<<$n, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace($\n, Config)], Stack, Config);
+unescape(<<$r, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace($\r, Config)], Stack, Config);
+unescape(<<$t, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace($\t, Config)], Stack, Config);
+unescape(<<?doublequote, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace($\", Config)], Stack, Config);
+unescape(<<?singlequote, Rest/binary>>, Handler, Acc, Stack, Config=#config{strict_single_quotes=false}) ->
+ string(Rest, Handler, [Acc, <<?singlequote>>], Stack, Config);
+unescape(<<?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace($\\, Config)], Stack, Config);
+unescape(<<?solidus, Rest/binary>>, Handler, Acc, Stack, Config) ->
+ string(Rest, Handler, [Acc, maybe_replace($/, Config)], Stack, Config);
+unescape(<<$u, F, A, B, C, ?rsolidus, $u, G, X, Y, Z, Rest/binary>>, Handler, Acc, Stack, Config)
+ when (A == $8 orelse A == $9 orelse A == $a orelse A == $b orelse A == $A orelse A == $B),
+ (X == $c orelse X == $d orelse X == $e orelse X == $f orelse X == $C orelse X == $D orelse X == $E orelse X == $F),
+ (F == $d orelse F == $D),
+ (G == $d orelse G == $D),
+ ?is_hex(B), ?is_hex(C), ?is_hex(Y), ?is_hex(Z)
+ ->
+ High = erlang:list_to_integer([$d, A, B, C], 16),
+ Low = erlang:list_to_integer([$d, X, Y, Z], 16),
+ Codepoint = (High - 16#d800) * 16#400 + (Low - 16#dc00) + 16#10000,
+ string(Rest, Handler, [Acc, <<Codepoint/utf8>>], Stack, Config);
+unescape(<<$u, F0, A, B, C, ?rsolidus, $u, W, X, Y, Z, Rest/binary>>, Handler, Acc, Stack, Config)
+ when (A == $8 orelse A == $9 orelse A == $a orelse A == $b orelse A == $A orelse A == $B),
+ (F0 == $d orelse F0 == $D),
+ ?is_hex(B), ?is_hex(C), ?is_hex(W), ?is_hex(X), ?is_hex(Y), ?is_hex(Z)
+ ->
+ case Config#config.strict_utf8 of
+ true -> ?error(string, <<$u, $d, A, B, C, ?rsolidus, $u, W, X, Y, Z, Rest/binary>>, Handler, Acc, Stack, Config);
+ false -> string(Rest, Handler, [Acc, <<16#fffd/utf8>>, <<16#fffd/utf8>>], Stack, Config)
+ end;
+unescape(<<$u, F, A, B, C, ?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config)
+ when (A == $8 orelse A == $9 orelse A == $a orelse A == $b orelse A == $A orelse A == $B),
+ (F == $d orelse F == $D),
+ ?is_hex(B), ?is_hex(C)
+ ->
+ incomplete(string, <<?rsolidus, $u, $d, A, B, C, ?rsolidus, Rest/binary>>, Handler, Acc, Stack, Config);
+unescape(<<$u, F, A, B, C>>, Handler, Acc, Stack, Config)
+ when (A == $8 orelse A == $9 orelse A == $a orelse A == $b orelse A == $A orelse A == $B),
+ (F == $d orelse F == $D),
+ ?is_hex(B), ?is_hex(C)
+ ->
+ incomplete(string, <<?rsolidus, $u, $d, A, B, C>>, Handler, Acc, Stack, Config);
+unescape(<<$u, A, B, C, D, Rest/binary>>, Handler, Acc, Stack, Config)
+ when ?is_hex(A), ?is_hex(B), ?is_hex(C), ?is_hex(D) ->
+ case erlang:list_to_integer([A, B, C, D], 16) of
+ Codepoint when Codepoint < 16#d800; Codepoint > 16#dfff ->
+ string(Rest, Handler, [Acc, maybe_replace(Codepoint, Config)], Stack, Config);
+ _ when Config#config.strict_utf8 ->
+ ?error(string, <<?rsolidus, $u, A, B, C, D, Rest/binary>>, Handler, Acc, Stack, Config);
+ _ -> string(Rest, Handler, [Acc, <<16#fffd/utf8>>], Stack, Config)
+ end;
+unescape(Bin, Handler, Acc, Stack, Config) ->
+ case is_partial_escape(Bin) of
+ true -> incomplete(string, <<?rsolidus/utf8, Bin/binary>>, Handler, Acc, Stack, Config);
+ false -> case Config#config.strict_escapes of
+ true -> ?error(string, <<?rsolidus, Bin/binary>>, Handler, Acc, Stack, Config);
+ false -> string(Bin, Handler, [Acc, <<?rsolidus>>], Stack, Config)
+ end
+ end.
+
+
+is_partial_escape(<<$u, A, B, C>>) when ?is_hex(A), ?is_hex(B), ?is_hex(C) -> true;
+is_partial_escape(<<$u, A, B>>) when ?is_hex(A), ?is_hex(B) -> true;
+is_partial_escape(<<$u, A>>) when ?is_hex(A) -> true;
+is_partial_escape(<<$u>>) -> true;
+is_partial_escape(<<>>) -> true;
+is_partial_escape(_) -> false.
+
+
+maybe_replace(C, #config{dirty_strings=true}) -> <<C>>;
+maybe_replace($\b, #config{escaped_strings=true}) -> <<$\\, $b>>;
+maybe_replace($\t, #config{escaped_strings=true}) -> <<$\\, $t>>;
+maybe_replace($\n, #config{escaped_strings=true}) -> <<$\\, $n>>;
+maybe_replace($\f, #config{escaped_strings=true}) -> <<$\\, $f>>;
+maybe_replace($\r, #config{escaped_strings=true}) -> <<$\\, $r>>;
+maybe_replace($\", #config{escaped_strings=true}) -> <<$\\, $\">>;
+maybe_replace($/, Config=#config{escaped_strings=true}) ->
+ case Config#config.escaped_forward_slashes of
+ true -> <<$\\, $/>>
+ ; false -> <<$/>>
+ end;
+maybe_replace($\\, #config{escaped_strings=true}) -> <<$\\, $\\>>;
+maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 ->
+ case Config#config.unescaped_jsonp of
+ true -> <<X/utf8>>
+ ; false -> json_escape_sequence(X)
+ end;
+maybe_replace(X, #config{escaped_strings=true}) when X < 32 ->
+ json_escape_sequence(X);
+maybe_replace(X, _Config) -> <<X/utf8>>.
+
+
+%% convert a codepoint to it's \uXXXX equiv.
+json_escape_sequence(X) when X < 65536 ->
+ <<A:4, B:4, C:4, D:4>> = <<X:16>>,
+ <<$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))>>;
+json_escape_sequence(X) ->
+ Adjusted = X - 16#10000,
+ <<A:10, B:10>> = <<Adjusted:20>>,
+ [json_escape_sequence(A + 16#d800), json_escape_sequence(B + 16#dc00)].
+
+
+%% ascii "1" is [49], "2" is [50], etc...
+to_hex(10) -> $a;
+to_hex(11) -> $b;
+to_hex(12) -> $c;
+to_hex(13) -> $d;
+to_hex(14) -> $e;
+to_hex(15) -> $f;
+to_hex(X) -> X + 48.
+
+
+number(<<$e, Rest/binary>>, Handler, Acc, [integer|Stack], Config) ->
+ number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
+number(<<$E, Rest/binary>>, Handler, Acc, [integer|Stack], Config) ->
+ number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
+number(<<$e, Rest/binary>>, Handler, Acc, [zero|Stack], Config) ->
+ number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
+number(<<$E, Rest/binary>>, Handler, Acc, [zero|Stack], Config) ->
+ number(Rest, Handler, [Acc, $., $0, $e], [e|Stack], Config);
+number(<<>>, Handler, Acc, [State|Stack], Config=#config{stream=false}) ->
+ NumType = case State of
+ zero -> integer;
+ integer -> integer;
+ decimal -> float;
+ exp -> float
+ end,
+ finish_number(<<>>, Handler, {NumType, iolist_to_binary(Acc)}, Stack, Config);
+number(<<>>, Handler, Acc, Stack, Config) ->
+ incomplete(number, <<>>, Handler, Acc, Stack, Config);
+number(Bin, Handler, Acc, [State|Stack], Config) ->
+ Counted = case State of
+ zero -> zero(Bin, 0);
+ integer -> integer(Bin, 0);
+ negative -> negative(Bin, 0);
+ initialdecimal -> initialdecimal(Bin, 0);
+ decimal -> decimal(Bin, 0);
+ e -> e(Bin, 0);
+ ex -> ex(Bin, 0);
+ exp -> exp(Bin, 0)
+ end,
+ case Counted of
+ {finish_integer, Size} ->
+ <<Clean:Size/binary, Rest/binary>> = Bin,
+ finish_number(Rest, Handler, {integer, iolist_to_binary([Acc, Clean])}, Stack, Config);
+ {finish_float, Size} ->
+ <<Clean:Size/binary, Rest/binary>> = Bin,
+ finish_number(Rest, Handler, {float, iolist_to_binary([Acc, Clean])}, Stack, Config);
+ {error, Size} ->
+ <<Clean:Size/binary, Rest/binary>> = Bin,
+ ?error(number, Rest, Handler, [Acc, Clean], Stack, Config);
+ {NewState, Size} ->
+ <<Clean:Size/binary, Rest/binary>> = Bin,
+ number(Rest, Handler, [Acc, Clean], [NewState|Stack], Config)
+ end.
+
+
+zero(<<?decimalpoint, Rest/binary>>, N) -> initialdecimal(Rest, N + 1);
+zero(<<$e, _/binary>>, N) -> {integer, N};
+zero(<<$E, _/binary>>, N) -> {integer, N};
+zero(<<>>, N) -> {zero, N};
+zero(_, N) -> {finish_integer, N}.
+
+
+integer(<<$0, Rest/binary>>, N) -> integer(Rest, N + 1);
+integer(<<$1, Rest/binary>>, N) -> integer(Rest, N + 1);
+integer(<<$2, Rest/binary>>, N) -> integer(Rest, N + 1);
+integer(<<$3, Rest/binary>>, N) -> integer(Rest, N + 1);
+integer(<<$4, Rest/binary>>, N) -> integer(Rest, N + 1);
+integer(<<$5, Rest/binary>>, N) -> integer(Rest, N + 1);
+integer(<<$6, Rest/binary>>, N) -> integer(Rest, N + 1);
+integer(<<$7, Rest/binary>>, N) -> integer(Rest, N + 1);
+integer(<<$8, Rest/binary>>, N) -> integer(Rest, N + 1);
+integer(<<$9, Rest/binary>>, N) -> integer(Rest, N + 1);
+integer(<<?decimalpoint, Rest/binary>>, N) -> initialdecimal(Rest, N + 1);
+integer(<<$e, _/binary>>, N) -> {integer, N};
+integer(<<$E, _/binary>>, N) -> {integer, N};
+integer(<<>>, N) -> {integer, N};
+integer(_, N) -> {finish_integer, N}.
+
+
+negative(<<$0, Rest/binary>>, N) -> zero(Rest, N + 1);
+negative(<<$1, Rest/binary>>, N) -> integer(Rest, N + 1);
+negative(<<$2, Rest/binary>>, N) -> integer(Rest, N + 1);
+negative(<<$3, Rest/binary>>, N) -> integer(Rest, N + 1);
+negative(<<$4, Rest/binary>>, N) -> integer(Rest, N + 1);
+negative(<<$5, Rest/binary>>, N) -> integer(Rest, N + 1);
+negative(<<$6, Rest/binary>>, N) -> integer(Rest, N + 1);
+negative(<<$7, Rest/binary>>, N) -> integer(Rest, N + 1);
+negative(<<$8, Rest/binary>>, N) -> integer(Rest, N + 1);
+negative(<<$9, Rest/binary>>, N) -> integer(Rest, N + 1);
+negative(<<>>, N) -> {negative, N};
+negative(_, N) -> {error, N}.
+
+
+initialdecimal(<<$0, Rest/binary>>, N) -> decimal(Rest, N + 1);
+initialdecimal(<<$1, Rest/binary>>, N) -> decimal(Rest, N + 1);
+initialdecimal(<<$2, Rest/binary>>, N) -> decimal(Rest, N + 1);
+initialdecimal(<<$3, Rest/binary>>, N) -> decimal(Rest, N + 1);
+initialdecimal(<<$4, Rest/binary>>, N) -> decimal(Rest, N + 1);
+initialdecimal(<<$5, Rest/binary>>, N) -> decimal(Rest, N + 1);
+initialdecimal(<<$6, Rest/binary>>, N) -> decimal(Rest, N + 1);
+initialdecimal(<<$7, Rest/binary>>, N) -> decimal(Rest, N + 1);
+initialdecimal(<<$8, Rest/binary>>, N) -> decimal(Rest, N + 1);
+initialdecimal(<<$9, Rest/binary>>, N) -> decimal(Rest, N + 1);
+initialdecimal(<<>>, N) -> {initialdecimal, N};
+initialdecimal(_, N) -> {error, N}.
+
+
+decimal(<<$0, Rest/binary>>, N) -> decimal(Rest, N + 1);
+decimal(<<$1, Rest/binary>>, N) -> decimal(Rest, N + 1);
+decimal(<<$2, Rest/binary>>, N) -> decimal(Rest, N + 1);
+decimal(<<$3, Rest/binary>>, N) -> decimal(Rest, N + 1);
+decimal(<<$4, Rest/binary>>, N) -> decimal(Rest, N + 1);
+decimal(<<$5, Rest/binary>>, N) -> decimal(Rest, N + 1);
+decimal(<<$6, Rest/binary>>, N) -> decimal(Rest, N + 1);
+decimal(<<$7, Rest/binary>>, N) -> decimal(Rest, N + 1);
+decimal(<<$8, Rest/binary>>, N) -> decimal(Rest, N + 1);
+decimal(<<$9, Rest/binary>>, N) -> decimal(Rest, N + 1);
+decimal(<<$e, Rest/binary>>, N) -> e(Rest, N + 1);
+decimal(<<$E, Rest/binary>>, N) -> e(Rest, N + 1);
+decimal(<<>>, N) -> {decimal, N};
+decimal(_, N) -> {finish_float, N}.
+
+
+e(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1);
+e(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1);
+e(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1);
+e(<<$3, Rest/binary>>, N) -> exp(Rest, N + 1);
+e(<<$4, Rest/binary>>, N) -> exp(Rest, N + 1);
+e(<<$5, Rest/binary>>, N) -> exp(Rest, N + 1);
+e(<<$6, Rest/binary>>, N) -> exp(Rest, N + 1);
+e(<<$7, Rest/binary>>, N) -> exp(Rest, N + 1);
+e(<<$8, Rest/binary>>, N) -> exp(Rest, N + 1);
+e(<<$9, Rest/binary>>, N) -> exp(Rest, N + 1);
+e(<<?positive, Rest/binary>>, N) -> ex(Rest, N + 1);
+e(<<?negative, Rest/binary>>, N) -> ex(Rest, N + 1);
+e(<<>>, N) -> {e, N};
+e(_, N) -> {error, N}.
+
+
+ex(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1);
+ex(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1);
+ex(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1);
+ex(<<$3, Rest/binary>>, N) -> exp(Rest, N + 1);
+ex(<<$4, Rest/binary>>, N) -> exp(Rest, N + 1);
+ex(<<$5, Rest/binary>>, N) -> exp(Rest, N + 1);
+ex(<<$6, Rest/binary>>, N) -> exp(Rest, N + 1);
+ex(<<$7, Rest/binary>>, N) -> exp(Rest, N + 1);
+ex(<<$8, Rest/binary>>, N) -> exp(Rest, N + 1);
+ex(<<$9, Rest/binary>>, N) -> exp(Rest, N + 1);
+ex(<<>>, N) -> {ex, N};
+ex(_, N) -> {error, N}.
+
+
+exp(<<$0, Rest/binary>>, N) -> exp(Rest, N + 1);
+exp(<<$1, Rest/binary>>, N) -> exp(Rest, N + 1);
+exp(<<$2, Rest/binary>>, N) -> exp(Rest, N + 1);
+exp(<<$3, Rest/binary>>, N) -> exp(Rest, N + 1);
+exp(<<$4, Rest/binary>>, N) -> exp(Rest, N + 1);
+exp(<<$5, Rest/binary>>, N) -> exp(Rest, N + 1);
+exp(<<$6, Rest/binary>>, N) -> exp(Rest, N + 1);
+exp(<<$7, Rest/binary>>, N) -> exp(Rest, N + 1);
+exp(<<$8, Rest/binary>>, N) -> exp(Rest, N + 1);
+exp(<<$9, Rest/binary>>, N) -> exp(Rest, N + 1);
+exp(<<>>, N) -> {exp, N};
+exp(_, N) -> {finish_float, N}.
+
+
+finish_number(Rest, Handler, Acc, Stack, Config) ->
+ maybe_done(Rest, handle_event(format_number(Acc), Handler, Config), Stack, Config).
+
+format_number({integer, Acc}) -> {integer, binary_to_integer(Acc)};
+format_number({float, Acc}) -> {float, binary_to_float(Acc)}.
+
+true(<<$r, $u, $e, Rest/binary>>, Handler, Stack, Config) ->
+ maybe_done(Rest, handle_event({literal, true}, Handler, Config), Stack, Config);
+true(<<$r, $u>>, Handler, Stack, Config) ->
+ incomplete(true, <<$r, $u>>, Handler, Stack, Config);
+true(<<$r>>, Handler, Stack, Config) ->
+ incomplete(true, <<$r>>, Handler, Stack, Config);
+true(<<>>, Handler, Stack, Config) ->
+ incomplete(true, <<>>, Handler, Stack, Config);
+true(Bin, Handler, Stack, Config) ->
+ ?error(true, Bin, Handler, Stack, Config).
+
+
+false(<<$a, $l, $s, $e, Rest/binary>>, Handler, Stack, Config) ->
+ maybe_done(Rest, handle_event({literal, false}, Handler, Config), Stack, Config);
+false(<<$a, $l, $s>>, Handler, Stack, Config) ->
+ incomplete(false, <<$a, $l, $s>>, Handler, Stack, Config);
+false(<<$a, $l>>, Handler, Stack, Config) ->
+ incomplete(false, <<$a, $l>>, Handler, Stack, Config);
+false(<<$a>>, Handler, Stack, Config) ->
+ incomplete(false, <<$a>>, Handler, Stack, Config);
+false(<<>>, Handler, Stack, Config) ->
+ incomplete(false, <<>>, Handler, Stack, Config);
+false(Bin, Handler, Stack, Config) ->
+ ?error(false, Bin, Handler, Stack, Config).
+
+
+null(<<$u, $l, $l, Rest/binary>>, Handler, Stack, Config) ->
+ maybe_done(Rest, handle_event({literal, null}, Handler, Config), Stack, Config);
+null(<<$u, $l>>, Handler, Stack, Config) ->
+ incomplete(null, <<$u, $l>>, Handler, Stack, Config);
+null(<<$u>>, Handler, Stack, Config) ->
+ incomplete(null, <<$u>>, Handler, Stack, Config);
+null(<<>>, Handler, Stack, Config) ->
+ incomplete(null, <<>>, Handler, Stack, Config);
+null(Bin, Handler, Stack, Config) ->
+ ?error(null, Bin, Handler, Stack, Config).
+
+
+comment(<<?newline, Rest/binary>>, Handler, Resume, [comment|Stack], Config) ->
+ resume(Rest, Resume, Handler, unused, Stack, Config);
+comment(<<?solidus, ?star, Rest/binary>>, Handler, Resume, Stack, Config) ->
+ comment(Rest, Handler, Resume, [multicomment|Stack], Config);
+comment(<<?solidus>>, Handler, Resume, [multicomment|_] = Stack, Config) ->
+ incomplete(comment, <<?solidus>>, Handler, Resume, Stack, Config);
+comment(<<?star, ?solidus, Rest/binary>>, Handler, Resume, [multicomment|Stack], Config) ->
+ case Stack of
+ [multicomment|_] -> comment(Rest, Handler, Resume, Stack, Config);
+ _ -> resume(Rest, Resume, Handler, unused, Stack, Config)
+ end;
+comment(<<?star>>, Handler, Resume, [multicomment|_] = Stack, Config) ->
+ incomplete(comment, <<?star>>, Handler, Resume, Stack, Config);
+comment(<<_/utf8, Rest/binary>>, Handler, Resume, Stack, Config) ->
+ comment(Rest, Handler, Resume, Stack, Config);
+comment(<<_, Rest/binary>>, Handler, Resume, Stack, Config=#config{strict_utf8=false}) ->
+ comment(Rest, Handler, Resume, Stack, Config);
+comment(<<>>, Handler, done, [Comment], Config=#config{stream=false})
+ when Comment == comment; Comment == multicomment ->
+ resume(<<>>, done, Handler, unused, [], Config);
+comment(<<>>, Handler, Resume, Stack, Config) ->
+ incomplete(comment, <<>>, Handler, Resume, Stack, Config);
+comment(Bin, Handler, Resume, Stack, Config) ->
+ ?error(comment, Bin, Handler, Resume, Stack, Config).
+
+
+maybe_done(<<Rest/binary>>, Handler, [], Config) ->
+ done(Rest, handle_event(end_json, Handler, Config), [], Config);
+maybe_done(<<?space, Rest/binary>>, Handler, Stack, Config) ->
+ maybe_done(Rest, Handler, Stack, Config);
+maybe_done(<<?end_object, Rest/binary>>, Handler, [object|Stack], Config) ->
+ maybe_done(Rest, handle_event(end_object, Handler, Config), Stack, Config);
+maybe_done(<<?end_array, Rest/binary>>, Handler, [array|Stack], Config) ->
+ maybe_done(Rest, handle_event(end_array, Handler, Config), Stack, Config);
+maybe_done(<<?comma, Rest/binary>>, Handler, [object|Stack], Config) ->
+ key(Rest, Handler, [key|Stack], Config);
+maybe_done(<<?comma, Rest/binary>>, Handler, [array|_] = Stack, Config) ->
+ value(Rest, Handler, Stack, Config);
+maybe_done(<<?newline, Rest/binary>>, Handler, Stack, Config) ->
+ maybe_done(Rest, Handler, Stack, Config);
+maybe_done(<<?tab, Rest/binary>>, Handler, Stack, Config) ->
+ maybe_done(Rest, Handler, Stack, Config);
+maybe_done(<<?cr, Rest/binary>>, Handler, Stack, Config) ->
+ maybe_done(Rest, Handler, Stack, Config);
+maybe_done(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
+ ?error(maybe_done, <<?solidus, Rest/binary>>, Handler, Stack, Config);
+maybe_done(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, maybe_done, [comment|Stack], Config);
+maybe_done(<<?solidus, ?star, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, maybe_done, [multicomment|Stack], Config);
+maybe_done(<<?solidus>>, Handler, Stack, Config) ->
+ incomplete(maybe_done, <<?solidus>>, Handler, Stack, Config);
+maybe_done(<<>>, Handler, Stack, Config) when length(Stack) > 0 ->
+ incomplete(maybe_done, <<>>, Handler, Stack, Config);
+maybe_done(Bin, Handler, Stack, Config) ->
+ ?error(maybe_done, Bin, Handler, Stack, Config).
+
+
+done(<<?space, Rest/binary>>, Handler, [], Config) ->
+ done(Rest, Handler, [], Config);
+done(<<?newline, Rest/binary>>, Handler, [], Config) ->
+ done(Rest, Handler, [], Config);
+done(<<?tab, Rest/binary>>, Handler, [], Config) ->
+ done(Rest, Handler, [], Config);
+done(<<?cr, Rest/binary>>, Handler, [], Config) ->
+ done(Rest, Handler, [], Config);
+done(<<?solidus, Rest/binary>>, Handler, Stack, Config=#config{strict_comments=true}) ->
+ ?error(done, <<?solidus, Rest/binary>>, Handler, Stack, Config);
+done(<<?solidus, ?solidus, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, done, [comment|Stack], Config);
+done(<<?solidus, ?star, Rest/binary>>, Handler, Stack, Config) ->
+ comment(Rest, Handler, done, [multicomment|Stack], Config);
+done(<<?solidus>>, Handler, Stack, Config) ->
+ incomplete(done, <<?solidus>>, Handler, Stack, Config);
+done(Bin, {_Handler, State}, _Stack, #config{return_tail=true}) ->
+ {with_tail,State, Bin};
+done(<<>>, {Handler, State}, [], Config=#config{stream=true}) ->
+ incomplete(done, <<>>, {Handler, State}, [], Config);
+done(<<>>, {_Handler, State}, [], _Config) -> State;
+done(Bin, {Handler, State}, _Stack, Config=#config{multi_term=true}) ->
+ value(Bin, {Handler, Handler:reset(State)}, [], Config);
+done(Bin, Handler, Stack, Config) -> ?error(done, Bin, Handler, Stack, Config).
+
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+
+json_to_bytes(JSON) -> json_to_bytes(JSON, []).
+
+json_to_bytes(<<>>, Acc) -> [<<>>] ++ lists:reverse(Acc);
+json_to_bytes(<<X, Rest/binary>>, Acc) -> json_to_bytes(Rest, [<<X>>] ++ Acc).
+
+
+decode(JSON) -> decode(JSON, []).
+decode(JSON, Config) -> (decoder(jsx, [], Config))(JSON).
+
+
+incremental_decode(JSON) -> incremental_decode(JSON, []).
+incremental_decode(JSON, Config) ->
+ Final = lists:foldl(
+ fun(Byte, Decoder) -> {incomplete, F} = Decoder(Byte), F end,
+ decoder(jsx, [], [stream] ++ Config),
+ json_to_bytes(JSON)
+ ),
+ Final(end_stream).
+
+
+%% all these numbers have different representation in erlang than in javascript and
+%% do not roundtrip like most integers/floats
+special_number_test_() ->
+ Cases = [
+ % {title, test form, json, opt flags}
+ {"-0", [{integer, 0}, end_json], <<"-0">>},
+ {"-0.0", [{float, 0.0}, end_json], <<"-0.0">>},
+ {"0e0", [{float, 0.0}, end_json], <<"0e0">>},
+ {"0e4", [{float, 0.0}, end_json], <<"0e4">>},
+ {"1e0", [{float, 1.0}, end_json], <<"1e0">>},
+ {"-1e0", [{float, -1.0}, end_json], <<"-1e0">>},
+ {"-0e0", [{float, -0.0}, end_json], <<"-0e0">>},
+ {"1e4", [{float, 1.0e4}, end_json], <<"1e4">>},
+ {"number terminated by whitespace",
+ [start_array, {integer, 1}, end_array, end_json],
+ <<"[ 1 ]">>
+ },
+ {"number terminated by comma",
+ [start_array, {integer, 1}, {integer, 1}, end_array, end_json],
+ <<"[ 1, 1 ]">>
+ },
+ {"number terminated by comma in object",
+ [start_object, {key, <<"x">>}, {integer, 1}, {key, <<"y">>}, {integer, 1}, end_object, end_json],
+ <<"{\"x\": 1, \"y\": 1}">>
+ }
+ ],
+ [{Title, ?_assertEqual(Events, decode(JSON))}
+ || {Title, Events, JSON} <- Cases
+ ] ++
+ [{Title ++ " (incremental)", ?_assertEqual(Events, incremental_decode(JSON))}
+ || {Title, Events, JSON} <- Cases
+ ].
+
+
+comments_test_() ->
+ Cases = [
+ % {title, test form, json, opt flags}
+ {"preceeding // comment",
+ [start_array, end_array, end_json],
+ <<"// comment ", ?newline, "[]">>
+ },
+ {"preceeding /**/ comment",
+ [start_array, end_array, end_json],
+ <<"/* comment */[]">>
+ },
+ {"trailing // comment",
+ [start_array, end_array, end_json],
+ <<"[]// comment", ?newline>>
+ },
+ {"trailing // comment (no newline)",
+ [start_array, end_array, end_json],
+ <<"[]// comment">>
+ },
+ {"trailing /**/ comment",
+ [start_array, end_array, end_json],
+ <<"[] /* comment */">>
+ },
+ {"// comment inside array",
+ [start_array, end_array, end_json],
+ <<"[ // comment", ?newline, "]">>
+ },
+ {"/**/ comment inside array",
+ [start_array, end_array, end_json],
+ <<"[ /* comment */ ]">>
+ },
+ {"// comment at beginning of array",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[ // comment", ?newline, "true", ?newline, "]">>
+ },
+ {"/**/ comment at beginning of array",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[ /* comment */ true ]">>
+ },
+ {"// comment at end of array",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[ true // comment", ?newline, "]">>
+ },
+ {"/**/ comment at end of array",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[ true /* comment */ ]">>
+ },
+ {"// comment midarray (post comma)",
+ [start_array, {literal, true}, {literal, false}, end_array, end_json],
+ <<"[ true, // comment", ?newline, "false ]">>
+ },
+ {"/**/ comment midarray (post comma)",
+ [start_array, {literal, true}, {literal, false}, end_array, end_json],
+ <<"[ true, /* comment */ false ]">>
+ },
+ {"// comment midarray (pre comma)",
+ [start_array, {literal, true}, {literal, false}, end_array, end_json],
+ <<"[ true// comment", ?newline, ", false ]">>
+ },
+ {"/**/ comment midarray (pre comma)",
+ [start_array, {literal, true}, {literal, false}, end_array, end_json],
+ <<"[ true/* comment */, false ]">>
+ },
+ {"// comment inside object",
+ [start_object, end_object, end_json],
+ <<"{ // comment", ?newline, "}">>
+ },
+ {"/**/ comment inside object",
+ [start_object, end_object, end_json],
+ <<"{ /* comment */ }">>
+ },
+ {"// comment at beginning of object",
+ [start_object, {key, <<"key">>}, {literal, true}, end_object, end_json],
+ <<"{ // comment", ?newline, " \"key\": true", ?newline, "}">>
+ },
+ {"/**/ comment at beginning of object",
+ [start_object, {key, <<"key">>}, {literal, true}, end_object, end_json],
+ <<"{ /* comment */ \"key\": true }">>
+ },
+ {"// comment at end of object",
+ [start_object, {key, <<"key">>}, {literal, true}, end_object, end_json],
+ <<"{ \"key\": true // comment", ?newline, "}">>
+ },
+ {"/**/ comment at end of object",
+ [start_object, {key, <<"key">>}, {literal, true}, end_object, end_json],
+ <<"{ \"key\": true /* comment */ }">>
+ },
+ {"// comment midobject (post comma)",
+ [
+ start_object,
+ {key, <<"x">>},
+ {literal, true},
+ {key, <<"y">>},
+ {literal, false},
+ end_object,
+ end_json
+ ],
+ <<"{ \"x\": true, // comment", ?newline, "\"y\": false }">>
+ },
+ {"/**/ comment midobject (post comma)",
+ [
+ start_object,
+ {key, <<"x">>},
+ {literal, true},
+ {key, <<"y">>},
+ {literal, false},
+ end_object,
+ end_json
+ ],
+ <<"{ \"x\": true, /* comment */", ?newline, "\"y\": false }">>
+ },
+ {"// comment midobject (pre comma)",
+ [
+ start_object,
+ {key, <<"x">>},
+ {literal, true},
+ {key, <<"y">>},
+ {literal, false},
+ end_object,
+ end_json
+ ],
+ <<"{ \"x\": true// comment", ?newline, ", \"y\": false }">>
+ },
+ {"/**/ comment midobject (pre comma)",
+ [
+ start_object,
+ {key, <<"x">>},
+ {literal, true},
+ {key, <<"y">>},
+ {literal, false},
+ end_object,
+ end_json
+ ],
+ <<"{ \"x\": true/* comment */", ?newline, ", \"y\": false }">>
+ },
+ {"// comment precolon",
+ [start_object, {key, <<"key">>}, {literal, true}, end_object, end_json],
+ <<"{ \"key\" // comment", ?newline, ": true }">>
+ },
+ {"/**/ comment precolon",
+ [start_object, {key, <<"key">>}, {literal, true}, end_object, end_json],
+ <<"{ \"key\"/* comment */: true }">>
+ },
+ {"// comment postcolon",
+ [start_object, {key, <<"key">>}, {literal, true}, end_object, end_json],
+ <<"{ \"key\": // comment", ?newline, " true }">>
+ },
+ {"/**/ comment postcolon",
+ [start_object, {key, <<"key">>}, {literal, true}, end_object, end_json],
+ <<"{ \"key\":/* comment */ true }">>
+ },
+ {"// comment terminating zero",
+ [start_array, {integer, 0}, end_array, end_json],
+ <<"[ 0// comment", ?newline, "]">>
+ },
+ {"// comment terminating integer",
+ [start_array, {integer, 1}, end_array, end_json],
+ <<"[ 1// comment", ?newline, "]">>
+ },
+ {"// comment terminating float",
+ [start_array, {float, 1.0}, end_array, end_json],
+ <<"[ 1.0// comment", ?newline, "]">>
+ },
+ {"// comment terminating exp",
+ [start_array, {float, 1.0e1}, end_array, end_json],
+ <<"[ 1e1// comment", ?newline, "]">>
+ },
+ {"/**/ comment terminating zero",
+ [start_array, {integer, 0}, end_array, end_json],
+ <<"[ 0/* comment */ ]">>
+ },
+ {"/**/ comment terminating integer",
+ [start_array, {integer, 1}, end_array, end_json],
+ <<"[ 1/* comment */ ]">>
+ },
+ {"/**/ comment terminating float",
+ [start_array, {float, 1.0}, end_array, end_json],
+ <<"[ 1.0/* comment */ ]">>
+ },
+ {"/**/ comment terminating exp",
+ [start_array, {float, 1.0e1}, end_array, end_json],
+ <<"[ 1e1/* comment */ ]">>
+ },
+ {"/**/ comment following /**/ comment",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[/* comment *//* comment */true]">>
+ },
+ {"/**/ comment following // comment",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[// comment", ?newline, "/* comment */true]">>
+ },
+ {"// comment following /**/ comment",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[/* comment */// comment", ?newline, "true]">>
+ },
+ {"// comment following // comment",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[// comment", ?newline, "// comment", ?newline, "true]">>
+ },
+ {"/**/ comment inside /**/ comment",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[ /* /* comment */ */ true ]">>
+ },
+ {"/**/ comment with /",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[ /* / */ true ]">>
+ },
+ {"/**/ comment with *",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[ /* * */ true ]">>
+ },
+ {"// comment with badutf",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[ // comment ", 16#00c0, " ", ?newline, "true]">>
+ },
+ {"/**/ comment with badutf",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[ /* comment ", 16#00c0, " */ true]">>
+ },
+ {"/**/ comment with badutf preceeded by /",
+ [start_array, {literal, true}, end_array, end_json],
+ <<"[ /* comment /", 16#00c0, " */ true]">>
+ }
+ ],
+ [{Title, ?_assertEqual(Events, decode(JSON))}
+ || {Title, Events, JSON} <- Cases
+ ] ++
+ [{Title ++ " (incremental)", ?_assertEqual(Events, incremental_decode(JSON))}
+ || {Title, Events, JSON} <- Cases
+ ] ++
+ % error when `{strict, [comments]}` is present
+ [{Title, ?_assertError(badarg, decode(JSON, [{strict, [comments]}]))}
+ || {Title, _Events, JSON} <- Cases
+ ] ++
+ [{Title ++ " (incremental)", ?_assertError(
+ badarg,
+ incremental_decode(JSON, [{strict, [comments]}])
+ )} || {Title, _Events, JSON} <- Cases
+ ].
+
+
+no_comments_test_() ->
+ Cases = [
+ {"// comment with badutf",
+ badarg,
+ <<"[ // comment ", 16#00c0, " ", ?newline, "true]">>,
+ [{strict, [utf8]}]
+ },
+ {"/**/ comment with badutf",
+ badarg,
+ <<"[ /* comment ", 16#00c0, " */ true]">>,
+ [{strict, [utf8]}]
+ },
+ {"/**/ comment with badutf preceeded by /",
+ badarg,
+ <<"[ /* comment /", 16#00c0, " */ true]">>,
+ [{strict, [utf8]}]
+ }
+ ],
+ [{Title, ?_assertError(Error, decode(JSON, Config))}
+ || {Title, Error, JSON, Config} <- Cases
+ ] ++
+ [{Title ++ " (incremental)", ?_assertError(Error, incremental_decode(JSON, Config))}
+ || {Title, Error, JSON, Config} <- Cases
+ ].
+
+
+% doing the full unicode range takes foreverrrrrrr so just do boundaries
+% excludes characters that may need escaping
+codepoints() ->
+ lists:seq(0, 32) ++
+ [32, 33] ++
+ lists:seq(35, 46) ++
+ lists:seq(48, 91) ++
+ lists:seq(93, 127) ++
+ [16#2027, 16#202a, 16#d7ff, 16#e000] ++
+ lists:seq(16#fdd0, 16#ffff) ++
+ [16#10000, 16#20000, 16#30000, 16#40000, 16#50000] ++
+ [16#60000, 16#70000, 16#80000, 16#90000, 16#a0000, 16#b0000] ++
+ [16#c0000, 16#d0000, 16#e0000, 16#f0000, 16#100000].
+
+
+surrogates() -> lists:seq(16#d800, 16#dfff).
+
+
+%% erlang refuses to decode certain codepoints, so fake them all
+to_fake_utf8(N) when N < 16#0080 -> <<34/utf8, N:8, 34/utf8>>;
+to_fake_utf8(N) when N < 16#0800 ->
+ <<0:5, Y:5, X:6>> = <<N:16>>,
+ <<34/utf8, 2#110:3, Y:5, 2#10:2, X:6, 34/utf8>>;
+to_fake_utf8(N) when N < 16#10000 ->
+ <<Z:4, Y:6, X:6>> = <<N:16>>,
+ <<34/utf8, 2#1110:4, Z:4, 2#10:2, Y:6, 2#10:2, X:6, 34/utf8>>;
+to_fake_utf8(N) ->
+ <<0:3, W:3, Z:6, Y:6, X:6>> = <<N:24>>,
+ <<34/utf8, 2#11110:5, W:3, 2#10:2, Z:6, 2#10:2, Y:6, 2#10:2, X:6, 34/utf8>>.
+
+
+clean_string_test_() ->
+ Clean = codepoints(),
+ Dirty = surrogates(),
+ % clean codepoints
+ [{"clean u+" ++ integer_to_list(Codepoint, 16), ?_assertEqual(
+ [{string, <<Codepoint/utf8>>}, end_json],
+ decode(<<34/utf8, Codepoint/utf8, 34/utf8>>)
+ )} || Codepoint <- Clean
+ ] ++
+ % bad codepoints replaced by u+FFFD
+ [{"clean u+" ++ integer_to_list(Codepoint, 16), ?_assertEqual(
+ [{string, <<16#fffd/utf8>>}, end_json],
+ decode(to_fake_utf8(Codepoint))
+ )} || Codepoint <- Dirty
+ ] ++
+ % bad codepoints that cause errors
+ [{"dirty u+" ++ integer_to_list(Codepoint, 16), ?_assertError(
+ badarg,
+ decode(to_fake_utf8(Codepoint), [{strict, [utf8]}])
+ )} || Codepoint <- Dirty
+ ].
+
+
+dirty_string_test_() ->
+ Cases = [
+ {"dirty \\n",
+ [start_array, {string, <<"\\n">>}, end_array, end_json],
+ <<"[\"\\n\"]">>,
+ [dirty_strings]
+ },
+ {"dirty \\uwxyz",
+ [start_array, {string, <<"\\uwxyz">>}, end_array, end_json],
+ <<"[\"\\uwxyz\"]">>,
+ [dirty_strings]
+ },
+ {"dirty \\x23",
+ [start_array, {string, <<"\\x23">>}, end_array, end_json],
+ <<"[\"\\x23\"]">>,
+ [dirty_strings]
+ },
+ {"dirty 0",
+ [start_array, {string, <<0>>}, end_array, end_json],
+ <<"[\"", 0, "\"]">>,
+ [dirty_strings]
+ },
+ {"dirty 0\\\"0",
+ [start_array, {string, <<0, ?rsolidus, ?doublequote, 0>>}, end_array, end_json],
+ <<"[\"", 0, ?rsolidus, ?doublequote, 0, "\"]">>,
+ [dirty_strings]
+ },
+ {"dirty 0\\\\\"0",
+ [start_array, {string, <<0, ?rsolidus, ?rsolidus, ?doublequote, 0>>}, end_array, end_json],
+ <<"[\"", 0, ?rsolidus, ?rsolidus, ?doublequote, 0, "\"]">>,
+ [dirty_strings]
+ },
+ {"dirty 16#d800",
+ [start_array, {string, <<237, 160, 128>>}, end_array, end_json],
+ <<"[\"", 237, 160, 128, "\"]">>,
+ [dirty_strings]
+ },
+ {"dirty /",
+ [start_array, {string, <<$/>>}, end_array, end_json],
+ <<"[\"", $/, "\"]">>,
+ [dirty_strings, escaped_forward_slashes]
+ },
+ {"dirty <<194, 129>>",
+ [start_array, {string, <<194, 129>>}, end_array, end_json],
+ <<"[\"", 194, 129, "\"]">>,
+ [dirty_strings]
+ }
+ ],
+ [{Title, ?_assertEqual(Events, decode(JSON, Config))}
+ || {Title, Events, JSON, Config} <- Cases
+ ] ++
+ % ensure `dirty_strings` and `strict` interact properly
+ [{Title, ?_assertEqual(Events, decode(JSON, Config ++ [strict]))}
+ || {Title, Events, JSON, Config} <- Cases
+ ] ++
+ [{Title ++ " (incremental)", ?_assertEqual(Events, incremental_decode(JSON, Config))}
+ || {Title, Events, JSON, Config} <- Cases
+ ].
+
+
+bad_utf8_test_() ->
+ Cases = [
+ {"orphan continuation byte u+0080", <<16#fffd/utf8>>, <<16#0080>>},
+ {"orphan continuation byte u+00bf", <<16#fffd/utf8>>, <<16#00bf>>},
+ {"2 continuation bytes",
+ binary:copy(<<16#fffd/utf8>>, 2),
+ <<(binary:copy(<<16#0080>>, 2))/binary>>
+ },
+ {"3 continuation bytes",
+ binary:copy(<<16#fffd/utf8>>, 3),
+ <<(binary:copy(<<16#0080>>, 3))/binary>>
+ },
+ {"4 continuation bytes",
+ binary:copy(<<16#fffd/utf8>>, 4),
+ <<(binary:copy(<<16#0080>>, 4))/binary>>
+ },
+ {"5 continuation bytes",
+ binary:copy(<<16#fffd/utf8>>, 5),
+ <<(binary:copy(<<16#0080>>, 5))/binary>>
+ },
+ {"6 continuation bytes",
+ binary:copy(<<16#fffd/utf8>>, 6),
+ <<(binary:copy(<<16#0080>>, 6))/binary>>
+ },
+ {"all continuation bytes",
+ binary:copy(<<16#fffd/utf8>>, length(lists:seq(16#0080, 16#00bf))),
+ <<(list_to_binary(lists:seq(16#0080, 16#00bf)))/binary>>
+ },
+ {"lonely start byte", <<16#fffd/utf8>>, <<16#00c0>>},
+ {"lonely start bytes (2 byte)",
+ <<16#fffd/utf8, 32, 16#fffd/utf8>>,
+ <<16#00c0, 32, 16#00df>>
+ },
+ {"lonely start bytes (3 byte)",
+ <<16#fffd/utf8, 32, 16#fffd/utf8>>,
+ <<16#00e0, 32, 16#00ef>>
+ },
+ {"lonely start bytes (4 byte)",
+ <<16#fffd/utf8, 32, 16#fffd/utf8>>,
+ <<16#00f0, 32, 16#00f7>>
+ },
+ {"missing continuation byte (3 byte)", <<16#fffd/utf8, 32>>, <<224, 160, 32>>},
+ {"missing continuation byte (4 byte missing one)",
+ <<16#fffd/utf8, 32>>,
+ <<240, 144, 128, 32>>
+ },
+ {"missing continuation byte (4 byte missing two)",
+ <<16#fffd/utf8, 32>>,
+ <<240, 144, 32>>
+ },
+ {"overlong encoding of u+002f (2 byte)",
+ <<16#fffd/utf8, 32>>,
+ <<16#c0, 16#af, 32>>
+ },
+ {"overlong encoding of u+002f (3 byte)",
+ <<16#fffd/utf8, 32>>,
+ <<16#e0, 16#80, 16#af, 32>>
+ },
+ {"overlong encoding of u+002f (4 byte)",
+ <<16#fffd/utf8, 32>>,
+ <<16#f0, 16#80, 16#80, 16#af, 32>>
+ },
+ {"highest overlong 2 byte sequence",
+ <<16#fffd/utf8, 32>>,
+ <<16#c1, 16#bf, 32>>
+ },
+ {"highest overlong 3 byte sequence",
+ <<16#fffd/utf8, 32>>,
+ <<16#e0, 16#9f, 16#bf, 32>>
+ },
+ {"highest overlong 4 byte sequence",
+ <<16#fffd/utf8, 32>>,
+ <<16#f0, 16#8f, 16#bf, 16#bf, 32>>
+ }
+ ],
+ [{Title, ?_assertError(
+ badarg,
+ decode(<<34, JSON/binary, 34>>, [{strict, [utf8]}])
+ )} || {Title, _, JSON} <- Cases
+ ] ++
+ [{Title ++ " (incremental)", ?_assertError(
+ badarg,
+ incremental_decode(<<34, JSON/binary, 34>>, [{strict, [utf8]}])
+ )} || {Title, _, JSON} <- Cases
+ ] ++
+ [{Title ++ " replaced", ?_assertEqual(
+ [{string, Replacement}, end_json],
+ decode(<<34, JSON/binary, 34>>)
+ )} || {Title, Replacement, JSON} <- Cases
+ ] ++
+ [{Title ++ " replaced (incremental)", ?_assertEqual(
+ [{string, Replacement}, end_json],
+ incremental_decode(<<34, JSON/binary, 34>>)
+ )} || {Title, Replacement, JSON} <- Cases
+ ].
+
+
+unescape_test_() ->
+ Cases = [
+ {"unescape backspace", <<"\b">>, <<"\\b"/utf8>>},
+ {"unescape tab", <<"\t">>, <<"\\t"/utf8>>},
+ {"unescape newline", <<"\n">>, <<"\\n"/utf8>>},
+ {"unescape formfeed", <<"\f">>, <<"\\f"/utf8>>},
+ {"unescape carriage return", <<"\r">>, <<"\\r"/utf8>>},
+ {"unescape quote", <<"\"">>, <<"\\\""/utf8>>},
+ {"unescape solidus", <<"/">>, <<"\\/"/utf8>>},
+ {"unescape reverse solidus", <<"\\">>, <<"\\\\"/utf8>>},
+ {"unescape control", <<0>>, <<"\\u0000"/utf8>>},
+ {"unescape surrogate pair", <<16#10000/utf8>>, <<"\\ud800\\udc00"/utf8>>},
+ {"unescape surrogate pair", <<16#10000/utf8>>, <<"\\uD800\\uDC00"/utf8>>},
+ {"replace bad high surrogate", <<16#fffd/utf8>>, <<"\\udc00"/utf8>>},
+ {"replace bad high surrogate", <<16#fffd/utf8>>, <<"\\uDC00"/utf8>>},
+ {"replace naked high surrogate",
+ <<16#fffd/utf8, "hello world">>,
+ <<"\\ud800hello world"/utf8>>
+ },
+ {"replace naked high surrogate",
+ <<16#fffd/utf8, "hello world">>,
+ <<"\\uD800hello world"/utf8>>
+ },
+ {"replace naked low surrogate",
+ <<16#fffd/utf8, "hello world">>,
+ <<"\\udc00hello world"/utf8>>
+ },
+ {"replace naked low surrogate",
+ <<16#fffd/utf8, "hello world">>,
+ <<"\\uDC00hello world"/utf8>>
+ },
+ {"replace bad surrogate pair", <<16#fffd/utf8, 16#fffd/utf8>>, <<"\\ud800\\u0000">>},
+ {"replace bad surrogate pair", <<16#fffd/utf8, 16#fffd/utf8>>, <<"\\uD800\\u0000">>}
+ ],
+ [{Title, ?_assertEqual([{string, Escaped}, end_json], decode(<<34, JSON/binary, 34>>))}
+ || {Title, Escaped, JSON} <- Cases
+ ] ++
+ [{Title ++ " (incremental)", ?_assertEqual(
+ [{string, Escaped}, end_json],
+ incremental_decode(<<34, JSON/binary, 34>>)
+ )} || {Title, Escaped, JSON} <- Cases
+ ].
+
+
+bad_escaped_surrogate_test_() ->
+ Cases = [
+ {"do not unescape bad high surrogate", <<"\\udc00">>},
+ {"do not unescape naked high surrogate", <<"\\ud800hello world">>},
+ {"do not unescape naked low surrogate", <<"\\udc00hello world">>},
+ {"do not unescape bad surrogate pair", <<"\\ud800\\u0000">>}
+ ],
+ [{Title, ?_assertError(badarg, decode(<<34, JSON/binary, 34>>, [{strict, [utf8]}]))}
+ || {Title, JSON} <- Cases
+ ].
+
+
+escape_test_() ->
+ Cases = [
+ {"backspace", <<"\b">>, <<"\\b">>},
+ {"tab", <<"\t">>, <<"\\t">>},
+ {"newline", <<"\n">>, <<"\\n">>},
+ {"formfeed", <<"\f">>, <<"\\f">>},
+ {"carriage return", <<"\r">>, <<"\\r">>},
+ {"quote", <<"\"">>, <<"\\\"">>},
+ {"backslash", <<"\\">>, <<"\\\\">>},
+ {"control", <<0>>, <<"\\u0000">>}
+ ],
+ [{"escape " ++ Title, ?_assertEqual(
+ [{string, Escaped}, end_json],
+ decode(<<34, Escaped/binary, 34>>, [escaped_strings])
+ )} || {Title, _Unescaped, Escaped} <- Cases
+ ] ++
+ [{"do not escape " ++ Title, ?_assertEqual(
+ [{string, Unescaped}, end_json],
+ decode(<<34, Escaped/binary, 34>>)
+ )} || {Title, Unescaped, Escaped} <- Cases
+ ].
+
+
+special_escape_test_() ->
+ Cases = [
+ {"escape forward slash", <<"\\/">>, <<"/"/utf8>>, [escaped_forward_slashes]},
+ {"do not escape forward slash", <<"/">>, <<"/"/utf8>>, []},
+ {"escape jsonp", <<"\\u2028">>, <<16#2028/utf8>>, []},
+ {"do not escape jsonp", <<16#2028/utf8>>, <<16#2028/utf8>>, [unescaped_jsonp]}
+ ],
+ [{Title, ?_assertEqual(
+ [{string, Expect}, end_json],
+ decode(<<34, Raw/binary, 34>>, [escaped_strings] ++ Config)
+ )} || {Title, Expect, Raw, Config} <- Cases
+ ].
+
+
+uescape_test_() ->
+ [
+ {"\"\\u0080\"", ?_assertEqual(
+ [{string, <<"\\u0080">>}, end_json],
+ decode(<<34, 128/utf8, 34>>, [uescape])
+ )},
+ {"\"\\u8ca8\\u5481\\u3002\\u0091\\u0091\"", ?_assertEqual(
+ [{string, <<"\\u8ca8\\u5481\\u3002\\u0091\\u0091">>}, end_json],
+ decode(
+ <<34,232,178,168,229,146,129,227,128,130,194,145,194,145,34>>,
+ [uescape]
+ )
+ )},
+ {"\"\\ud834\\udd1e\"", ?_assertEqual(
+ [{string, <<"\\ud834\\udd1e">>}, end_json],
+ decode(<<34, 240, 157, 132, 158, 34>>, [uescape])
+ )},
+ {"\"\\ud83d\\ude0a\"", ?_assertEqual(
+ [{string, <<"\\ud83d\\ude0a">>}, end_json],
+ decode(<<34, 240, 159, 152, 138, 34>>, [uescape])
+ )}
+ ].
+
+
+single_quoted_string_test_() ->
+ Cases = [
+ {"single quoted string", [{string, <<"hello world">>}, end_json], <<39, "hello world", 39>>},
+ {"single quoted string with embedded double quotes",
+ [{string, <<"quoth the raven, \"nevermore\"">>}, end_json],
+ <<39, "quoth the raven, \"nevermore\"", 39>>
+ },
+ {"escaped single quote",
+ [{string, <<"quoth the raven, 'nevermore'">>}, end_json],
+ <<39, "quoth the raven, \\'nevermore\\'", 39>>
+ },
+ {"single quoted key",
+ [start_object,
+ {key, <<"key">>}, {string, <<"value">>},
+ {key, <<"another key">>}, {string, <<"another value">>},
+ end_object, end_json],
+ <<"{'key':'value','another key':'another value'}">>
+ }
+ ],
+ [{Title, ?_assertEqual(Expect, decode(Raw, []))} || {Title, Expect, Raw} <- Cases] ++
+ [{Title, ?_assertError(
+ badarg,
+ decode(Raw, [{strict, [single_quotes]}])
+ )} || {Title, _Expect, Raw} <- Cases
+ ].
+
+
+embedded_single_quoted_string_test_() ->
+ [
+ {"string with embedded single quotes", ?_assertEqual(
+ [{string, <<"quoth the raven, 'nevermore'">>}, end_json],
+ decode(<<34, "quoth the raven, 'nevermore'", 34>>, [])
+ )},
+ {"string with embedded single quotes", ?_assertEqual(
+ [{string, <<"quoth the raven, 'nevermore'">>}, end_json],
+ decode(<<34, "quoth the raven, 'nevermore'", 34>>, [{strict, [single_quotes]}])
+ )}
+ ].
+
+
+ignored_bad_escapes_test_() ->
+ [
+ {"ignore unrecognized escape sequence", ?_assertEqual(
+ [{string, <<"\\x25">>}, end_json],
+ decode(<<"\"\\x25\"">>, [])
+ )}
+ ].
+
+
+bom_test_() ->
+ [
+ {"bom", ?_assertEqual(
+ [start_array, end_array, end_json],
+ decode(<<16#ef, 16#bb, 16#bf, "[]"/utf8>>, [])
+ )}
+ ].
+
+
+trailing_comma_test_() ->
+ [
+ {"trailing comma in object", ?_assertEqual(
+ [start_object, {key, <<"key">>}, {literal, true}, end_object, end_json],
+ decode(<<"{\"key\": true,}">>, [])
+ )},
+ {"strict trailing comma in object", ?_assertError(
+ badarg,
+ decode(<<"{\"key\": true,}">>, [{strict, [trailing_commas]}])
+ )},
+ {"two trailing commas in object", ?_assertError(
+ badarg,
+ decode(<<"{\"key\": true,,}">>, [])
+ )},
+ {"comma in empty object", ?_assertError(
+ badarg,
+ decode(<<"{,}">>, [])
+ )},
+ {"trailing comma in list", ?_assertEqual(
+ [start_array, {literal, true}, end_array, end_json],
+ decode(<<"[true,]">>, [])
+ )},
+ {"strict trailing comma in list", ?_assertError(
+ badarg,
+ decode(<<"[true,]">>, [{strict, [trailing_commas]}])
+ )},
+ {"two trailing commas in list", ?_assertError(
+ badarg,
+ decode(<<"[true,,]">>, [])
+ )},
+ {"comma in empty list", ?_assertError(
+ badarg,
+ decode(<<"[,]">>, [])
+ )}
+ ].
+
+
+incomplete_test_() ->
+ [
+ {"stream false", ?_assertError(
+ badarg,
+ decode(<<"{">>)
+ )},
+ {"stream true", ?_assertMatch(
+ {incomplete, _},
+ decode(<<"{">>, [stream])
+ )},
+ {"complete input", ?_assertMatch(
+ {incomplete, _},
+ decode(<<"{}">>, [stream])
+ )}
+ ].
+
+
+error_test_() ->
+ Cases = [
+ {"maybe_bom error", <<16#ef, 0>>},
+ {"definitely_bom error", <<16#ef, 16#bb, 0>>},
+ {"object error", <<"{"/utf8, 0>>},
+ {"colon error", <<"{\"\""/utf8, 0>>},
+ {"key error", <<"{\"\":1,"/utf8, 0>>},
+ {"value error", <<0>>},
+ {"negative error", <<"-"/utf8, 0>>},
+ {"zero error", <<"0"/utf8, 0>>},
+ {"integer error", <<"1"/utf8, 0>>},
+ {"decimal error", <<"1.0"/utf8, 0>>},
+ {"e error", <<"1e"/utf8, 0>>},
+ {"ex error", <<"1e+"/utf8, 0>>},
+ {"exp error", <<"1e1"/utf8, 0>>},
+ {"exp error", <<"1.0e1"/utf8, 0>>},
+ {"exp error", <<"1.e"/utf8>>},
+ {"true error", <<"tru"/utf8, 0>>},
+ {"false error", <<"fals"/utf8, 0>>},
+ {"null error", <<"nul"/utf8, 0>>},
+ {"maybe_done error", <<"[[]"/utf8, 0>>},
+ {"done error", <<"[]"/utf8, 0>>}
+ ],
+ [{Title, ?_assertError(badarg, decode(State))} || {Title, State} <- Cases].
+
+
+custom_incomplete_handler_test_() ->
+ [
+ {"custom incomplete handler", ?_assertError(
+ incomplete,
+ decode(<<>>, [{incomplete_handler, fun(_, _, _) -> erlang:error(incomplete) end}, stream])
+ )}
+ ].
+
+
+return_tail_test_() ->
+ [
+ {"return_tail with tail", ?_assertEqual(
+ {with_tail,#{},<<"3">>},
+ jsx:decode(<<"{} 3">>, [return_tail])
+ )},
+ {"return_tail without tail", ?_assertEqual(
+ {with_tail,#{},<<"">>},
+ jsx:decode(<<"{}">>, [return_tail])
+ )},
+ {"return_tail with trimmed whitespace", ?_assertEqual(
+ {with_tail,#{},<<"">>},
+ jsx:decode(<<"{} ">>, [return_tail])
+ )},
+ {"return_tail and streaming", ?_assertEqual(
+ {with_tail,#{},<<"3">>},
+ begin
+ {incomplete, F} = jsx:decode(<<"{">>, [return_tail, stream]),
+ F(<<"} 3">>)
+ end
+ )},
+ {"return_tail and streaming", ?_assertEqual(
+ {with_tail,#{},<<"">>},
+ begin
+ %% In case of infinite stream of objects a user does not know
+ %% when to call F(end_stream).
+ %% So, return_tail overwrites conservative stream end.
+ %% This means that we don't need to call end_stream explicitly.
+ {incomplete, F} = jsx:decode(<<"{">>, [return_tail, stream]),
+ F(<<"}">>)
+ end
+ )}
+ ].
+
+-endif.
diff --git a/server/_build/default/lib/jsx/src/jsx_encoder.erl b/server/_build/default/lib/jsx/src/jsx_encoder.erl
new file mode 100644
index 0000000..a1242a7
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx_encoder.erl
@@ -0,0 +1,116 @@
+%% The MIT License
+
+%% Copyright (c) 2010-2013 Alisdair Sullivan <alisdairsullivan@yahoo.ca>
+
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+
+
+-module(jsx_encoder).
+
+-export([encoder/3, encode/1, encode/2]).
+
+-spec encoder(Handler::module(), State::any(), Config::jsx_config:options()) -> jsx:encoder().
+
+encoder(Handler, State, Config) ->
+ Parser = jsx:parser(Handler, State, Config),
+ fun(Term) -> Parser(encode(Term) ++ [end_json]) end.
+
+
+-spec encode(Term::any()) -> [any(), ...].
+
+encode(Term) -> encode(Term, ?MODULE).
+
+
+-spec encode(Term::any(), EntryPoint::module()) -> [any(), ...].
+
+encode(Map, _EntryPoint) when is_map(Map), map_size(Map) < 1 ->
+ [start_object, end_object];
+encode(Term, EntryPoint) when is_map(Term) ->
+ [start_object] ++ unpack(Term, EntryPoint);
+encode(Term, EntryPoint) -> encode_(Term, EntryPoint).
+
+encode_([], _EntryPoint) -> [start_array, end_array];
+encode_([{}], _EntryPoint) -> [start_object, end_object];
+
+%% datetime special case
+encode_([{{_,_,_},{_,_,_}} = DateTime|Rest], EntryPoint) ->
+ [start_array] ++ [DateTime] ++ unhitch(Rest, EntryPoint);
+encode_([{_, _}|_] = Term, EntryPoint) ->
+ [start_object] ++ unzip(Term, EntryPoint);
+encode_(Term, EntryPoint) when is_list(Term) ->
+ [start_array] ++ unhitch(Term, EntryPoint);
+
+encode_(Else, _EntryPoint) -> [Else].
+
+
+unzip([{K, V}|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) ->
+ [K] ++ EntryPoint:encode(V, EntryPoint) ++ unzip(Rest, EntryPoint);
+unzip([], _) -> [end_object];
+unzip(_, _) -> erlang:error(badarg).
+
+
+unhitch([V|Rest], EntryPoint) ->
+ EntryPoint:encode(V, EntryPoint) ++ unhitch(Rest, EntryPoint);
+unhitch([], _) -> [end_array].
+
+unpack(Map, EntryPoint) -> unpack(Map, maps:keys(Map), EntryPoint).
+
+unpack(Map, [K|Rest], EntryPoint) when is_integer(K); is_binary(K); is_atom(K) ->
+ [K] ++ EntryPoint:encode(maps:get(K, Map), EntryPoint) ++ unpack(Map, Rest, EntryPoint);
+unpack(_, [], _) -> [end_object].
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+
+parser(Term, Opts) -> (jsx:parser(jsx, [], Opts))(Term).
+
+
+error_test_() ->
+ [
+ {"value error", ?_assertError(badarg, parser(self(), []))},
+ {"string error", ?_assertError(badarg, parser(<<239, 191, 191>>, [strict]))}
+ ].
+
+custom_error_handler_test_() ->
+ Error = fun(Term, {_, State, _, _}, _) -> {State, Term} end,
+ [
+ {"value error", ?_assertEqual(
+ {value, [self()]},
+ parser(self(), [{error_handler, Error}])
+ )},
+ {"string error", ?_assertEqual(
+ {value, [{string, <<237, 160, 128>>}]},
+ parser(<<237, 160, 128>>, [{error_handler, Error}, strict])
+ )}
+ ].
+
+improper_lists_test_() ->
+ [
+ {"improper proplist", ?_assertError(
+ badarg,
+ encode([{<<"key">>, <<"value">>}, false])
+ )},
+ {"improper list", ?_assertError(
+ badarg,
+ encode([{literal, true}, false, null])
+ )}
+ ].
+
+-endif.
diff --git a/server/_build/default/lib/jsx/src/jsx_parser.erl b/server/_build/default/lib/jsx/src/jsx_parser.erl
new file mode 100644
index 0000000..8506b03
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx_parser.erl
@@ -0,0 +1,1214 @@
+%% The MIT License
+
+%% Copyright (c) 2010-2013 Alisdair Sullivan <alisdairsullivan@yahoo.ca>
+
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+
+
+-module(jsx_parser).
+
+-export([parser/3, resume/5]).
+-export([init/1, handle_event/2]).
+
+
+-spec parser(Handler::module(), State::any(), Config::jsx_config:options()) -> jsx:parser().
+
+parser(Handler, State, Config) ->
+ fun(Tokens) -> value(Tokens, {Handler, Handler:init(State)}, [], jsx_config:parse_config(Config)) end.
+
+
+%% resume allows continuation from interrupted decoding without having to explicitly export
+%% all states
+-spec resume(
+ Rest::jsx:token(),
+ State::atom(),
+ Handler::module(),
+ Stack::list(atom()),
+ Config::jsx:config()
+ ) -> jsx:parser() | {incomplete, jsx:parser()}.
+
+resume(Rest, State, Handler, Stack, Config) ->
+ case State of
+ value -> value(Rest, Handler, Stack, Config);
+ object -> object(Rest, Handler, Stack, Config);
+ array -> array(Rest, Handler, Stack, Config);
+ maybe_done -> maybe_done(Rest, Handler, Stack, Config);
+ done -> done(Rest, Handler, Stack, Config)
+ end.
+
+
+-include("jsx_config.hrl").
+
+
+%% error, incomplete and event macros
+-ifndef(error).
+-define(error(State, Terms, Handler, Stack, Config),
+ case Config#config.error_handler of
+ false -> erlang:error(badarg);
+ F -> F(Terms, {parser, State, Handler, Stack}, jsx_config:config_to_list(Config))
+ end
+
+).
+-endif.
+
+
+incomplete(State, Handler, Stack, Config=#config{stream=false}) ->
+ ?error(State, [], Handler, Stack, Config);
+incomplete(State, Handler, Stack, Config=#config{incomplete_handler=false}) ->
+ {incomplete, fun(End) when End == end_stream; End == end_json ->
+ case resume([end_json], State, Handler, Stack, Config) of
+ {incomplete, _} -> ?error(State, [], Handler, Stack, Config);
+ Else -> Else
+ end;
+ (Tokens) ->
+ resume(Tokens, State, Handler, Stack, Config)
+ end
+ };
+incomplete(State, Handler, Stack, Config=#config{incomplete_handler=F}) ->
+ F([], {parser, State, Handler, Stack}, jsx_config:config_to_list(Config)).
+
+
+handle_event(Event, {Handler, State}, _Config) -> {Handler, Handler:handle_event(Event, State)}.
+
+
+value([String|Tokens], Handler, Stack, Config) when is_binary(String) ->
+ try clean_string(String, Config) of Clean ->
+ maybe_done(Tokens, handle_event({string, Clean}, Handler, Config), Stack, Config)
+ catch error:badarg ->
+ ?error(value, [{string, String}|Tokens], Handler, Stack, Config)
+ end;
+value([true|Tokens], Handler, Stack, Config) ->
+ maybe_done(Tokens, handle_event({literal, true}, Handler, Config), Stack, Config);
+value([false|Tokens], Handler, Stack, Config) ->
+ maybe_done(Tokens, handle_event({literal, false}, Handler, Config), Stack, Config);
+value([null|Tokens], Handler, Stack, Config) ->
+ maybe_done(Tokens, handle_event({literal, null}, Handler, Config), Stack, Config);
+value([start_object|Tokens], Handler, Stack, Config) ->
+ object(Tokens, handle_event(start_object, Handler, Config), [object|Stack], Config);
+value([start_array|Tokens], Handler, Stack, Config) ->
+ array(Tokens, handle_event(start_array, Handler, Config), [array|Stack], Config);
+value([Number|Tokens], Handler, Stack, Config) when is_integer(Number) ->
+ maybe_done(Tokens, handle_event({integer, Number}, Handler, Config), Stack, Config);
+value([Number|Tokens], Handler, Stack, Config) when is_float(Number) ->
+ maybe_done(Tokens, handle_event({float, Number}, Handler, Config), Stack, Config);
+value([{raw, Raw}|Tokens], Handler, Stack, Config) when is_binary(Raw) ->
+ value((jsx:decoder(?MODULE, [], []))(Raw) ++ Tokens, Handler, Stack, Config);
+value([{_,_,_}=Timestamp|Tokens], Handler, Stack, Config) ->
+ {{Year, Month, Day}, {Hour, Min, Sec}} = calendar:now_to_datetime(
+ Timestamp),
+ value([{string, unicode:characters_to_binary(io_lib:format(
+ "~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0BZ",
+ [Year, Month, Day, Hour, Min, Sec]
+ ))}|Tokens],
+ Handler,
+ Stack,
+ Config
+ );
+value([{{Year, Month, Day}, {Hour, Min, Sec}}|Tokens], Handler, Stack, Config)
+when is_integer(Year), is_integer(Month), is_integer(Day), is_integer(Hour), is_integer(Min), is_integer(Sec) ->
+ value([{string, unicode:characters_to_binary(io_lib:format(
+ "~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~2.10.0BZ",
+ [Year, Month, Day, Hour, Min, Sec]
+ ))}|Tokens],
+ Handler,
+ Stack,
+ Config
+ );
+value([{{Year, Month, Day}, {Hour, Min, Sec}}|Tokens], Handler, Stack, Config)
+when is_integer(Year), is_integer(Month), is_integer(Day), is_integer(Hour), is_integer(Min), is_float(Sec) ->
+ value([{string, unicode:characters_to_binary(io_lib:format(
+ "~4.10.0B-~2.10.0B-~2.10.0BT~2.10.0B:~2.10.0B:~9.6.0fZ",
+ [Year, Month, Day, Hour, Min, Sec]
+ ))}|Tokens],
+ Handler,
+ Stack,
+ Config
+ );
+value([{literal, Value}|Tokens], Handler, Stack, Config)
+when Value == true; Value == false; Value == null ->
+ value([Value] ++ Tokens, Handler, Stack, Config);
+value([{integer, Value}|Tokens], Handler, Stack, Config)
+when is_integer(Value) ->
+ value([Value] ++ Tokens, Handler, Stack, Config);
+value([{float, Value}|Tokens], Handler, Stack, Config)
+when is_float(Value) ->
+ value([Value] ++ Tokens, Handler, Stack, Config);
+value([{string, Value}|Tokens], Handler, Stack, Config)
+when is_binary(Value); is_atom(Value) ->
+ value([Value] ++ Tokens, Handler, Stack, Config);
+value([{number, Value}|Tokens], Handler, Stack, Config)
+when is_float(Value); is_integer(Value) ->
+ value([Value] ++ Tokens, Handler, Stack, Config);
+value([String|Tokens], Handler, Stack, Config) when is_atom(String) ->
+ value([{string, atom_to_binary(String, utf8)}] ++ Tokens, Handler, Stack, Config);
+value([], Handler, Stack, Config) ->
+ incomplete(value, Handler, Stack, Config);
+value(BadTokens, Handler, Stack, Config) when is_list(BadTokens) ->
+ ?error(value, BadTokens, Handler, Stack, Config);
+value(Token, Handler, Stack, Config) ->
+ value([Token], Handler, Stack, Config).
+
+
+object([end_object|Tokens], Handler, [object|Stack], Config) ->
+ maybe_done(Tokens, handle_event(end_object, Handler, Config), Stack, Config);
+object([{key, Key}|Tokens], Handler, Stack, Config)
+when is_atom(Key); is_binary(Key); is_integer(Key) ->
+ object([Key|Tokens], Handler, Stack, Config);
+object([Key|Tokens], Handler, [object|Stack], Config)
+when is_atom(Key); is_binary(Key); is_integer(Key) ->
+ try clean_string(fix_key(Key), Config)
+ of K ->
+ value(
+ Tokens,
+ handle_event({key, K}, Handler, Config),
+ [object|Stack],
+ Config
+ )
+ catch error:badarg ->
+ ?error(object, [{string, Key}|Tokens], Handler, Stack, Config)
+ end;
+object([], Handler, Stack, Config) ->
+ incomplete(object, Handler, Stack, Config);
+object(Token, Handler, Stack, Config) ->
+ object([Token], Handler, Stack, Config).
+
+
+array([end_array|Tokens], Handler, [array|Stack], Config) ->
+ maybe_done(Tokens, handle_event(end_array, Handler, Config), Stack, Config);
+array([], Handler, Stack, Config) ->
+ incomplete(array, Handler, Stack, Config);
+array(Tokens, Handler, Stack, Config) when is_list(Tokens) ->
+ value(Tokens, Handler, Stack, Config);
+array(Token, Handler, Stack, Config) ->
+ array([Token], Handler, Stack, Config).
+
+
+maybe_done([end_json], Handler, [], Config) ->
+ done([end_json], Handler, [], Config);
+maybe_done(Tokens, Handler, [object|_] = Stack, Config) when is_list(Tokens) ->
+ object(Tokens, Handler, Stack, Config);
+maybe_done(Tokens, Handler, [array|_] = Stack, Config) when is_list(Tokens) ->
+ array(Tokens, Handler, Stack, Config);
+maybe_done([], Handler, Stack, Config) ->
+ incomplete(maybe_done, Handler, Stack, Config);
+maybe_done(BadTokens, Handler, Stack, Config) when is_list(BadTokens) ->
+ ?error(maybe_done, BadTokens, Handler, Stack, Config);
+maybe_done(Token, Handler, Stack, Config) ->
+ maybe_done([Token], Handler, Stack, Config).
+
+
+done([], Handler, [], Config=#config{stream=true}) ->
+ incomplete(done, Handler, [], Config);
+done(Tokens, Handler, [], Config) when Tokens == [end_json]; Tokens == [] ->
+ {_, State} = handle_event(end_json, Handler, Config),
+ State;
+done(BadTokens, Handler, Stack, Config) when is_list(BadTokens) ->
+ ?error(done, BadTokens, Handler, Stack, Config);
+done(Token, Handler, Stack, Config) ->
+ done([Token], Handler, Stack, Config).
+
+
+fix_key(Key) when is_atom(Key) -> atom_to_binary(Key, utf8);
+fix_key(Key) when is_integer(Key) -> list_to_binary(integer_to_list(Key));
+fix_key(Key) when is_binary(Key) -> Key.
+
+
+clean_string(Bin, #config{dirty_strings=true}) -> Bin;
+clean_string(Bin, Config) -> clean(Bin, [], Config).
+
+
+%% unroll the control characters
+clean(<<0, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(0, Config)], Config);
+clean(<<1, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(1, Config)], Config);
+clean(<<2, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(2, Config)], Config);
+clean(<<3, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(3, Config)], Config);
+clean(<<4, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(4, Config)], Config);
+clean(<<5, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(5, Config)], Config);
+clean(<<6, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(6, Config)], Config);
+clean(<<7, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(7, Config)], Config);
+clean(<<8, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(8, Config)], Config);
+clean(<<9, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(9, Config)], Config);
+clean(<<10, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(10, Config)], Config);
+clean(<<11, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(11, Config)], Config);
+clean(<<12, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(12, Config)], Config);
+clean(<<13, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(13, Config)], Config);
+clean(<<14, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(14, Config)], Config);
+clean(<<15, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(15, Config)], Config);
+clean(<<16, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(16, Config)], Config);
+clean(<<17, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(17, Config)], Config);
+clean(<<18, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(18, Config)], Config);
+clean(<<19, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(19, Config)], Config);
+clean(<<20, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(20, Config)], Config);
+clean(<<21, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(21, Config)], Config);
+clean(<<22, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(22, Config)], Config);
+clean(<<23, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(23, Config)], Config);
+clean(<<24, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(24, Config)], Config);
+clean(<<25, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(25, Config)], Config);
+clean(<<26, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(26, Config)], Config);
+clean(<<27, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(27, Config)], Config);
+clean(<<28, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(28, Config)], Config);
+clean(<<29, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(29, Config)], Config);
+clean(<<30, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(30, Config)], Config);
+clean(<<31, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(31, Config)], Config);
+clean(<<34, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(34, Config)], Config);
+clean(<<47, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(47, Config)], Config);
+clean(<<92, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(92, Config)], Config);
+clean(<<X/utf8, Rest/binary>> = Bin, Acc, Config=#config{uescape=true}) ->
+ case X of
+ X when X < 16#80 -> start_count(Bin, Acc, Config);
+ _ -> clean(Rest, [Acc, json_escape_sequence(X)], Config)
+ end;
+%% u+2028
+clean(<<226, 128, 168, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(16#2028, Config)], Config);
+%% u+2029
+clean(<<226, 128, 169, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(16#2029, Config)], Config);
+clean(<<_/utf8, _/binary>> = Bin, Acc, Config) -> start_count(Bin, Acc, Config);
+%% surrogates
+clean(<<237, X, _, Rest/binary>>, Acc, Config) when X >= 160 ->
+ clean(Rest, [Acc, maybe_replace(surrogate, Config)], Config);
+%% overlong encodings and missing continuations of a 2 byte sequence
+clean(<<X, Rest/binary>>, Acc, Config) when X >= 192, X =< 223 ->
+ clean(strip_continuations(Rest, 1), [Acc, maybe_replace(badutf, Config)], Config);
+%% overlong encodings and missing continuations of a 3 byte sequence
+clean(<<X, Rest/binary>>, Acc, Config) when X >= 224, X =< 239 ->
+ clean(strip_continuations(Rest, 2), [Acc, maybe_replace(badutf, Config)], Config);
+%% overlong encodings and missing continuations of a 4 byte sequence
+clean(<<X, Rest/binary>>, Acc, Config) when X >= 240, X =< 247 ->
+ clean(strip_continuations(Rest, 3), [Acc, maybe_replace(badutf, Config)], Config);
+clean(<<_, Rest/binary>>, Acc, Config) ->
+ clean(Rest, [Acc, maybe_replace(badutf, Config)], Config);
+clean(<<>>, Acc, _) -> iolist_to_binary(Acc).
+
+
+start_count(Bin, Acc, Config) ->
+ Size = count(Bin, 0, Config),
+ <<Clean:Size/binary, Rest/binary>> = Bin,
+ clean(Rest, [Acc, Clean], Config).
+
+
+%% again, unrolling ascii makes a huge difference. sadly
+count(<<0, _/binary>>, N, _) -> N;
+count(<<1, _/binary>>, N, _) -> N;
+count(<<2, _/binary>>, N, _) -> N;
+count(<<3, _/binary>>, N, _) -> N;
+count(<<4, _/binary>>, N, _) -> N;
+count(<<5, _/binary>>, N, _) -> N;
+count(<<6, _/binary>>, N, _) -> N;
+count(<<7, _/binary>>, N, _) -> N;
+count(<<8, _/binary>>, N, _) -> N;
+count(<<9, _/binary>>, N, _) -> N;
+count(<<10, _/binary>>, N, _) -> N;
+count(<<11, _/binary>>, N, _) -> N;
+count(<<12, _/binary>>, N, _) -> N;
+count(<<13, _/binary>>, N, _) -> N;
+count(<<14, _/binary>>, N, _) -> N;
+count(<<15, _/binary>>, N, _) -> N;
+count(<<16, _/binary>>, N, _) -> N;
+count(<<17, _/binary>>, N, _) -> N;
+count(<<18, _/binary>>, N, _) -> N;
+count(<<19, _/binary>>, N, _) -> N;
+count(<<20, _/binary>>, N, _) -> N;
+count(<<21, _/binary>>, N, _) -> N;
+count(<<22, _/binary>>, N, _) -> N;
+count(<<23, _/binary>>, N, _) -> N;
+count(<<24, _/binary>>, N, _) -> N;
+count(<<25, _/binary>>, N, _) -> N;
+count(<<26, _/binary>>, N, _) -> N;
+count(<<27, _/binary>>, N, _) -> N;
+count(<<28, _/binary>>, N, _) -> N;
+count(<<29, _/binary>>, N, _) -> N;
+count(<<30, _/binary>>, N, _) -> N;
+count(<<31, _/binary>>, N, _) -> N;
+count(<<32, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<33, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<34, _/binary>>, N, _) -> N;
+count(<<35, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<36, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<37, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<38, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<39, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<40, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<41, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<42, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<43, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<44, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<45, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<46, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<47, _/binary>>, N, _) -> N;
+count(<<48, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<49, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<50, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<51, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<52, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<53, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<54, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<55, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<56, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<57, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<58, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<59, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<60, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<61, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<62, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<63, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<64, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<65, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<66, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<67, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<68, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<69, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<70, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<71, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<72, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<73, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<74, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<75, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<76, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<77, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<78, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<79, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<80, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<81, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<82, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<83, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<84, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<85, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<86, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<87, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<88, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<89, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<90, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<91, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<92, _/binary>>, N, _) -> N;
+count(<<93, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<94, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<95, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<96, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<97, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<98, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<99, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<100, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<101, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<102, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<103, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<104, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<105, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<106, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<107, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<108, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<109, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<110, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<111, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<112, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<113, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<114, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<115, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<116, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<117, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<118, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<119, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<120, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<121, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<122, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<123, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<124, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<125, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<126, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<127, Rest/binary>>, N, Config) ->
+ count(Rest, N + 1, Config);
+count(<<_/utf8, _/binary>>, N, #config{uescape=true}) -> N;
+count(<<X/utf8, Rest/binary>>, N, Config) ->
+ case X of
+ X when X < 16#800 -> count(Rest, N + 2, Config);
+ 16#2028 -> N;
+ 16#2029 -> N;
+ X when X < 16#10000 -> count(Rest, N + 3, Config);
+ _ -> count(Rest, N + 4, Config)
+ end;
+count(<<_, _/binary>>, N, _) -> N;
+count(<<>>, N, _) -> N.
+
+
+strip_continuations(Bin, 0) -> Bin;
+strip_continuations(<<X, Rest/binary>>, N) when X >= 128, X =< 191 ->
+ strip_continuations(Rest, N - 1);
+%% not a continuation byte
+strip_continuations(Bin, _) -> Bin.
+
+
+maybe_replace($\b, #config{escaped_strings=true}) -> <<$\\, $b>>;
+maybe_replace($\t, #config{escaped_strings=true}) -> <<$\\, $t>>;
+maybe_replace($\n, #config{escaped_strings=true}) -> <<$\\, $n>>;
+maybe_replace($\f, #config{escaped_strings=true}) -> <<$\\, $f>>;
+maybe_replace($\r, #config{escaped_strings=true}) -> <<$\\, $r>>;
+maybe_replace($\", #config{escaped_strings=true}) -> <<$\\, $\">>;
+maybe_replace($/, Config=#config{escaped_strings=true}) ->
+ case Config#config.escaped_forward_slashes of
+ true -> <<$\\, $/>>;
+ false -> <<$/>>
+ end;
+maybe_replace($\\, #config{escaped_strings=true}) -> <<$\\, $\\>>;
+maybe_replace(X, #config{escaped_strings=true}) when X < 32 ->
+ json_escape_sequence(X);
+maybe_replace(X, Config=#config{escaped_strings=true}) when X == 16#2028; X == 16#2029 ->
+ case Config#config.unescaped_jsonp of
+ true -> <<X/utf8>>;
+ false -> json_escape_sequence(X)
+ end;
+maybe_replace(Atom, #config{strict_utf8=true}) when is_atom(Atom) ->
+ erlang:error(badarg);
+maybe_replace(surrogate, _Config) ->
+ <<16#fffd/utf8>>;
+maybe_replace(badutf, _Config) ->
+ <<16#fffd/utf8>>;
+maybe_replace(X, _Config) ->
+ <<X/utf8>>.
+
+
+%% convert a codepoint to it's \uXXXX equiv.
+json_escape_sequence(X) when X < 65536 ->
+ <<A:4, B:4, C:4, D:4>> = <<X:16>>,
+ <<$\\, $u, (to_hex(A)), (to_hex(B)), (to_hex(C)), (to_hex(D))>>;
+json_escape_sequence(X) ->
+ Adjusted = X - 16#10000,
+ <<A:10, B:10>> = <<Adjusted:20>>,
+ [json_escape_sequence(A + 16#d800), json_escape_sequence(B + 16#dc00)].
+
+
+to_hex(10) -> $a;
+to_hex(11) -> $b;
+to_hex(12) -> $c;
+to_hex(13) -> $d;
+to_hex(14) -> $e;
+to_hex(15) -> $f;
+to_hex(X) -> X + 48. %% ascii "1" is [49], "2" is [50], etc...
+
+
+%% for raw input
+-spec init([]) -> [].
+
+init([]) -> [].
+
+
+-spec handle_event(Event::any(), Acc::list()) -> list().
+
+handle_event(end_json, State) -> lists:reverse(State);
+handle_event(Event, State) -> [Event] ++ State.
+
+
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+
+parse(Events, Config) -> value(Events, {jsx, []}, [], jsx_config:parse_config(Config)).
+
+
+error_test_() ->
+ [
+ {"value error", ?_assertError(badarg, parse([self()], []))},
+ {"maybe_done error", ?_assertError(badarg, parse([start_array, end_array, start_array, end_json], []))},
+ {"done error", ?_assertError(badarg, parse([{string, <<"">>}, {literal, true}, end_json], []))},
+ {"string error", ?_assertError(badarg, parse([{string, <<237, 160, 128>>}, end_json], [strict]))}
+ ].
+
+
+custom_error_handler_test_() ->
+ Error = fun(Rest, {_, State, _, _}, _) -> {State, Rest} end,
+ [
+ {"value error", ?_assertEqual(
+ {value, [self()]},
+ parse([self()], [{error_handler, Error}])
+ )},
+ {"maybe_done error", ?_assertEqual(
+ {maybe_done, [start_array, end_json]},
+ parse([start_array, end_array, start_array, end_json], [{error_handler, Error}])
+ )},
+ {"done error", ?_assertEqual(
+ {maybe_done, [{literal, true}, end_json]},
+ parse([{string, <<"">>}, {literal, true}, end_json], [{error_handler, Error}])
+ )},
+ {"string error", ?_assertEqual(
+ {value, [{string, <<237, 160, 128>>}, end_json]},
+ parse([{string, <<237, 160, 128>>}, end_json], [{error_handler, Error}, strict])
+ )}
+ ].
+
+
+incomplete_test_() ->
+ Cases = [
+ {"incomplete value", []},
+ {"incomplete object", [start_object]},
+ {"incomplete array", [start_array]},
+ {"incomplete maybe_done", [start_array, end_array]}
+ ],
+ [{Title, ?_assertError(badarg, parse(Events, []))}
+ || {Title, Events} <- Cases
+ ].
+
+
+custom_incomplete_handler_test_() ->
+ [
+ {"custom incomplete handler", ?_assertError(
+ badarg,
+ parse([], [{incomplete_handler, fun(_, _, _) -> erlang:error(badarg) end}])
+ )}
+ ].
+
+
+raw_test_() ->
+ Parse = fun(Events, Config) -> (parser(?MODULE, [], Config))(Events ++ [end_json]) end,
+ [
+ {"raw empty list", ?_assertEqual(
+ [start_array, end_array],
+ Parse([{raw, <<"[]">>}], [])
+ )},
+ {"raw empty object", ?_assertEqual(
+ [start_object, end_object],
+ Parse([{raw, <<"{}">>}], [])
+ )},
+ {"raw chunk inside stream", ?_assertEqual(
+ [start_object, {key, <<"key">>}, start_array, {literal, true}, end_array, end_object],
+ Parse([start_object, {key, <<"key">>}, {raw, <<"[true]">>}, end_object], [])
+ )}
+ ].
+
+
+%% erlang refuses to encode certain codepoints, so fake them
+to_fake_utf8(N) when N < 16#0080 -> <<N:8>>;
+to_fake_utf8(N) when N < 16#0800 ->
+ <<0:5, Y:5, X:6>> = <<N:16>>,
+ <<2#110:3, Y:5, 2#10:2, X:6>>;
+to_fake_utf8(N) when N < 16#10000 ->
+ <<Z:4, Y:6, X:6>> = <<N:16>>,
+ <<2#1110:4, Z:4, 2#10:2, Y:6, 2#10:2, X:6>>;
+to_fake_utf8(N) ->
+ <<0:3, W:3, Z:6, Y:6, X:6>> = <<N:24>>,
+ <<2#11110:5, W:3, 2#10:2, Z:6, 2#10:2, Y:6, 2#10:2, X:6>>.
+
+
+codepoints() ->
+ unicode:characters_to_binary(
+ [32, 33]
+ ++ lists:seq(35, 46)
+ ++ lists:seq(48, 91)
+ ++ lists:seq(93, 16#2027)
+ ++ lists:seq(16#202a, 16#d7ff)
+ ++ lists:seq(16#e000, 16#ffff)
+ ).
+
+
+extended_codepoints() ->
+ unicode:characters_to_binary(
+ lists:seq(16#10000, 16#1ffff) ++ [
+ 16#20000, 16#30000, 16#40000, 16#50000, 16#60000,
+ 16#70000, 16#80000, 16#90000, 16#a0000, 16#b0000,
+ 16#c0000, 16#d0000, 16#e0000, 16#f0000, 16#100000
+ ]
+ ).
+
+
+surrogates() -> [ to_fake_utf8(N) || N <- lists:seq(16#d800, 16#dfff) ].
+
+
+clean_string_helper(String) ->
+ try clean_string(String, #config{strict_utf8=true}) of Clean -> Clean
+ catch error:badarg -> {error, badarg}
+ end.
+
+
+clean_string_test_() ->
+ [
+ {"clean codepoints", ?_assertEqual(
+ codepoints(),
+ clean_string(codepoints(), #config{})
+ )},
+ {"clean extended codepoints", ?_assertEqual(
+ extended_codepoints(),
+ clean_string(extended_codepoints(), #config{})
+ )},
+ {"escape path codepoints", ?_assertEqual(
+ codepoints(),
+ clean_string(codepoints(), #config{escaped_strings=true})
+ )},
+ {"escape path extended codepoints", ?_assertEqual(
+ extended_codepoints(),
+ clean_string(extended_codepoints(), #config{escaped_strings=true})
+ )},
+ {"error surrogates", ?_assertEqual(
+ lists:duplicate(length(surrogates()), {error, badarg}),
+ lists:map(fun(Codepoint) -> clean_string_helper(Codepoint) end, surrogates())
+ )},
+ {"clean surrogates", ?_assertEqual(
+ lists:duplicate(length(surrogates()), <<16#fffd/utf8>>),
+ lists:map(fun(Codepoint) -> clean_string(Codepoint, #config{}) end, surrogates())
+ )}
+ ].
+
+
+escape_test_() ->
+ [
+ {"maybe_escape backspace", ?_assertEqual(
+ <<"\\b">>,
+ clean_string(<<16#0008/utf8>>, #config{escaped_strings=true})
+ )},
+ {"don't escape backspace", ?_assertEqual(
+ <<"\b">>,
+ clean_string(<<16#0008/utf8>>, #config{})
+ )},
+ {"maybe_escape tab", ?_assertEqual(
+ <<"\\t">>,
+ clean_string(<<16#0009/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape newline", ?_assertEqual(
+ <<"\\n">>,
+ clean_string(<<16#000a/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape formfeed", ?_assertEqual(
+ <<"\\f">>,
+ clean_string(<<16#000c/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape carriage return", ?_assertEqual(
+ <<"\\r">>,
+ clean_string(<<16#000d/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape quote", ?_assertEqual(
+ <<"\\\"">>,
+ clean_string(<<16#0022/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape forward slash", ?_assertEqual(
+ <<"\\/">>,
+ clean_string(<<16#002f/utf8>>, #config{escaped_strings=true, escaped_forward_slashes=true})
+ )},
+ {"do not maybe_escape forward slash", ?_assertEqual(
+ <<"/">>,
+ clean_string(<<16#002f/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape backslash", ?_assertEqual(
+ <<"\\\\">>,
+ clean_string(<<16#005c/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape jsonp (u2028)", ?_assertEqual(
+ <<"\\u2028">>,
+ clean_string(<<16#2028/utf8>>, #config{escaped_strings=true})
+ )},
+ {"do not maybe_escape jsonp (u2028)", ?_assertEqual(
+ <<16#2028/utf8>>,
+ clean_string(<<16#2028/utf8>>, #config{escaped_strings=true, unescaped_jsonp=true})
+ )},
+ {"maybe_escape jsonp (u2029)", ?_assertEqual(
+ <<"\\u2029">>,
+ clean_string(<<16#2029/utf8>>, #config{escaped_strings=true})
+ )},
+ {"do not maybe_escape jsonp (u2029)", ?_assertEqual(
+ <<16#2029/utf8>>,
+ clean_string(<<16#2029/utf8>>, #config{escaped_strings=true, unescaped_jsonp=true})
+ )},
+ {"maybe_escape u0000", ?_assertEqual(
+ <<"\\u0000">>,
+ clean_string(<<16#0000/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0001", ?_assertEqual(
+ <<"\\u0001">>,
+ clean_string(<<16#0001/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0002", ?_assertEqual(
+ <<"\\u0002">>,
+ clean_string(<<16#0002/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0003", ?_assertEqual(
+ <<"\\u0003">>,
+ clean_string(<<16#0003/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0004", ?_assertEqual(
+ <<"\\u0004">>,
+ clean_string(<<16#0004/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0005", ?_assertEqual(
+ <<"\\u0005">>,
+ clean_string(<<16#0005/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0006", ?_assertEqual(
+ <<"\\u0006">>,
+ clean_string(<<16#0006/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0007", ?_assertEqual(
+ <<"\\u0007">>,
+ clean_string(<<16#0007/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u000b", ?_assertEqual(
+ <<"\\u000b">>,
+ clean_string(<<16#000b/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u000e", ?_assertEqual(
+ <<"\\u000e">>,
+ clean_string(<<16#000e/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u000f", ?_assertEqual(
+ <<"\\u000f">>,
+ clean_string(<<16#000f/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0010", ?_assertEqual(
+ <<"\\u0010">>,
+ clean_string(<<16#0010/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0011", ?_assertEqual(
+ <<"\\u0011">>,
+ clean_string(<<16#0011/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0012", ?_assertEqual(
+ <<"\\u0012">>,
+ clean_string(<<16#0012/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0013", ?_assertEqual(
+ <<"\\u0013">>,
+ clean_string(<<16#0013/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0014", ?_assertEqual(
+ <<"\\u0014">>,
+ clean_string(<<16#0014/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0015", ?_assertEqual(
+ <<"\\u0015">>,
+ clean_string(<<16#0015/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0016", ?_assertEqual(
+ <<"\\u0016">>,
+ clean_string(<<16#0016/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0017", ?_assertEqual(
+ <<"\\u0017">>,
+ clean_string(<<16#0017/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0018", ?_assertEqual(
+ <<"\\u0018">>,
+ clean_string(<<16#0018/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u0019", ?_assertEqual(
+ <<"\\u0019">>,
+ clean_string(<<16#0019/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u001a", ?_assertEqual(
+ <<"\\u001a">>,
+ clean_string(<<16#001a/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u001b", ?_assertEqual(
+ <<"\\u001b">>,
+ clean_string(<<16#001b/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u001c", ?_assertEqual(
+ <<"\\u001c">>,
+ clean_string(<<16#001c/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u001d", ?_assertEqual(
+ <<"\\u001d">>,
+ clean_string(<<16#001d/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u001e", ?_assertEqual(
+ <<"\\u001e">>,
+ clean_string(<<16#001e/utf8>>, #config{escaped_strings=true})
+ )},
+ {"maybe_escape u001f", ?_assertEqual(
+ <<"\\u001f">>,
+ clean_string(<<16#001f/utf8>>, #config{escaped_strings=true})
+ )}
+ ].
+
+
+bad_utf8_test_() ->
+ [
+ {"orphan continuation byte u+0080", ?_assertError(
+ badarg,
+ clean_string(<<16#0080>>, #config{strict_utf8=true})
+ )},
+ {"orphan continuation byte u+0080 replaced", ?_assertEqual(
+ <<16#fffd/utf8>>,
+ clean_string(<<16#0080>>, #config{})
+ )},
+ {"orphan continuation byte u+00bf", ?_assertError(
+ badarg,
+ clean_string(<<16#00bf>>, #config{strict_utf8=true})
+ )},
+ {"orphan continuation byte u+00bf replaced", ?_assertEqual(
+ <<16#fffd/utf8>>,
+ clean_string(<<16#00bf>>, #config{})
+ )},
+ {"2 continuation bytes", ?_assertError(
+ badarg,
+ clean_string(<<(binary:copy(<<16#0080>>, 2))/binary>>, #config{strict_utf8=true})
+ )},
+ {"2 continuation bytes replaced", ?_assertEqual(
+ binary:copy(<<16#fffd/utf8>>, 2),
+ clean_string(<<(binary:copy(<<16#0080>>, 2))/binary>>, #config{})
+ )},
+ {"3 continuation bytes", ?_assertError(
+ badarg,
+ clean_string(<<(binary:copy(<<16#0080>>, 3))/binary>>, #config{strict_utf8=true})
+ )},
+ {"3 continuation bytes replaced", ?_assertEqual(
+ binary:copy(<<16#fffd/utf8>>, 3),
+ clean_string(<<(binary:copy(<<16#0080>>, 3))/binary>>, #config{})
+ )},
+ {"4 continuation bytes", ?_assertError(
+ badarg,
+ clean_string(<<(binary:copy(<<16#0080>>, 4))/binary>>, #config{strict_utf8=true})
+ )},
+ {"4 continuation bytes replaced", ?_assertEqual(
+ binary:copy(<<16#fffd/utf8>>, 4),
+ clean_string(<<(binary:copy(<<16#0080>>, 4))/binary>>, #config{})
+ )},
+ {"5 continuation bytes", ?_assertError(
+ badarg,
+ clean_string(<<(binary:copy(<<16#0080>>, 5))/binary>>, #config{strict_utf8=true})
+ )},
+ {"5 continuation bytes replaced", ?_assertEqual(
+ binary:copy(<<16#fffd/utf8>>, 5),
+ clean_string(<<(binary:copy(<<16#0080>>, 5))/binary>>, #config{})
+ )},
+ {"6 continuation bytes", ?_assertError(
+ badarg,
+ clean_string(<<(binary:copy(<<16#0080>>, 6))/binary>>, #config{strict_utf8=true})
+ )},
+ {"6 continuation bytes replaced", ?_assertEqual(
+ binary:copy(<<16#fffd/utf8>>, 6),
+ clean_string(<<(binary:copy(<<16#0080>>, 6))/binary>>, #config{})
+ )},
+ {"all continuation bytes", ?_assertError(
+ badarg,
+ clean_string(<<(list_to_binary(lists:seq(16#0080, 16#00bf)))/binary>>, #config{strict_utf8=true})
+ )},
+ {"all continuation bytes replaced", ?_assertEqual(
+ binary:copy(<<16#fffd/utf8>>, length(lists:seq(16#0080, 16#00bf))),
+ clean_string(
+ <<(list_to_binary(lists:seq(16#0080, 16#00bf)))/binary>>,
+ #config{}
+ )
+ )},
+ {"lonely start byte", ?_assertError(
+ badarg,
+ clean_string(<<16#00c0>>, #config{strict_utf8=true})
+ )},
+ {"lonely start byte replaced", ?_assertEqual(
+ <<16#fffd/utf8>>,
+ clean_string(<<16#00c0>>, #config{})
+ )},
+ {"lonely start bytes (2 byte)", ?_assertError(
+ badarg,
+ clean_string(<<16#00c0, 32, 16#00df>>, #config{strict_utf8=true})
+ )},
+ {"lonely start bytes (2 byte) replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32, 16#fffd/utf8>>,
+ clean_string(<<16#00c0, 32, 16#00df>>, #config{})
+ )},
+ {"lonely start bytes (3 byte)", ?_assertError(
+ badarg,
+ clean_string(<<16#00e0, 32, 16#00ef>>, #config{strict_utf8=true})
+ )},
+ {"lonely start bytes (3 byte) replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32, 16#fffd/utf8>>,
+ clean_string(<<16#00e0, 32, 16#00ef>>, #config{})
+ )},
+ {"lonely start bytes (4 byte)", ?_assertError(
+ badarg,
+ clean_string(<<16#00f0, 32, 16#00f7>>, #config{strict_utf8=true})
+ )},
+ {"lonely start bytes (4 byte) replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32, 16#fffd/utf8>>,
+ clean_string(<<16#00f0, 32, 16#00f7>>, #config{})
+ )},
+ {"missing continuation byte (3 byte)", ?_assertError(
+ badarg,
+ clean_string(<<224, 160, 32>>, #config{strict_utf8=true})
+ )},
+ {"missing continuation byte (3 byte) replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32>>,
+ clean_string(<<224, 160, 32>>, #config{})
+ )},
+ {"missing continuation byte (4 byte missing one)", ?_assertError(
+ badarg,
+ clean_string(<<240, 144, 128, 32>>, #config{strict_utf8=true})
+ )},
+ {"missing continuation byte (4 byte missing one) replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32>>,
+ clean_string(<<240, 144, 128, 32>>, #config{})
+ )},
+ {"missing continuation byte (4 byte missing two)", ?_assertError(
+ badarg,
+ clean_string(<<240, 144, 32>>, #config{strict_utf8=true})
+ )},
+ {"missing continuation byte (4 byte missing two) replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32>>,
+ clean_string(<<240, 144, 32>>, #config{})
+ )},
+ {"overlong encoding of u+002f (2 byte)", ?_assertError(
+ badarg,
+ clean_string(<<16#c0, 16#af, 32>>, #config{strict_utf8=true})
+ )},
+ {"overlong encoding of u+002f (2 byte) replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32>>,
+ clean_string(<<16#c0, 16#af, 32>>, #config{})
+ )},
+ {"overlong encoding of u+002f (3 byte)", ?_assertError(
+ badarg,
+ clean_string(<<16#e0, 16#80, 16#af, 32>>, #config{strict_utf8=true})
+ )},
+ {"overlong encoding of u+002f (3 byte) replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32>>,
+ clean_string(<<16#e0, 16#80, 16#af, 32>>, #config{})
+ )},
+ {"overlong encoding of u+002f (4 byte)", ?_assertError(
+ badarg,
+ clean_string(<<16#f0, 16#80, 16#80, 16#af, 32>>, #config{strict_utf8=true})
+ )},
+ {"overlong encoding of u+002f (4 byte) replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32>>,
+ clean_string(<<16#f0, 16#80, 16#80, 16#af, 32>>, #config{})
+ )},
+ {"highest overlong 2 byte sequence", ?_assertError(
+ badarg,
+ clean_string(<<16#c1, 16#bf, 32>>, #config{strict_utf8=true})
+ )},
+ {"highest overlong 2 byte sequence replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32>>,
+ clean_string(<<16#c1, 16#bf, 32>>, #config{})
+ )},
+ {"highest overlong 3 byte sequence", ?_assertError(
+ badarg,
+ clean_string(<<16#e0, 16#9f, 16#bf, 32>>, #config{strict_utf8=true})
+ )},
+ {"highest overlong 3 byte sequence replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32>>,
+ clean_string(<<16#e0, 16#9f, 16#bf, 32>>, #config{})
+ )},
+ {"highest overlong 4 byte sequence", ?_assertError(
+ badarg,
+ clean_string(<<16#f0, 16#8f, 16#bf, 16#bf, 32>>, #config{strict_utf8=true})
+ )},
+ {"highest overlong 4 byte sequence replaced", ?_assertEqual(
+ <<16#fffd/utf8, 32>>,
+ clean_string(<<16#f0, 16#8f, 16#bf, 16#bf, 32>>, #config{})
+ )}
+ ].
+
+
+json_escape_sequence_test_() ->
+ [
+ {"json escape sequence test - 16#0000", ?_assertEqual(<<"\\u0000"/utf8>>, json_escape_sequence(16#0000))},
+ {"json escape sequence test - 16#abc", ?_assertEqual(<<"\\u0abc"/utf8>>, json_escape_sequence(16#abc))},
+ {"json escape sequence test - 16#def", ?_assertEqual(<<"\\u0def"/utf8>>, json_escape_sequence(16#def))}
+ ].
+
+
+uescape_test_() ->
+ [
+ {"\"\\u0080\"", ?_assertEqual(
+ <<"\\u0080">>,
+ clean_string(<<128/utf8>>, #config{uescape=true})
+ )},
+ {"\"\\u8ca8\\u5481\\u3002\\u0091\\u0091\"", ?_assertEqual(
+ <<"\\u8ca8\\u5481\\u3002\\u0091\\u0091">>,
+ clean_string(
+ <<232,178,168,229,146,129,227,128,130,194,145,194,145>>,
+ #config{uescape=true}
+ )
+ )},
+ {"\"\\ud834\\udd1e\"", ?_assertEqual(
+ <<"\\ud834\\udd1e">>,
+ clean_string(<<240, 157, 132, 158>>, #config{uescape=true})
+ )},
+ {"\"\\ud83d\\ude0a\"", ?_assertEqual(
+ <<"\\ud83d\\ude0a">>,
+ clean_string(<<240, 159, 152, 138>>, #config{uescape=true})
+ )}
+ ].
+
+
+fix_key_test_() ->
+ [
+ {"binary key", ?_assertEqual(fix_key(<<"foo">>), <<"foo">>)},
+ {"atom key", ?_assertEqual(fix_key(foo), <<"foo">>)},
+ {"integer key", ?_assertEqual(fix_key(123), <<"123">>)}
+ ].
+
+
+datetime_test_() ->
+ [
+ {"datetime", ?_assertEqual(
+ [start_array, {string, <<"2014-08-13T23:12:34Z">>}, end_array, end_json],
+ parse([start_array, {{2014,08,13},{23,12,34}}, end_array, end_json], [])
+ )},
+ {"datetime", ?_assertEqual(
+ [start_array, {string, <<"2014-08-13T23:12:34.363369Z">>}, end_array, end_json],
+ parse([start_array, {{2014,08,13},{23,12,34.363369}}, end_array, end_json], [])
+ )}
+ ].
+
+
+timestamp_test_() ->
+ [
+ {"timestamp", ?_assertEqual(
+ [start_array, {string, <<"2016-01-15T18:19:28Z">>}, end_array, end_json],
+ parse([start_array, {1452,881968,111772}, end_array, end_json], [])
+ )}
+ ].
+
+
+rogue_tuple_test_() ->
+ [
+ {"kv in value position of object", ?_assertError(
+ badarg,
+ parse([start_object, <<"key">>, {<<"key">>, <<"value">>}, end_object, end_json], [])
+ )},
+ {"kv in value position of list", ?_assertError(
+ badarg,
+ parse([start_array, {<<"key">>, <<"value">>}, end_array, end_json], [])
+ )}
+ ].
+
+
+-endif.
diff --git a/server/_build/default/lib/jsx/src/jsx_to_json.erl b/server/_build/default/lib/jsx/src/jsx_to_json.erl
new file mode 100644
index 0000000..d20add6
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx_to_json.erl
@@ -0,0 +1,408 @@
+%% The MIT License
+
+%% Copyright (c) 2010-2013 alisdair sullivan <alisdairsullivan@yahoo.ca>
+
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+
+
+-module(jsx_to_json).
+
+-export([to_json/2, format/2]).
+-export([init/1, handle_event/2]).
+-export([start_json/0, start_json/1]).
+-export([start_object/1, start_array/1, finish/1, insert/2, get_key/1, get_value/1]).
+
+
+-record(config, {
+ space = 0,
+ indent = 0,
+ depth = 0,
+ newline = <<$\n>>
+}).
+
+-type config() :: proplists:proplist().
+
+
+-spec to_json(Source::jsx:json_term(), Config::jsx_config:options()) -> binary().
+
+to_json(Source, Config) when is_list(Config) ->
+ (jsx:encoder(?MODULE, Config, jsx_config:extract_config(Config ++ [escaped_strings])))(Source).
+
+
+-spec format(Source::binary(), Config::jsx_config:options()) -> jsx:json_text().
+
+format(Source, Config) when is_binary(Source) andalso is_list(Config) ->
+ (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config ++ [escaped_strings])))(Source);
+format(_, _) -> erlang:error(badarg).
+
+
+parse_config(Config) -> parse_config(Config, #config{}).
+
+parse_config([{space, Val}|Rest], Config) when is_integer(Val), Val > 0 ->
+ parse_config(Rest, Config#config{space = Val});
+parse_config([space|Rest], Config) ->
+ parse_config(Rest, Config#config{space = 1});
+parse_config([{indent, Val}|Rest], Config) when is_integer(Val), Val > 0 ->
+ parse_config(Rest, Config#config{indent = Val});
+parse_config([indent|Rest], Config) ->
+ parse_config(Rest, Config#config{indent = 1});
+parse_config([{newline, Val}|Rest], Config) when is_binary(Val) ->
+ parse_config(Rest, Config#config{newline = Val});
+parse_config([{K, _}|Rest] = Options, Config) ->
+ case lists:member(K, jsx_config:valid_flags()) of
+ true -> parse_config(Rest, Config)
+ ; false -> erlang:error(badarg, [Options, Config])
+ end;
+parse_config([K|Rest] = Options, Config) ->
+ case lists:member(K, jsx_config:valid_flags()) of
+ true -> parse_config(Rest, Config)
+ ; false -> erlang:error(badarg, [Options, Config])
+ end;
+parse_config([], Config) ->
+ Config.
+
+
+-define(start_object, <<"{">>).
+-define(start_array, <<"[">>).
+-define(end_object, <<"}">>).
+-define(end_array, <<"]">>).
+-define(colon, <<":">>).
+-define(comma, <<",">>).
+-define(quote, <<"\"">>).
+-define(space, <<" ">>).
+-define(newline, <<"\n">>).
+
+
+-type state() :: {unicode:charlist(), #config{}}.
+-spec init(Config::config()) -> state().
+
+init(Config) -> {[], parse_config(Config)}.
+
+
+-spec handle_event(Event::any(), State::state()) -> state().
+
+handle_event(end_json, State) -> get_value(State);
+
+handle_event(start_object, State) -> start_object(State);
+handle_event(end_object, State) -> finish(State);
+
+handle_event(start_array, State) -> start_array(State);
+handle_event(end_array, State) -> finish(State);
+
+handle_event({Type, Event}, {_, Config} = State) -> insert(encode(Type, Event, Config), State).
+
+
+encode(string, String, _Config) ->
+ [?quote, String, ?quote];
+encode(key, Key, _Config) ->
+ [?quote, Key, ?quote];
+encode(literal, Literal, _Config) ->
+ erlang:atom_to_list(Literal);
+encode(integer, Integer, _Config) ->
+ erlang:integer_to_list(Integer);
+encode(float, Float, _Config) ->
+ io_lib:format("~p", [Float]).
+
+
+space(Config) ->
+ case Config#config.space of
+ 0 -> <<>>
+ ; X when X > 0 -> binary:copy(?space, X)
+ end.
+
+
+indent(Config) ->
+ case Config#config.indent of
+ 0 -> <<>>
+ ; X when X > 0 -> <<(Config#config.newline)/binary, (binary:copy(?space, X * Config#config.depth))/binary>>
+ end.
+
+
+indent_or_space(Config) ->
+ case Config#config.indent > 0 of
+ true -> indent(Config)
+ ; false -> space(Config)
+ end.
+
+
+%% internal state is a stack and a config object
+%% `{Stack, Config}`
+%% the stack is a list of in progress objects/arrays
+%% `[Current, Parent, Grandparent,...OriginalAncestor]`
+%% an object has the representation on the stack of
+%% `{object, Object}`
+%% of if there's a key with a yet to be matched value
+%% `{object, Key, Object}`
+%% an array looks like
+%% `{array, Array}`
+%% `Object` and `Array` are utf8 encoded binaries
+
+start_json() -> {[], #config{}}.
+
+start_json(Config) when is_list(Config) -> {[], parse_config(Config)}.
+
+%% allocate a new object on top of the stack
+start_object({Stack, Config = #config{depth = Depth}}) ->
+ {[{object, ?start_object}] ++ Stack, Config#config{depth = Depth + 1}}.
+
+%% allocate a new array on top of the stack
+start_array({Stack, Config = #config{depth = Depth}}) ->
+ {[{array, ?start_array}] ++ Stack, Config#config{depth = Depth + 1}}.
+
+%% finish an object or array and insert it into the parent object if it exists
+finish({Stack, Config = #config{depth = Depth}}) ->
+ NewConfig = Config#config{depth = Depth - 1},
+ finish_({Stack, NewConfig}).
+
+finish_({[{object, <<"{">>}], Config}) -> {<<"{}">>, Config};
+finish_({[{array, <<"[">>}], Config}) -> {<<"[]">>, Config};
+finish_({[{object, <<"{">>}|Rest], Config}) -> insert(<<"{}">>, {Rest, Config});
+finish_({[{array, <<"[">>}|Rest], Config}) -> insert(<<"[]">>, {Rest, Config});
+finish_({[{object, Object}], Config}) ->
+ {[Object, indent(Config), ?end_object], Config};
+finish_({[{object, Object}|Rest], Config}) ->
+ insert([Object, indent(Config), ?end_object], {Rest, Config});
+finish_({[{array, Array}], Config}) ->
+ {[Array, indent(Config), ?end_array], Config};
+finish_({[{array, Array}|Rest], Config}) ->
+ insert([Array, indent(Config), ?end_array], {Rest, Config});
+finish_(_) -> erlang:error(badarg).
+
+%% insert a value when there's no parent object or array
+insert(Value, {[], Config}) ->
+ {Value, Config};
+%% insert a key or value into an object or array, autodetects the 'right' thing
+insert(Key, {[{object, Object}|Rest], Config}) ->
+ {[{object, Key, Object}] ++ Rest, Config};
+insert(Value, {[{object, Key, ?start_object}|Rest], Config}) ->
+ {
+ [{object, [
+ ?start_object,
+ indent(Config),
+ Key,
+ ?colon,
+ space(Config),
+ Value
+ ]}] ++ Rest,
+ Config
+ };
+insert(Value, {[{object, Key, Object}|Rest], Config}) ->
+ {
+ [{object, [
+ Object,
+ ?comma,
+ indent_or_space(Config),
+ Key,
+ ?colon,
+ space(Config),
+ Value
+ ]}] ++ Rest,
+ Config
+ };
+insert(Value, {[{array, ?start_array}|Rest], Config}) ->
+ {[{array, [?start_array, indent(Config), Value]}] ++ Rest, Config};
+insert(Value, {[{array, Array}|Rest], Config}) ->
+ {
+ [{array, [Array,
+ ?comma,
+ indent_or_space(Config),
+ Value
+ ]}] ++ Rest,
+ Config
+ };
+insert(_, _) -> erlang:error(badarg).
+
+
+get_key({[{object, Key, _}|_], _}) -> Key;
+get_key(_) -> erlang:error(badarg).
+
+
+get_value({Value, _Config}) ->
+ try unicode:characters_to_binary(Value)
+ catch error:_ -> erlang:error(badarg)
+ end;
+get_value(_) -> erlang:error(badarg).
+
+
+
+%% eunit tests
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+
+config_test_() ->
+ [
+ {"empty config", ?_assertEqual(#config{}, parse_config([]))},
+ {"unspecified indent/space", ?_assertEqual(
+ #config{space=1, indent=1},
+ parse_config([space, indent])
+ )},
+ {"specific indent", ?_assertEqual(
+ #config{indent=4},
+ parse_config([{indent, 4}])
+ )},
+ {"specific space", ?_assertEqual(
+ #config{space=2},
+ parse_config([{space, 2}])
+ )},
+ {"specific space and indent", ?_assertEqual(
+ #config{space=2, indent=2},
+ parse_config([{space, 2}, {indent, 2}])
+ )},
+ {"invalid opt flag", ?_assertError(badarg, parse_config([error]))},
+ {"invalid opt tuple", ?_assertError(badarg, parse_config([{error, true}]))}
+ ].
+
+
+space_test_() ->
+ [
+ {"no space", ?_assertEqual(<<>>, space(#config{space=0}))},
+ {"one space", ?_assertEqual(<<" ">>, space(#config{space=1}))},
+ {"four spaces", ?_assertEqual(<<" ">>, space(#config{space=4}))}
+ ].
+
+
+indent_test_() ->
+ [
+ {"no indent", ?_assertEqual(<<>>, indent(#config{indent=0, depth=1}))},
+ {"indent 1 depth 1", ?_assertEqual(
+ <<?newline/binary, <<" ">>/binary>>,
+ indent(#config{indent=1, depth=1})
+ )},
+ {"indent 1 depth 2", ?_assertEqual(
+ <<?newline/binary, <<" ">>/binary>>,
+ indent(#config{indent=1, depth=2})
+ )},
+ {"indent 4 depth 1", ?_assertEqual(
+ <<?newline/binary, <<" ">>/binary>>,
+ indent(#config{indent=4, depth=1})
+ )},
+ {"indent 4 depth 2", ?_assertEqual(
+ <<?newline/binary, <<" ">>/binary, <<" ">>/binary>>,
+ indent(#config{indent=4, depth=2})
+ )}
+ ].
+
+
+indent_or_space_test_() ->
+ [
+ {"no indent so space", ?_assertEqual(
+ <<" ">>,
+ indent_or_space(#config{space=1, indent=0, depth=1})
+ )},
+ {"indent so no space", ?_assertEqual(
+ <<?newline/binary, <<" ">>/binary>>,
+ indent_or_space(#config{space=1, indent=1, depth=1})
+ )}
+ ].
+
+
+encode_test_() ->
+ [
+ {"0.0", ?_assert(encode(float, 0.0, #config{}) =:= ["0.0"])},
+ {"1.0", ?_assert(encode(float, 1.0, #config{}) =:= ["1.0"])},
+ {"-1.0", ?_assert(encode(float, -1.0, #config{}) =:= ["-1.0"])},
+ {"3.1234567890987654321",
+ ?_assert(
+ encode(float, 3.1234567890987654321, #config{}) =:= ["3.1234567890987655"])
+ },
+ {"1.0e23", ?_assert(encode(float, 1.0e23, #config{}) =:= ["1.0e23"])},
+ {"0.3", ?_assert(encode(float, 3.0/10.0, #config{}) =:= ["0.3"])},
+ {"0.0001", ?_assert(encode(float, 0.0001, #config{}) =:= ["0.0001"])},
+ {"0.00001", ?_assert(encode(float, 0.00001, #config{}) =:= ["1.0e-5"])},
+ {"0.00000001", ?_assert(encode(float, 0.00000001, #config{}) =:= ["1.0e-8"])},
+ {"1.0e-323", ?_assert(encode(float, 1.0e-323, #config{}) =:= ["1.0e-323"])},
+ {"1.0e308", ?_assert(encode(float, 1.0e308, #config{}) =:= ["1.0e308"])},
+ {"min normalized float",
+ ?_assert(
+ encode(float, math:pow(2, -1022), #config{}) =:= ["2.2250738585072014e-308"]
+ )
+ },
+ {"max normalized float",
+ ?_assert(
+ encode(float, (2 - math:pow(2, -52)) * math:pow(2, 1023), #config{})
+ =:= ["1.7976931348623157e308"]
+ )
+ },
+ {"min denormalized float",
+ ?_assert(encode(float, math:pow(2, -1074), #config{}) =:= ["5.0e-324"])
+ },
+ {"max denormalized float",
+ ?_assert(
+ encode(float, (1 - math:pow(2, -52)) * math:pow(2, -1022), #config{})
+ =:= ["2.225073858507201e-308"]
+ )
+ },
+ {"hello world", ?_assert(encode(string, <<"hello world">>, #config{})
+ =:= [<<"\"">>, <<"hello world">>, <<"\"">>]
+ )},
+ {"key", ?_assert(encode(key, <<"key">>, #config{}) =:= [<<"\"">>, <<"key">>, <<"\"">>])},
+ {"1", ?_assert(encode(integer, 1, #config{}) =:= "1")},
+ {"-1", ?_assert(encode(integer, -1, #config{}) =:= "-1")},
+ {"true", ?_assert(encode(literal, true, #config{}) =:= "true")},
+ {"false", ?_assert(encode(literal, false, #config{}) =:= "false")},
+ {"null", ?_assert(encode(literal, null, #config{}) =:= "null")}
+ ].
+
+
+format_test_() ->
+ % {minified version, pretty version}
+ Cases = [
+ {"empty object", <<"{}">>, <<"{}">>},
+ {"empty array", <<"[]">>, <<"[]">>},
+ {"single key object", <<"{\"k\":\"v\"}">>, <<"{\n \"k\": \"v\"\n}">>},
+ {"single member array", <<"[true]">>, <<"[\n true\n]">>},
+ {"multiple key object",
+ <<"{\"k\":\"v\",\"x\":\"y\"}">>,
+ <<"{\n \"k\": \"v\",\n \"x\": \"y\"\n}">>
+ },
+ {"multiple member array",
+ <<"[1.0,2.0,3.0]">>,
+ <<"[\n 1.0,\n 2.0,\n 3.0\n]">>
+ },
+ {"nested structure",
+ <<"[[{},[],true],{\"k\":\"v\",\"x\":\"y\"}]">>,
+ <<"[\n [\n {},\n [],\n true\n ],\n {\n \"k\": \"v\",\n \"x\": \"y\"\n }\n]">>
+ }
+ ],
+ [{Title, ?_assertEqual(Min, jsx:minify(Pretty))} || {Title, Min, Pretty} <- Cases] ++
+ [{Title, ?_assertEqual(Pretty, jsx:prettify(Min))} || {Title, Min, Pretty} <- Cases].
+
+custom_newline_test_() ->
+ [
+ {"single key object", ?_assert(
+ jsx:format(<<"{\"k\":\"v\"}">>, [space, {indent, 2}, {newline, <<$\r>>}])
+ =:= <<"{\r \"k\": \"v\"\r}">>)
+ }
+ ].
+
+handle_event_test_() ->
+ Data = jsx:test_cases() ++ jsx:special_test_cases(),
+ [
+ {
+ Title, ?_assertEqual(
+ JSON,
+ lists:foldl(fun handle_event/2, init([]), Events ++ [end_json])
+ )
+ } || {Title, JSON, _, Events} <- Data
+ ].
+
+
+-endif.
diff --git a/server/_build/default/lib/jsx/src/jsx_to_term.erl b/server/_build/default/lib/jsx/src/jsx_to_term.erl
new file mode 100644
index 0000000..07beba4
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx_to_term.erl
@@ -0,0 +1,389 @@
+%% The MIT License
+
+%% Copyright (c) 2010-2013 Alisdair Sullivan <alisdairsullivan@yahoo.ca>
+
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+
+
+-module(jsx_to_term).
+
+-export([to_term/2]).
+-export([init/1, handle_event/2]).
+-export([
+ start_term/1,
+ start_object/1,
+ start_array/1,
+ finish/1,
+ insert/2,
+ get_key/1,
+ get_value/1
+]).
+
+
+-record(config, {
+ labels = binary,
+ return_maps = false
+}).
+
+-type config() :: proplists:proplist().
+
+-spec to_term(Source::binary(), Config::jsx_config:options()) -> jsx:json_term() | {incomplete, jsx:decoder()}.
+
+to_term(Source, Config) when is_list(Config) ->
+ (jsx:decoder(?MODULE, [return_maps] ++ Config, jsx_config:extract_config(Config)))(Source).
+
+parse_config(Config) -> parse_config(Config, #config{}).
+
+parse_config([{labels, Val}|Rest], Config)
+ when Val == binary; Val == atom; Val == existing_atom; Val == attempt_atom ->
+ parse_config(Rest, Config#config{labels = Val});
+parse_config([labels|Rest], Config) ->
+ parse_config(Rest, Config#config{labels = binary});
+parse_config([{return_maps, Val}|Rest], Config)
+ when Val == true; Val == false ->
+ parse_config(Rest, Config#config{return_maps = Val});
+parse_config([return_maps|Rest], Config) ->
+ parse_config(Rest, Config#config{return_maps = true});
+parse_config([{K, _}|Rest] = Options, Config) ->
+ case lists:member(K, jsx_config:valid_flags()) of
+ true -> parse_config(Rest, Config)
+ ; false -> erlang:error(badarg, [Options, Config])
+ end;
+parse_config([K|Rest] = Options, Config) ->
+ case lists:member(K, jsx_config:valid_flags()) of
+ true -> parse_config(Rest, Config)
+ ; false -> erlang:error(badarg, [Options, Config])
+ end;
+parse_config([], Config) ->
+ Config.
+
+
+-type state() :: {list(), #config{}}.
+-spec init(Config::config()) -> state().
+
+init(Config) -> start_term(Config).
+
+-spec handle_event(Event::any(), State::state()) -> state().
+
+handle_event(end_json, State) -> get_value(State);
+
+handle_event(start_object, State) -> start_object(State);
+handle_event(end_object, State) -> finish(State);
+
+handle_event(start_array, State) -> start_array(State);
+handle_event(end_array, State) -> finish(State);
+
+handle_event({key, Key}, {_, Config} = State) -> insert(format_key(Key, Config), State);
+
+handle_event({_, Event}, State) -> insert(Event, State).
+
+
+format_key(Key, Config) ->
+ case Config#config.labels of
+ binary -> Key
+ ; atom -> binary_to_atom(Key, utf8)
+ ; existing_atom -> binary_to_existing_atom(Key, utf8)
+ ; attempt_atom ->
+ try binary_to_existing_atom(Key, utf8) of
+ Result -> Result
+ catch
+ error:badarg -> Key
+ end
+ end.
+
+
+%% internal state is a stack and a config object
+%% `{Stack, Config}`
+%% the stack is a list of in progress objects/arrays
+%% `[Current, Parent, Grandparent,...OriginalAncestor]`
+%% an object has the representation on the stack of
+%% `{object, [
+%% {NthKey, NthValue},
+%% {NMinus1Key, NthMinus1Value},
+%% ...,
+%% {FirstKey, FirstValue}
+%% ]}`
+%% or if returning maps
+%% `{object, #{
+%% FirstKey => FirstValue,
+%% SecondKey => SecondValue,
+%% ...,
+%% NthKey => NthValue
+%% }}`
+%% or if there's a key with a yet to be matched value
+%% `{object, Key, ...}`
+%% an array looks like
+%% `{array, [NthValue, NthMinus1Value,...FirstValue]}`
+
+start_term(Config) when is_list(Config) -> {[], parse_config(Config)}.
+
+%% allocate a new object on top of the stack
+start_object({Stack, Config=#config{return_maps=true}}) ->
+ {[{object, #{}}] ++ Stack, Config};
+start_object({Stack, Config}) ->
+ {[{object, []}] ++ Stack, Config}.
+
+
+%% allocate a new array on top of the stack
+start_array({Stack, Config}) -> {[{array, []}] ++ Stack, Config}.
+
+
+%% finish an object or array and insert it into the parent object if it exists or
+%% return it if it is the root object
+finish({[{object, Map}], Config=#config{return_maps=true}}) -> {Map, Config};
+finish({[{object, Map}|Rest], Config=#config{return_maps=true}}) -> insert(Map, {Rest, Config});
+finish({[{object, []}], Config}) -> {[{}], Config};
+finish({[{object, []}|Rest], Config}) -> insert([{}], {Rest, Config});
+finish({[{object, Pairs}], Config}) -> {lists:reverse(Pairs), Config};
+finish({[{object, Pairs}|Rest], Config}) -> insert(lists:reverse(Pairs), {Rest, Config});
+finish({[{array, Values}], Config}) -> {lists:reverse(Values), Config};
+finish({[{array, Values}|Rest], Config}) -> insert(lists:reverse(Values), {Rest, Config});
+finish(_) -> erlang:error(badarg).
+
+
+%% insert a value when there's no parent object or array
+insert(Value, {[], Config}) -> {Value, Config};
+%% insert a key or value into an object or array, autodetects the 'right' thing
+insert(Key, {[{object, Map}|Rest], Config=#config{return_maps=true}}) ->
+ {[{object, Key, Map}] ++ Rest, Config};
+insert(Key, {[{object, Pairs}|Rest], Config}) ->
+ {[{object, Key, Pairs}] ++ Rest, Config};
+insert(Value, {[{object, Key, Map}|Rest], Config=#config{return_maps=true}}) ->
+ {[{object, maps:put(Key, Value, Map)}] ++ Rest, Config};
+insert(Value, {[{object, Key, Pairs}|Rest], Config}) ->
+ {[{object, [{Key, Value}] ++ Pairs}] ++ Rest, Config};
+insert(Value, {[{array, Values}|Rest], Config}) ->
+ {[{array, [Value] ++ Values}] ++ Rest, Config};
+insert(_, _) -> erlang:error(badarg).
+
+get_key({[{object, Key, _}|_], _}) -> Key;
+get_key(_) -> erlang:error(badarg).
+
+
+get_value({Value, _Config}) -> Value;
+get_value(_) -> erlang:error(badarg).
+
+
+
+%% eunit tests
+
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+
+config_test_() ->
+ [
+ {"empty config", ?_assertEqual(#config{}, parse_config([]))},
+ {"implicit binary labels", ?_assertEqual(#config{}, parse_config([labels]))},
+ {"binary labels", ?_assertEqual(#config{}, parse_config([{labels, binary}]))},
+ {"atom labels", ?_assertEqual(#config{labels=atom}, parse_config([{labels, atom}]))},
+ {"existing atom labels", ?_assertEqual(
+ #config{labels=existing_atom},
+ parse_config([{labels, existing_atom}])
+ )},
+ {"return_maps true", ?_assertEqual(
+ #config{return_maps=true},
+ parse_config([return_maps])
+ )},
+ {"invalid opt flag", ?_assertError(badarg, parse_config([error]))},
+ {"invalid opt tuple", ?_assertError(badarg, parse_config([{error, true}]))}
+ ].
+
+
+format_key_test_() ->
+ [
+ {"binary key", ?_assertEqual(<<"key">>, format_key(<<"key">>, #config{labels=binary}))},
+ {"atom key", ?_assertEqual(key, format_key(<<"key">>, #config{labels=atom}))},
+ {"existing atom key", ?_assertEqual(
+ key,
+ format_key(<<"key">>, #config{labels=existing_atom})
+ )},
+ {"nonexisting atom key", ?_assertError(
+ badarg,
+ format_key(<<"nonexistentatom">>, #config{labels=existing_atom})
+ )},
+ {"sloppy existing atom key", ?_assertEqual(
+ key,
+ format_key(<<"key">>, #config{labels=attempt_atom})
+ )},
+ {"nonexisting atom key", ?_assertEqual(
+ <<"nonexistentatom">>,
+ format_key(<<"nonexistentatom">>, #config{labels=attempt_atom})
+ )}
+ ].
+
+
+rep_manipulation_test_() ->
+ [
+ {"allocate a new context with option", ?_assertEqual(
+ {[], #config{labels=atom}},
+ start_term([{labels, atom}])
+ )},
+ {"allocate a new object on an empty stack", ?_assertEqual(
+ {[{object, []}], #config{}},
+ start_object({[], #config{}})
+ )},
+ {"allocate a new object on a stack", ?_assertEqual(
+ {[{object, []}, {object, []}], #config{}},
+ start_object({[{object, []}], #config{}})
+ )},
+ {"allocate a new array on an empty stack", ?_assertEqual(
+ {[{array, []}], #config{}},
+ start_array({[], #config{}})
+ )},
+ {"allocate a new array on a stack", ?_assertEqual(
+ {[{array, []}, {object, []}], #config{}},
+ start_array({[{object, []}], #config{}})
+ )},
+ {"insert a key into an object", ?_assertEqual(
+ {[{object, key, []}, junk], #config{}},
+ insert(key, {[{object, []}, junk], #config{}})
+ )},
+ {"get current key", ?_assertEqual(
+ key,
+ get_key({[{object, key, []}], #config{}})
+ )},
+ {"try to get non-key from object", ?_assertError(
+ badarg,
+ get_key({[{object, []}], #config{}})
+ )},
+ {"try to get key from array", ?_assertError(
+ badarg,
+ get_key({[{array, []}], #config{}})
+ )},
+ {"insert a value into an object", ?_assertEqual(
+ {[{object, [{key, value}]}, junk], #config{}},
+ insert(value, {[{object, key, []}, junk], #config{}})
+ )},
+ {"insert a value into an array", ?_assertEqual(
+ {[{array, [value]}, junk], #config{}},
+ insert(value, {[{array, []}, junk], #config{}})
+ )},
+ {"finish an object with no ancestor", ?_assertEqual(
+ {[{a, b}, {x, y}], #config{}},
+ finish({[{object, [{x, y}, {a, b}]}], #config{}})
+ )},
+ {"finish an empty object", ?_assertEqual(
+ {[{}], #config{}},
+ finish({[{object, []}], #config{}})
+ )},
+ {"finish an object with an ancestor", ?_assertEqual(
+ {[{object, [{key, [{a, b}, {x, y}]}, {foo, bar}]}], #config{}},
+ finish({[{object, [{x, y}, {a, b}]}, {object, key, [{foo, bar}]}], #config{}})
+ )},
+ {"finish an array with no ancestor", ?_assertEqual(
+ {[a, b, c], #config{}},
+ finish({[{array, [c, b, a]}], #config{}})
+ )},
+ {"finish an array with an ancestor", ?_assertEqual(
+ {[{array, [[a, b, c], d, e, f]}], #config{}},
+ finish({[{array, [c, b, a]}, {array, [d, e, f]}], #config{}})
+ )}
+ ].
+
+
+rep_manipulation_with_maps_test_() ->
+ [
+ {"allocate a new object on an empty stack", ?_assertEqual(
+ {[{object, #{}}], #config{return_maps=true}},
+ start_object({[], #config{return_maps=true}})
+ )},
+ {"allocate a new object on a stack", ?_assertEqual(
+ {[{object, #{}}, {object, #{}}], #config{return_maps=true}},
+ start_object({[{object, #{}}], #config{return_maps=true}})
+ )},
+ {"insert a key into an object", ?_assertEqual(
+ {[{object, key, #{}}, junk], #config{return_maps=true}},
+ insert(key, {[{object, #{}}, junk], #config{return_maps=true}})
+ )},
+ {"get current key", ?_assertEqual(
+ key,
+ get_key({[{object, key, #{}}], #config{return_maps=true}})
+ )},
+ {"try to get non-key from object", ?_assertError(
+ badarg,
+ get_key({[{object, #{}}], #config{return_maps=true}})
+ )},
+ {"insert a value into an object", ?_assertEqual(
+ {[{object, #{key => value}}, junk], #config{return_maps=true}},
+ insert(value, {[{object, key, #{}}, junk], #config{return_maps=true}})
+ )},
+ {"finish an object with no ancestor", ?_assertEqual(
+ {#{a => b, x => y}, #config{return_maps=true}},
+ finish({[{object, #{x => y, a => b}}], #config{return_maps=true}})
+ )},
+ {"finish an empty object", ?_assertEqual(
+ {#{}, #config{return_maps=true}},
+ finish({[{object, #{}}], #config{return_maps=true}})
+ )},
+ {"finish an object with an ancestor", ?_assertEqual(
+ {
+ [{object, #{key => #{a => b, x => y}, foo => bar}}],
+ #config{return_maps=true}
+ },
+ finish({
+ [{object, #{x => y, a => b}}, {object, key, #{foo => bar}}],
+ #config{return_maps=true}
+ })
+ )}
+ ].
+
+
+return_maps_test_() ->
+ [
+ {"an empty map", ?_assertEqual(
+ #{},
+ jsx:decode(<<"{}">>, [])
+ )},
+ {"an empty map", ?_assertEqual(
+ #{},
+ jsx:decode(<<"{}">>, [])
+ )},
+ {"an empty map", ?_assertEqual(
+ [{}],
+ jsx:decode(<<"{}">>, [{return_maps, false}])
+ )},
+ {"a small map", ?_assertEqual(
+ #{<<"awesome">> => true, <<"library">> => <<"jsx">>},
+ jsx:decode(<<"{\"library\": \"jsx\", \"awesome\": true}">>, [])
+ )},
+ {"a recursive map", ?_assertEqual(
+ #{<<"key">> => #{<<"key">> => true}},
+ jsx:decode(<<"{\"key\": {\"key\": true}}">>, [])
+ )},
+ {"a map inside a list", ?_assertEqual(
+ [#{}],
+ jsx:decode(<<"[{}]">>, [])
+ )}
+ ].
+
+
+handle_event_test_() ->
+ Data = jsx:test_cases(),
+ [
+ {
+ Title, ?_assertEqual(
+ Term,
+ lists:foldl(fun handle_event/2, init([]), Events ++ [end_json])
+ )
+ } || {Title, _, Term, Events} <- Data
+ ].
+
+
+-endif.
diff --git a/server/_build/default/lib/jsx/src/jsx_verify.erl b/server/_build/default/lib/jsx/src/jsx_verify.erl
new file mode 100644
index 0000000..5eef4d2
--- /dev/null
+++ b/server/_build/default/lib/jsx/src/jsx_verify.erl
@@ -0,0 +1,121 @@
+%% The MIT License
+
+%% Copyright (c) 2010-2013 alisdair sullivan <alisdairsullivan@yahoo.ca>
+
+%% Permission is hereby granted, free of charge, to any person obtaining a copy
+%% of this software and associated documentation files (the "Software"), to deal
+%% in the Software without restriction, including without limitation the rights
+%% to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+%% copies of the Software, and to permit persons to whom the Software is
+%% furnished to do so, subject to the following conditions:
+
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+%% AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+%% OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+%% THE SOFTWARE.
+
+
+-module(jsx_verify).
+
+-export([is_json/2, is_term/2]).
+-export([init/1, handle_event/2]).
+
+-type config() :: proplists:proplist().
+
+-spec is_json(Source::binary(), Config::jsx_config:options()) -> true | false | {incomplete, jsx:decoder()}.
+
+is_json(Source, Config) when is_list(Config) ->
+ try (jsx:decoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
+ catch error:badarg -> false
+ end.
+
+
+-spec is_term(Source::jsx:json_term() | end_stream | end_json,
+ Config::jsx_config:options()) -> true | false | {incomplete, jsx:encoder()}.
+
+is_term(Source, Config) when is_list(Config) ->
+ try (jsx:encoder(?MODULE, Config, jsx_config:extract_config(Config)))(Source)
+ catch error:badarg -> false
+ end.
+
+
+parse_config(Config) -> parse_config(Config, []).
+
+%% ignore deprecated flags
+parse_config([no_repeated_keys|Rest], Config) ->
+ parse_config(Rest, Config);
+parse_config([{repeated_keys, Val}|Rest], Config) when Val == true; Val == false ->
+ parse_config(Rest, Config);
+parse_config([repeated_keys|Rest], Config) ->
+ parse_config(Rest, Config);
+parse_config([{K, _}|Rest] = Options, Config) ->
+ case lists:member(K, jsx_config:valid_flags()) of
+ true -> parse_config(Rest, Config);
+ false -> erlang:error(badarg, [Options, Config])
+ end;
+parse_config([K|Rest] = Options, Config) ->
+ case lists:member(K, jsx_config:valid_flags()) of
+ true -> parse_config(Rest, Config);
+ false -> erlang:error(badarg, [Options, Config])
+ end;
+parse_config([], Config) ->
+ Config.
+
+
+%% we don't actually need any state for this
+-type state() :: [].
+-spec init(Config::config()) -> state().
+
+init(Config) -> parse_config(Config).
+
+
+-spec handle_event(Event::any(), State::state()) -> state().
+
+handle_event(end_json, _) -> true;
+
+handle_event(_, State) -> State.
+
+
+
+%% eunit tests
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+
+config_test_() ->
+ [
+ {"empty config", ?_assertEqual([], parse_config([]))},
+ {"no repeat keys", ?_assertEqual([], parse_config([no_repeated_keys]))},
+ {"bare repeated keys", ?_assertEqual([], parse_config([repeated_keys]))},
+ {"repeated keys true", ?_assertEqual(
+ [],
+ parse_config([{repeated_keys, true}])
+ )},
+ {"repeated keys false", ?_assertEqual(
+ [],
+ parse_config([{repeated_keys, false}])
+ )},
+ {"invalid opt flag", ?_assertError(badarg, parse_config([error]))},
+ {"invalid opt tuple", ?_assertError(badarg, parse_config([{error, true}]))}
+ ].
+
+
+handle_event_test_() ->
+ Data = jsx:test_cases() ++ jsx:special_test_cases(),
+ [
+ {
+ Title, ?_assertEqual(
+ true,
+ lists:foldl(fun handle_event/2, [], Events ++ [end_json])
+ )
+ } || {Title, _, _, Events} <- Data
+ ].
+
+
+-endif.
diff --git a/server/_build/default/lib/jwt/LICENSE b/server/_build/default/lib/jwt/LICENSE
new file mode 100644
index 0000000..0e00cef
--- /dev/null
+++ b/server/_build/default/lib/jwt/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Katō
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/server/_build/default/lib/jwt/README.md b/server/_build/default/lib/jwt/README.md
new file mode 100644
index 0000000..09e56f6
--- /dev/null
+++ b/server/_build/default/lib/jwt/README.md
@@ -0,0 +1,101 @@
+
+
+# jwt — Erlang JWT Library #
+---------
+
+[![Build Status](https://travis-ci.org/artemeff/jwt.svg?branch=master)
+](https://travis-ci.org/artemeff/jwt)
+[![Coverage Status](https://coveralls.io/repos/github/artemeff/jwt/badge.svg?branch=master)
+](https://coveralls.io/github/artemeff/jwt?branch=master)
+[![Hex.pm](https://img.shields.io/hexpm/v/jwt.svg)
+](https://hex.pm/packages/jwt)
+
+---------
+
+JWT is a simple authorization token [format](https://jwt.io/) based on JSON.
+
+
+#### <a name="Installation">Installation</a> ####
+
+If you use rebar (supports both 2 and 3 versions) or mix (Elixir):
+
+```erlang
+
+% in rebar.config for rebar3
+{deps, [{jwt}]}.
+
+% or for rebar2
+{deps, [{jwt, ".*", {git, "https://github.com/artemeff/jwt", {tag, "0.1.0"}}}]}
+
+```
+
+```elixir
+
+% mix.exs
+def deps do
+ [{:jwt, "~> 0.1"}]
+end
+
+```
+
+Or use it as git dependency.
+
+
+#### <a name="Usage_example">Usage example</a> ####
+
+```erlang
+
+%% Create JWT token
+> application:ensure_all_started(jwt).
+> Key = <<"supas3cri7">>.
+> Claims = [
+ {user_id, 42},
+ {user_name, <<"Bob">>}
+ ].
+> {ok, Token} = jwt:encode(<<"HS256">>, Claims, Key).
+%% or with expiration
+> ExpirationSeconds = 86400.
+> {ok, Token} = jwt:encode(<<"HS256">>, Claims, ExpirationSeconds, Key).
+
+%% Parse JWT token
+> {ok, Claims} = jwt:decode(Token, Key).
+%% Issuer specific keys workflow
+
+%% The encoder just knows about itself
+> Issuer = <<"iss1">>.
+> IssuerKey = <<"Issuer-1-Key">>.
+> Claims2 = [
+ {iss, Issuer},
+ {user_id, 42},
+ {user_name, <<"Bob">>}
+ ].
+> {ok, Token2} = jwt:encode(<<"HS256">>, Claims, ExpirationSeconds, IssuerKey).
+
+%% Decoder Workflow
+%% The decoder knows about all encoder keys (issuer specific)
+> IssuerKeyMapping = #{ Issuer => IssuerKey,
+ <<"iss2">> => <<"Issuer2Key">>}.
+> {ok, Claims} = jwt:decode(Token, <<"default-key">>, IssuerKeyMapping).
+
+```
+---------
+
+
+### <a name="Contributing">Contributing</a> ###
+* Fork it
+* Create your feature branch (`git checkout -b my-new-feature`)
+* Commit your changes (`git commit -am 'add some feature'`)
+* Push to the branch (`git push origin my-new-feature`)
+* Create new Pull Request
+
+
+
+
+## Modules ##
+
+
+<table width="100%" border="0" summary="list of modules">
+<tr><td><a href="http://github.com/artemeff/jwt/blob/master/doc/jwk.md" class="module">jwk</a></td></tr>
+<tr><td><a href="http://github.com/artemeff/jwt/blob/master/doc/jwt.md" class="module">jwt</a></td></tr>
+<tr><td><a href="http://github.com/artemeff/jwt/blob/master/doc/jwt_ecdsa.md" class="module">jwt_ecdsa</a></td></tr></table>
+
diff --git a/server/_build/default/lib/jwt/ebin/jwk.beam b/server/_build/default/lib/jwt/ebin/jwk.beam
new file mode 100644
index 0000000..c583f9c
--- /dev/null
+++ b/server/_build/default/lib/jwt/ebin/jwk.beam
Binary files differ
diff --git a/server/_build/default/lib/jwt/ebin/jwt.app b/server/_build/default/lib/jwt/ebin/jwt.app
new file mode 100644
index 0000000..c792f7d
--- /dev/null
+++ b/server/_build/default/lib/jwt/ebin/jwt.app
@@ -0,0 +1,9 @@
+{application,jwt,
+ [{description,"Erlang JWT library"},
+ {vsn,"0.1.11"},
+ {registered,[]},
+ {applications,[kernel,stdlib,crypto,public_key,jsx,base64url]},
+ {env,[]},
+ {licenses,["MIT"]},
+ {links,[{"GitHub","https://github.com/artemeff/jwt"}]},
+ {modules,[jwk,jwt,jwt_ecdsa]}]}.
diff --git a/server/_build/default/lib/jwt/ebin/jwt.beam b/server/_build/default/lib/jwt/ebin/jwt.beam
new file mode 100644
index 0000000..fe6332a
--- /dev/null
+++ b/server/_build/default/lib/jwt/ebin/jwt.beam
Binary files differ
diff --git a/server/_build/default/lib/jwt/ebin/jwt_ecdsa.beam b/server/_build/default/lib/jwt/ebin/jwt_ecdsa.beam
new file mode 100644
index 0000000..cb8ffde
--- /dev/null
+++ b/server/_build/default/lib/jwt/ebin/jwt_ecdsa.beam
Binary files differ
diff --git a/server/_build/default/lib/jwt/hex_metadata.config b/server/_build/default/lib/jwt/hex_metadata.config
new file mode 100644
index 0000000..6de7750
--- /dev/null
+++ b/server/_build/default/lib/jwt/hex_metadata.config
@@ -0,0 +1,21 @@
+{<<"app">>,<<"jwt">>}.
+{<<"build_tools">>,[<<"rebar3">>]}.
+{<<"description">>,<<"Erlang JWT library">>}.
+{<<"files">>,
+ [<<"LICENSE">>,<<"README.md">>,<<"include/jwt_ecdsa.hrl">>,
+ <<"rebar.config">>,<<"rebar.config.script">>,<<"rebar.lock">>,
+ <<"src/jwk.erl">>,<<"src/jwt.app.src">>,<<"src/jwt.erl">>,
+ <<"src/jwt_ecdsa.erl">>]}.
+{<<"licenses">>,[<<"MIT">>]}.
+{<<"links">>,[{<<"GitHub">>,<<"https://github.com/artemeff/jwt">>}]}.
+{<<"name">>,<<"jwt">>}.
+{<<"requirements">>,
+ [{<<"base64url">>,
+ [{<<"app">>,<<"base64url">>},
+ {<<"optional">>,false},
+ {<<"requirement">>,<<"~>0.0.1">>}]},
+ {<<"jsx">>,
+ [{<<"app">>,<<"jsx">>},
+ {<<"optional">>,false},
+ {<<"requirement">>,<<"~>2.8.0">>}]}]}.
+{<<"version">>,<<"0.1.11">>}.
diff --git a/server/_build/default/lib/jwt/include/jwt_ecdsa.hrl b/server/_build/default/lib/jwt/include/jwt_ecdsa.hrl
new file mode 100644
index 0000000..7f58843
--- /dev/null
+++ b/server/_build/default/lib/jwt/include/jwt_ecdsa.hrl
@@ -0,0 +1,49 @@
+-define(EC_GROUP_DEGREE, #{
+ sect571r1 => 571,
+ sect571k1 => 571,
+ sect409r1 => 409,
+ sect409k1 => 409,
+ secp521r1 => 521,
+ secp384r1 => 384,
+ secp224r1 => 224,
+ secp224k1 => 224,
+ secp192k1 => 192,
+ secp160r2 => 160,
+ secp128r2 => 128,
+ secp128r1 => 128,
+ sect233r1 => 233,
+ sect233k1 => 233,
+ sect193r2 => 193,
+ sect193r1 => 193,
+ sect131r2 => 131,
+ sect131r1 => 131,
+ sect283r1 => 283,
+ sect283k1 => 283,
+ sect163r2 => 163,
+ secp256k1 => 256,
+ secp160k1 => 160,
+ secp160r1 => 160,
+ secp112r2 => 112,
+ secp112r1 => 112,
+ sect113r2 => 113,
+ sect113r1 => 113,
+ sect239k1 => 239,
+ sect163r1 => 163,
+ sect163k1 => 163,
+ secp256r1 => 256,
+ secp192r1 => 192,
+ brainpoolP160r1 => 160,
+ brainpoolP160t1 => 160,
+ brainpoolP192r1 => 192,
+ brainpoolP192t1 => 192,
+ brainpoolP224r1 => 224,
+ brainpoolP224t1 => 224,
+ brainpoolP256r1 => 256,
+ brainpoolP256t1 => 256,
+ brainpoolP320r1 => 320,
+ brainpoolP320t1 => 320,
+ brainpoolP384r1 => 384,
+ brainpoolP384t1 => 384,
+ brainpoolP512r1 => 512,
+ brainpoolP512t1 => 512
+}).
diff --git a/server/_build/default/lib/jwt/rebar.config b/server/_build/default/lib/jwt/rebar.config
new file mode 100644
index 0000000..3d00da9
--- /dev/null
+++ b/server/_build/default/lib/jwt/rebar.config
@@ -0,0 +1,22 @@
+{erl_opts, [warnings_as_errors]}.
+{deps, [base64url, jsx]}.
+{plugins, [coveralls]}.
+{eunit_compile_opts, [export_all]}.
+
+{profiles, [
+ {edown, [
+ {deps, [
+ {edown, "0.8.1"}
+ ]},
+ {edoc_opts, [
+ {doclet, edown_doclet},
+ {top_level_readme,
+ {"./README.md", "http://github.com/artemeff/jwt", "master"}}
+ ]}
+ ]}
+]}.
+
+{cover_enabled, true}.
+{cover_export_enabled, true}.
+{coveralls_coverdata, "_build/test/cover/eunit.coverdata"}.
+{coveralls_service_name, "travis-ci"}.
diff --git a/server/_build/default/lib/jwt/rebar.config.script b/server/_build/default/lib/jwt/rebar.config.script
new file mode 100644
index 0000000..45cbf92
--- /dev/null
+++ b/server/_build/default/lib/jwt/rebar.config.script
@@ -0,0 +1,35 @@
+IsRebar3OrMix = case application:get_env(rebar, vsn) of
+ {ok, VSN} ->
+ [Major|_] = string:tokens(VSN, "."),
+ (list_to_integer(Major) >= 3);
+
+ undefined ->
+ %% mix is used?
+ lists:keymember(mix, 1, application:loaded_applications())
+ end,
+
+GitDeps =
+ [ {base64url, ".*", {git, "https://github.com/dvv/base64url", {tag, "v1.0"}}}
+ , {jsx, ".*", {git, "https://github.com/talentdeficit/jsx", {tag, "2.8.0"}}}
+ ],
+
+Config = case IsRebar3OrMix of
+ true -> CONFIG;
+ _ -> lists:keyreplace(deps, 1, CONFIG, {deps, GitDeps})
+end,
+
+ConfigCI = case os:getenv("CI") of
+ "true" ->
+ Plugins = [rebar3_lint | proplists:get_value(plugins, Config, [])],
+ lists:keystore(plugins, 1, Config, {plugins, Plugins});
+ _ ->
+ Config
+end,
+
+case os:getenv("TRAVIS") of
+ "true" ->
+ JobId = os:getenv("TRAVIS_JOB_ID"),
+ lists:keystore(coveralls_service_job_id, 1, ConfigCI, {coveralls_service_job_id, JobId});
+ _ ->
+ ConfigCI
+end.
diff --git a/server/_build/default/lib/jwt/rebar.lock b/server/_build/default/lib/jwt/rebar.lock
new file mode 100644
index 0000000..8e1c439
--- /dev/null
+++ b/server/_build/default/lib/jwt/rebar.lock
@@ -0,0 +1,11 @@
+{"1.2.0",
+[{<<"base64url">>,{pkg,<<"base64url">>,<<"0.0.1">>},0},
+ {<<"jsx">>,{pkg,<<"jsx">>,<<"2.8.0">>},0}]}.
+[
+{pkg_hash,[
+ {<<"base64url">>, <<"36A90125F5948E3AFD7BE97662A1504B934DD5DAC78451CA6E9ABF85A10286BE">>},
+ {<<"jsx">>, <<"749BEC6D205C694AE1786D62CEA6CC45A390437E24835FD16D12D74F07097727">>}]},
+{pkg_hash_ext,[
+ {<<"base64url">>, <<"FAB09B20E3F5DB886725544CBCF875B8E73EC93363954EB8A1A9ED834AA8C1F9">>},
+ {<<"jsx">>, <<"A8BA15D5BAC2C48B2BE1224A0542AD794538D79E2CC16841A4E24CA75F0F8378">>}]}
+].
diff --git a/server/_build/default/lib/jwt/src/jwk.erl b/server/_build/default/lib/jwt/src/jwk.erl
new file mode 100644
index 0000000..9226aae
--- /dev/null
+++ b/server/_build/default/lib/jwt/src/jwk.erl
@@ -0,0 +1,69 @@
+%% @doc RFC 7517: JSON Web Key (JWK)
+
+-module(jwk).
+-include_lib("public_key/include/OTP-PUB-KEY.hrl").
+
+-export([encode/2, decode/2]).
+
+-type id() :: binary().
+-type public_key() :: #'RSAPublicKey'{} | pem().
+-type pem() :: binary().
+-type json() :: binary().
+
+-spec encode(id(), public_key()) -> {ok, json()} | {error, _}.
+%% @doc encode Erlang/OTP Key to JWK
+encode(Id, #'RSAPublicKey'{modulus = N, publicExponent = E}) ->
+ {ok, jsx:encode(
+ #{
+ keys =>
+ [
+ #{
+ kid => Id,
+ kty => <<"RSA">>,
+ n => encode_int(N),
+ e => encode_int(E)
+ }
+ ]
+ }
+ )};
+encode(Id, PEM) when is_binary(PEM) ->
+ [RSAEntry] = public_key:pem_decode(PEM),
+ encode(Id, public_key:pem_entry_decode(RSAEntry));
+encode(_, _) ->
+ {error, not_supported}.
+
+-spec decode(id(), json()) -> {ok, public_key()} | {error, _}.
+%% @doc decode JWK to Erlang/OTP Key
+decode(Id, #{<<"keys">> := JWTs}) ->
+ decode(
+ lists:dropwhile(
+ fun(X) ->
+ maps:get(<<"kid">>, X, undefined) /= Id
+ end,
+ JWTs
+ )
+ );
+decode(Id, Json) when is_binary(Json) ->
+ decode(Id, jsx:decode(Json, [return_maps])).
+
+%% @private
+decode([#{<<"kty">> := <<"RSA">>, <<"n">> := N, <<"e">> := E} | _]) ->
+ {ok,
+ #'RSAPublicKey'{
+ modulus = decode_int(N),
+ publicExponent = decode_int(E)
+ }
+ };
+decode([]) ->
+ {error, not_found};
+decode(_) ->
+ {error, not_supported}.
+
+
+%% @private
+encode_int(X) ->
+ base64url:encode(binary:encode_unsigned(X)).
+
+%% @private
+decode_int(X) ->
+ binary:decode_unsigned(base64url:decode(X)).
diff --git a/server/_build/default/lib/jwt/src/jwt.app.src b/server/_build/default/lib/jwt/src/jwt.app.src
new file mode 100644
index 0000000..b39bcb6
--- /dev/null
+++ b/server/_build/default/lib/jwt/src/jwt.app.src
@@ -0,0 +1,8 @@
+{application,jwt,
+ [{description,"Erlang JWT library"},
+ {vsn,"0.1.11"},
+ {registered,[]},
+ {applications,[kernel,stdlib,crypto,public_key,jsx,base64url]},
+ {env,[]},
+ {licenses,["MIT"]},
+ {links,[{"GitHub","https://github.com/artemeff/jwt"}]}]}.
diff --git a/server/_build/default/lib/jwt/src/jwt.erl b/server/_build/default/lib/jwt/src/jwt.erl
new file mode 100644
index 0000000..b17b679
--- /dev/null
+++ b/server/_build/default/lib/jwt/src/jwt.erl
@@ -0,0 +1,340 @@
+%% @doc JWT Library for Erlang.
+%%
+%% Written by Peter Hizalev at Kato (http://kato.im)
+%%
+%% Rewritten by Yuri Artemev (http://artemff.com)
+%%
+%% @end
+-module(jwt).
+
+-export([decode/2, decode/3]).
+-export([encode/3, encode/4]).
+
+-define(HOUR, 3600).
+-define(DAY, (?HOUR * 24)).
+
+%% Handle version compatibility for crypto
+-ifdef(OTP_RELEASE).
+ -if(?OTP_RELEASE >= 23).
+ -define(HMAC(Type, Key, Data), crypto:mac(hmac, Type, Key, Data)).
+ -else.
+ -define(HMAC(Type, Key, Data), crypto:hmac(Type, Key, Data)).
+ -endif.
+-else.
+ -define(HMAC(Type, Key, Data), crypto:hmac(Type, Key, Data)).
+-endif.
+
+-type expiration() :: {hourly, non_neg_integer()} | {daily, non_neg_integer()} | non_neg_integer().
+-type context() :: map().
+
+%%
+%% API
+%%
+-spec encode(
+ Alg :: binary(),
+ ClaimsSet :: map() | list(),
+ Key :: binary() | public_key:private_key()
+) -> {ok, Token :: binary()} | {error, any()}.
+%% @doc Creates a token from given data and signs it with a given secret
+%%
+%% Parameters are
+%% <ul>
+%% <li>
+%% `Alg' is a binary one of
+%%
+%% [HS256, HS384, HS512, RS256, RS384, RS512, ES256, ES384, ES512, PS256, PS384, PS512]
+%%
+%% But only [HS256, HS384, HS512, RS256] are supported
+%% </li>
+%% <li>`ClaimsSet' the payload of the token. Can be both map and proplist</li>
+%% <li>`Key' binary in case of hmac encryption and private key if rsa</li>
+%% </ul>
+%%
+%% @end
+encode(Alg, ClaimsSet, Key) ->
+ Claims = base64url:encode(jsx:encode(ClaimsSet)),
+ Header = base64url:encode(jsx:encode(jwt_header(Alg))),
+ Payload = <<Header/binary, ".", Claims/binary>>,
+ case jwt_sign(Alg, Payload, Key) of
+ undefined -> {error, algorithm_not_supported};
+ Signature -> {ok, <<Payload/binary, ".", Signature/binary>>}
+ end.
+
+-spec encode(
+ Alg :: binary(),
+ ClaimsSet :: map() | list(),
+ Expiration :: expiration(),
+ Key :: binary() | public_key:private_key()
+) -> {ok, Token :: binary()} | {error, any()}.
+%% @doc Creates a token from given data and signs it with a given secret
+%%
+%% and also adds `exp' claim to payload
+%%
+%% `Expiration' can be one of the tuples:
+%% `{hourly, SecondsAfterBeginningOfCurrentHour}',
+%% `{daily, SecondsAfterBeginningOfCurrentDay}'
+%% or can be just an integer representing the amount of seconds
+%% the token will live
+%%
+%% @end
+encode(Alg, ClaimsSet, Expiration, Key) ->
+ Claims = jwt_add_exp(ClaimsSet, Expiration),
+ encode(Alg, Claims, Key).
+
+-spec decode(
+ Token :: binary(),
+ Key :: binary() | public_key:public_key() | public_key:private_key()
+) -> {ok, Claims :: map()} | {error, any()}.
+%% @doc Decodes a token, checks the signature and returns the content of the token
+%%
+%% <ul>
+%% <li>`Token' is a JWT itself</li>
+%% <li>`Key' is a secret phrase or public/private key depend on encryption algorithm</li>
+%% </ul>
+%%
+%% @end
+decode(Token, Key) ->
+ decode(Token, Key, #{}).
+
+% When there are multiple issuers and keys are on a per issuer bases
+% then apply those keys instead
+-spec decode(
+ Token :: binary(),
+ DefaultKey :: binary() | public_key:public_key() | public_key:private_key(),
+ IssuerKeyMapping :: map()
+) -> {ok, Claims :: map()} | {error, any()}.
+%% @doc Decode with an issuer key mapping
+%%
+%% Receives the issuer key mapping as the last parameter
+%%
+%% @end
+decode(Token, DefaultKey, IssuerKeyMapping) ->
+ result(reduce_while(fun(F, Acc) -> apply(F, [Acc]) end, #{token => Token}, [
+ fun split_token/1,
+ fun decode_jwt/1,
+ fun (Context) ->
+ get_key(Context, DefaultKey, IssuerKeyMapping)
+ end,
+ fun check_signature/1,
+ fun check_expired/1
+ ])).
+
+result(#{claims_json := ClaimsJSON}) ->
+ {ok, ClaimsJSON};
+result({error, _} = Error) ->
+ Error.
+
+reduce_while(_Fun, Acc, []) ->
+ Acc;
+reduce_while(Fun, Acc, [Item|Rest]) ->
+ case Fun(Item, Acc) of
+ {cont, NewAcc} ->
+ reduce_while(Fun, NewAcc, Rest);
+ {halt, Result} ->
+ Result
+ end.
+
+-spec split_token(Context :: context()) ->
+ {cont, context()} | {halt, {error, invalid_token}}.
+%% @private
+split_token(#{token := Token} = Context) ->
+ case binary:split(Token, <<".">>, [global]) of
+ [Header, Claims, Signature] ->
+ {cont, maps:merge(Context, #{
+ header => Header,
+ claims => Claims,
+ signature => Signature
+ })};
+ _ ->
+ {halt, {error, invalid_token}}
+ end.
+
+-spec decode_jwt(context()) -> {cont, context()} | {halt, {error, invalid_token}}.
+%% @private
+decode_jwt(#{header := Header, claims := Claims} = Context) ->
+ try
+ [HeaderJSON, ClaimsJSON] =
+ Decoded = [jsx_decode_safe(base64url:decode(X)) || X <- [Header, Claims]],
+ case lists:any(fun(E) -> E =:= invalid end, Decoded) of
+ false ->
+ {cont, maps:merge(Context, #{
+ header_json => HeaderJSON,
+ claims_json => ClaimsJSON
+ })};
+ true ->
+ {halt, {error, invalid_token}}
+ end
+ catch _:_ ->
+ {halt, {error, invalid_token}}
+ end.
+
+%% @private
+get_key(#{claims_json := Claims} = Context, DefaultKey, IssuerKeyMapping) ->
+ Issuer = maps:get(<<"iss">>, Claims, undefined),
+ Key = maps:get(Issuer, IssuerKeyMapping, DefaultKey),
+ {cont, maps:merge(Context, #{key => Key})}.
+
+%% @private
+check_signature(#{
+ key := Key,
+ header := Header,
+ claims := Claims,
+ signature := Signature,
+ header_json := #{<<"alg">> := Alg}
+} = Context) ->
+ case jwt_check_sig(Alg, Header, Claims, Signature, Key) of
+ true ->
+ {cont, Context};
+ false ->
+ {halt, {error, invalid_signature}}
+ end.
+
+%% @private
+check_expired(#{claims_json := ClaimsJSON} = Context) ->
+ case jwt_is_expired(ClaimsJSON) of
+ true ->
+ {halt, {error, expired}};
+ false ->
+ {cont, Context}
+ end.
+
+%%
+%% Decoding helpers
+%%
+-spec jsx_decode_safe(binary()) -> map() | invalid.
+%% @private
+jsx_decode_safe(Bin) ->
+ try
+ jsx:decode(Bin, [return_maps])
+ catch _ ->
+ invalid
+ end.
+
+-spec jwt_is_expired(map()) -> boolean().
+%% @private
+jwt_is_expired(#{<<"exp">> := Exp} = _ClaimsJSON) ->
+ case (Exp - epoch()) of
+ DeltaSecs when DeltaSecs > 0 -> false;
+ _ -> true
+ end;
+jwt_is_expired(_) ->
+ false.
+
+-spec jwt_check_sig(
+ Alg :: binary(),
+ Header :: binary(),
+ Claims :: binary(),
+ Signature :: binary(),
+ Key :: binary() | public_key:public_key() | public_key:private_key()
+) -> boolean().
+%% @private
+jwt_check_sig(Alg, Header, Claims, Signature, Key) ->
+ jwt_check_sig(algorithm_to_crypto(Alg), <<Header/binary, ".", Claims/binary>>, Signature, Key).
+
+-spec jwt_check_sig(
+ {atom(), atom()},
+ Payload :: binary(),
+ Signature :: binary(),
+ Key :: binary() | public_key:public_key() | public_key:private_key()
+) -> boolean().
+%% @private
+jwt_check_sig({hmac, _} = Alg, Payload, Signature, Key) ->
+ jwt_sign_with_crypto(Alg, Payload, Key) =:= Signature;
+
+jwt_check_sig({Algo, Crypto}, Payload, Signature, Pem)
+ when (Algo =:= rsa orelse Algo =:= ecdsa) andalso is_binary(Pem) ->
+ jwt_check_sig({Algo, Crypto}, Payload, Signature, pem_to_key(Pem));
+
+jwt_check_sig({rsa, Crypto}, Payload, Signature, Key) ->
+ public_key:verify(Payload, Crypto, base64url:decode(Signature), Key);
+
+jwt_check_sig({ecdsa, Crypto}, Payload, Signature, Key) ->
+ public_key:verify(Payload, Crypto, jwt_ecdsa:signature(Signature), Key);
+
+jwt_check_sig(_, _, _, _) ->
+ false.
+
+%%
+%% Encoding helpers
+%%
+-spec jwt_add_exp(ClaimsSet :: map() | list(), Expiration :: expiration()) -> map() | list().
+%% @private
+jwt_add_exp(ClaimsSet, Expiration) ->
+ Exp = expiration_to_epoch(Expiration),
+ append_claim(ClaimsSet, <<"exp">>, Exp).
+
+-spec jwt_header(Alg :: binary()) -> list().
+jwt_header(Alg) ->
+ [ {<<"alg">>, Alg}
+ , {<<"typ">>, <<"JWT">>}
+ ].
+
+%%
+%% Helpers
+%%
+-spec jwt_sign(
+ Alg :: binary(),
+ Payload :: binary(),
+ Key :: binary() | public_key:private_key()
+) -> binary() | undefined.
+%% @private
+jwt_sign(Alg, Payload, Key) ->
+ jwt_sign_with_crypto(algorithm_to_crypto(Alg), Payload, Key).
+
+jwt_sign_with_crypto({hmac, Crypto}, Payload, Key) ->
+ base64url:encode(?HMAC(Crypto, Key, Payload));
+
+jwt_sign_with_crypto({Algo, Crypto}, Payload, Pem)
+ when (Algo =:= rsa orelse Algo =:= ecdsa) andalso is_binary(Pem) ->
+ jwt_sign_with_crypto({Algo, Crypto}, Payload, pem_to_key(Pem));
+
+jwt_sign_with_crypto({rsa, Crypto}, Payload, Key) ->
+ base64url:encode(public_key:sign(Payload, Crypto, Key));
+
+jwt_sign_with_crypto({ecdsa, Crypto}, Payload, Key) ->
+ base64url:encode(jwt_ecdsa:signature(Payload, Crypto, Key));
+
+jwt_sign_with_crypto(_, _Payload, _Key) ->
+ undefined.
+
+-spec algorithm_to_crypto(binary()) -> {atom(), atom()} | undefined.
+%% @private
+algorithm_to_crypto(<<"HS256">>) -> {hmac, sha256};
+algorithm_to_crypto(<<"HS384">>) -> {hmac, sha384};
+algorithm_to_crypto(<<"HS512">>) -> {hmac, sha512};
+algorithm_to_crypto(<<"RS256">>) -> {rsa, sha256};
+algorithm_to_crypto(<<"RS384">>) -> {rsa, sha384};
+algorithm_to_crypto(<<"RS512">>) -> {rsa, sha512};
+algorithm_to_crypto(<<"ES256">>) -> {ecdsa, sha256};
+algorithm_to_crypto(_) -> undefined.
+
+-spec epoch() -> non_neg_integer().
+%% @private
+epoch() -> erlang:system_time(seconds).
+
+-spec expiration_to_epoch(Expiration :: expiration()) -> neg_integer().
+%% @private
+expiration_to_epoch(Expiration) ->
+ expiration_to_epoch(Expiration, epoch()).
+
+expiration_to_epoch(Expiration, Ts) ->
+ case Expiration of
+ {hourly, Expiration0} -> (Ts - (Ts rem ?HOUR)) + Expiration0;
+ {daily, Expiration0} -> (Ts - (Ts rem ?DAY)) + Expiration0;
+ _ -> epoch() + Expiration
+ end.
+
+-spec append_claim(ClaimsSet :: map() | list(), binary(), any()) -> map() | list().
+%% @private
+append_claim(ClaimsSet, Key, Val) when is_map(ClaimsSet) ->
+ ClaimsSet#{ Key => Val };
+append_claim(ClaimsSet, Key, Val) -> [{ Key, Val } | ClaimsSet].
+
+pem_to_key(Pem) ->
+ Decoded = case public_key:pem_decode(Pem) of
+ [_, Key] ->
+ Key;
+ [Key] ->
+ Key
+ end,
+ public_key:pem_entry_decode(Decoded).
diff --git a/server/_build/default/lib/jwt/src/jwt_ecdsa.erl b/server/_build/default/lib/jwt/src/jwt_ecdsa.erl
new file mode 100644
index 0000000..7596e90
--- /dev/null
+++ b/server/_build/default/lib/jwt/src/jwt_ecdsa.erl
@@ -0,0 +1,76 @@
+%% @doc Eliptic curve digital signature algorithm
+%%
+%% Helper functions for encoding/decoding ECDSA signature
+%%
+%% @end
+-module(jwt_ecdsa).
+
+-include_lib("jwt_ecdsa.hrl").
+-include_lib("public_key/include/public_key.hrl").
+
+-export([
+ signature/1,
+ signature/3
+]).
+
+%% @doc Signature for JWT verification
+%%
+%% Transcode the ECDSA Base64-encoded signature into ASN.1/DER format
+%%
+%% @end
+signature(Base64Sig) ->
+ Signature = base64url:decode(Base64Sig),
+ SignatureLen = byte_size(Signature),
+ {RBin, SBin} = split_binary(Signature, (SignatureLen div 2)),
+ R = crypto:bytes_to_integer(RBin),
+ S = crypto:bytes_to_integer(SBin),
+ public_key:der_encode('ECDSA-Sig-Value', #'ECDSA-Sig-Value'{ r = R, s = S }).
+
+%% @doc Signature to sign JWT
+%%
+%% Transcodes the JCA ASN.1/DER-encoded signature into the concatenated R + S format
+%% a.k.a <em>raw</em> format
+%%
+%% @end
+signature(Payload, Crypto, Key) ->
+ Der = public_key:sign(Payload, Crypto, Key),
+ raw(Der, Key).
+
+raw(Der, #'ECPrivateKey'{parameters = {namedCurve, NamedCurve}}) ->
+ #'ECDSA-Sig-Value'{ r = R, s = S } = public_key:der_decode('ECDSA-Sig-Value', Der),
+ CurveName = pubkey_cert_records:namedCurves(NamedCurve),
+ GroupDegree = group_degree(CurveName),
+ Size = (GroupDegree + 7) div 8,
+ RBin = int_to_bin(R),
+ SBin = int_to_bin(S),
+ RPad = pad(RBin, Size),
+ SPad = pad(SBin, Size),
+ <<RPad/binary, SPad/binary>>.
+
+%% @private
+int_to_bin(X) when X < 0 ->
+ int_to_bin_neg(X, []);
+int_to_bin(X) ->
+ int_to_bin_pos(X, []).
+
+%% @private
+int_to_bin_pos(0, Ds = [_|_]) ->
+ list_to_binary(Ds);
+int_to_bin_pos(X, Ds) ->
+ int_to_bin_pos(X bsr 8, [(X band 255)|Ds]).
+
+%% @private
+int_to_bin_neg(-1, Ds = [MSB|_]) when MSB >= 16#80 ->
+ list_to_binary(Ds);
+int_to_bin_neg(X, Ds) ->
+ int_to_bin_neg(X bsr 8, [(X band 255)|Ds]).
+
+%% @private
+pad(Bin, Size) when byte_size(Bin) =:= Size ->
+ Bin;
+pad(Bin, Size) when byte_size(Bin) < Size ->
+ pad(<<0, Bin/binary>>, Size).
+
+%% See the OpenSSL documentation for EC_GROUP_get_degree()
+group_degree(CurveName) ->
+ maps:get(CurveName, ?EC_GROUP_DEGREE).
diff --git a/server/_build/default/lib/poolboy/LICENSE b/server/_build/default/lib/poolboy/LICENSE
new file mode 100644
index 0000000..ce34eab
--- /dev/null
+++ b/server/_build/default/lib/poolboy/LICENSE
@@ -0,0 +1,15 @@
+ISC License
+
+Copyright (c) 2014, Devin Alexander Torres <devin@devintorr.es>
+
+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/poolboy/README.md b/server/_build/default/lib/poolboy/README.md
new file mode 100644
index 0000000..495d812
--- /dev/null
+++ b/server/_build/default/lib/poolboy/README.md
@@ -0,0 +1,171 @@
+# Poolboy - A hunky Erlang worker pool factory
+
+[![Build Status](https://api.travis-ci.org/devinus/poolboy.svg?branch=master)](https://travis-ci.org/devinus/poolboy)
+
+[![Support via Gratipay](https://cdn.rawgit.com/gratipay/gratipay-badge/2.3.0/dist/gratipay.png)](https://gratipay.com/devinus/)
+
+Poolboy is a **lightweight**, **generic** pooling library for Erlang with a
+focus on **simplicity**, **performance**, and **rock-solid** disaster recovery.
+
+## Usage
+
+```erl-sh
+1> Worker = poolboy:checkout(PoolName).
+<0.9001.0>
+2> gen_server:call(Worker, Request).
+ok
+3> poolboy:checkin(PoolName, Worker).
+ok
+```
+
+## Example
+
+This is an example application showcasing database connection pools using
+Poolboy and [epgsql](https://github.com/epgsql/epgsql).
+
+### example.app
+
+```erlang
+{application, example, [
+ {description, "An example application"},
+ {vsn, "0.1"},
+ {applications, [kernel, stdlib, sasl, crypto, ssl]},
+ {modules, [example, example_worker]},
+ {registered, [example]},
+ {mod, {example, []}},
+ {env, [
+ {pools, [
+ {pool1, [
+ {size, 10},
+ {max_overflow, 20}
+ ], [
+ {hostname, "127.0.0.1"},
+ {database, "db1"},
+ {username, "db1"},
+ {password, "abc123"}
+ ]},
+ {pool2, [
+ {size, 5},
+ {max_overflow, 10}
+ ], [
+ {hostname, "127.0.0.1"},
+ {database, "db2"},
+ {username, "db2"},
+ {password, "abc123"}
+ ]}
+ ]}
+ ]}
+]}.
+```
+
+### example.erl
+
+```erlang
+-module(example).
+-behaviour(application).
+-behaviour(supervisor).
+
+-export([start/0, stop/0, squery/2, equery/3]).
+-export([start/2, stop/1]).
+-export([init/1]).
+
+start() ->
+ application:start(?MODULE).
+
+stop() ->
+ application:stop(?MODULE).
+
+start(_Type, _Args) ->
+ supervisor:start_link({local, example_sup}, ?MODULE, []).
+
+stop(_State) ->
+ ok.
+
+init([]) ->
+ {ok, Pools} = application:get_env(example, pools),
+ PoolSpecs = lists:map(fun({Name, SizeArgs, WorkerArgs}) ->
+ PoolArgs = [{name, {local, Name}},
+ {worker_module, example_worker}] ++ SizeArgs,
+ poolboy:child_spec(Name, PoolArgs, WorkerArgs)
+ end, Pools),
+ {ok, {{one_for_one, 10, 10}, PoolSpecs}}.
+
+squery(PoolName, Sql) ->
+ poolboy:transaction(PoolName, fun(Worker) ->
+ gen_server:call(Worker, {squery, Sql})
+ end).
+
+equery(PoolName, Stmt, Params) ->
+ poolboy:transaction(PoolName, fun(Worker) ->
+ gen_server:call(Worker, {equery, Stmt, Params})
+ end).
+```
+
+### example_worker.erl
+
+```erlang
+-module(example_worker).
+-behaviour(gen_server).
+-behaviour(poolboy_worker).
+
+-export([start_link/1]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+
+-record(state, {conn}).
+
+start_link(Args) ->
+ gen_server:start_link(?MODULE, Args, []).
+
+init(Args) ->
+ process_flag(trap_exit, true),
+ Hostname = proplists:get_value(hostname, Args),
+ Database = proplists:get_value(database, Args),
+ Username = proplists:get_value(username, Args),
+ Password = proplists:get_value(password, Args),
+ {ok, Conn} = epgsql:connect(Hostname, Username, Password, [
+ {database, Database}
+ ]),
+ {ok, #state{conn=Conn}}.
+
+handle_call({squery, Sql}, _From, #state{conn=Conn}=State) ->
+ {reply, epgsql:squery(Conn, Sql), State};
+handle_call({equery, Stmt, Params}, _From, #state{conn=Conn}=State) ->
+ {reply, epgsql:equery(Conn, Stmt, Params), State};
+handle_call(_Request, _From, State) ->
+ {reply, ok, State}.
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, #state{conn=Conn}) ->
+ ok = epgsql:close(Conn),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+```
+
+## Options
+
+- `name`: the pool name
+- `worker_module`: the module that represents the workers
+- `size`: maximum pool size
+- `max_overflow`: maximum number of workers created if pool is empty
+- `strategy`: `lifo` or `fifo`, determines whether checked in workers should be
+ placed first or last in the line of available workers. So, `lifo` operates like a traditional stack; `fifo` like a queue. Default is `lifo`.
+
+## Authors
+
+- Devin Torres (devinus) <devin@devintorres.com>
+- Andrew Thompson (Vagabond) <andrew@hijacked.us>
+- Kurt Williams (onkel-dirtus) <kurt.r.williams@gmail.com>
+
+## License
+
+Poolboy is available in the public domain (see `UNLICENSE`).
+Poolboy is also optionally available under the ISC license (see `LICENSE`),
+meant especially for jurisdictions that do not recognize public domain works.
diff --git a/server/_build/default/lib/poolboy/ebin/poolboy.app b/server/_build/default/lib/poolboy/ebin/poolboy.app
new file mode 100644
index 0000000..228edc3
--- /dev/null
+++ b/server/_build/default/lib/poolboy/ebin/poolboy.app
@@ -0,0 +1,9 @@
+{application,poolboy,
+ [{description,"A hunky Erlang worker pool factory"},
+ {vsn,"1.5.2"},
+ {applications,[kernel,stdlib]},
+ {registered,[poolboy]},
+ {maintainers,["Devin Torres","Andrew Thompson","Kurt Williams"]},
+ {licenses,["Unlicense","Apache 2.0"]},
+ {links,[{"GitHub","https://github.com/devinus/poolboy"}]},
+ {modules,[poolboy,poolboy_sup,poolboy_worker]}]}.
diff --git a/server/_build/default/lib/poolboy/ebin/poolboy.beam b/server/_build/default/lib/poolboy/ebin/poolboy.beam
new file mode 100644
index 0000000..c6a0e49
--- /dev/null
+++ b/server/_build/default/lib/poolboy/ebin/poolboy.beam
Binary files differ
diff --git a/server/_build/default/lib/poolboy/ebin/poolboy_sup.beam b/server/_build/default/lib/poolboy/ebin/poolboy_sup.beam
new file mode 100644
index 0000000..76226d6
--- /dev/null
+++ b/server/_build/default/lib/poolboy/ebin/poolboy_sup.beam
Binary files differ
diff --git a/server/_build/default/lib/poolboy/ebin/poolboy_worker.beam b/server/_build/default/lib/poolboy/ebin/poolboy_worker.beam
new file mode 100644
index 0000000..c4f0656
--- /dev/null
+++ b/server/_build/default/lib/poolboy/ebin/poolboy_worker.beam
Binary files differ
diff --git a/server/_build/default/lib/poolboy/hex_metadata.config b/server/_build/default/lib/poolboy/hex_metadata.config
new file mode 100644
index 0000000..4b24846
--- /dev/null
+++ b/server/_build/default/lib/poolboy/hex_metadata.config
@@ -0,0 +1,15 @@
+{<<"name">>,<<"poolboy">>}.
+{<<"version">>,<<"1.5.2">>}.
+{<<"requirements">>,#{}}.
+{<<"app">>,<<"poolboy">>}.
+{<<"maintainers">>,
+ [<<"Devin Torres">>,<<"Andrew Thompson">>,<<"Kurt Williams">>]}.
+{<<"precompiled">>,false}.
+{<<"description">>,<<"A hunky Erlang worker pool factory">>}.
+{<<"files">>,
+ [<<"src/poolboy.app.src">>,<<"LICENSE">>,<<"README.md">>,<<"rebar.config">>,
+ <<"rebar.lock">>,<<"src/poolboy.erl">>,<<"src/poolboy_sup.erl">>,
+ <<"src/poolboy_worker.erl">>]}.
+{<<"licenses">>,[<<"Unlicense">>,<<"Apache 2.0">>]}.
+{<<"links">>,[{<<"GitHub">>,<<"https://github.com/devinus/poolboy">>}]}.
+{<<"build_tools">>,[<<"rebar3">>]}.
diff --git a/server/_build/default/lib/poolboy/rebar.config b/server/_build/default/lib/poolboy/rebar.config
new file mode 100644
index 0000000..1d494ca
--- /dev/null
+++ b/server/_build/default/lib/poolboy/rebar.config
@@ -0,0 +1,15 @@
+{erl_opts, [
+ debug_info,
+ {platform_define, "^R", pre17}
+]}.
+
+{eunit_opts, [verbose]}.
+{cover_enabled, true}.
+
+{profiles, [
+ {test, [
+ {plugins, [
+ {rebar3_eqc, ".*", {git, "https://github.com/kellymclaughlin/rebar3-eqc-plugin.git", {tag, "0.1.0"}}}
+ ]}
+ ]
+}]}.
diff --git a/server/_build/default/lib/poolboy/rebar.lock b/server/_build/default/lib/poolboy/rebar.lock
new file mode 100644
index 0000000..57afcca
--- /dev/null
+++ b/server/_build/default/lib/poolboy/rebar.lock
@@ -0,0 +1 @@
+[].
diff --git a/server/_build/default/lib/poolboy/src/poolboy.app.src b/server/_build/default/lib/poolboy/src/poolboy.app.src
new file mode 100644
index 0000000..5119212
--- /dev/null
+++ b/server/_build/default/lib/poolboy/src/poolboy.app.src
@@ -0,0 +1,8 @@
+{application,poolboy,
+ [{description,"A hunky Erlang worker pool factory"},
+ {vsn,"1.5.2"},
+ {applications,[kernel,stdlib]},
+ {registered,[poolboy]},
+ {maintainers,["Devin Torres","Andrew Thompson","Kurt Williams"]},
+ {licenses,["Unlicense","Apache 2.0"]},
+ {links,[{"GitHub","https://github.com/devinus/poolboy"}]}]}.
diff --git a/server/_build/default/lib/poolboy/src/poolboy.erl b/server/_build/default/lib/poolboy/src/poolboy.erl
new file mode 100644
index 0000000..0023412
--- /dev/null
+++ b/server/_build/default/lib/poolboy/src/poolboy.erl
@@ -0,0 +1,357 @@
+%% Poolboy - A hunky Erlang worker pool factory
+
+-module(poolboy).
+-behaviour(gen_server).
+
+-export([checkout/1, checkout/2, checkout/3, checkin/2, transaction/2,
+ transaction/3, child_spec/2, child_spec/3, start/1, start/2,
+ start_link/1, start_link/2, stop/1, status/1]).
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
+ code_change/3]).
+-export_type([pool/0]).
+
+-define(TIMEOUT, 5000).
+
+-ifdef(pre17).
+-type pid_queue() :: queue().
+-else.
+-type pid_queue() :: queue:queue().
+-endif.
+
+-ifdef(OTP_RELEASE). %% this implies 21 or higher
+-define(EXCEPTION(Class, Reason, Stacktrace), Class:Reason:Stacktrace).
+-define(GET_STACK(Stacktrace), Stacktrace).
+-else.
+-define(EXCEPTION(Class, Reason, _), Class:Reason).
+-define(GET_STACK(_), erlang:get_stacktrace()).
+-endif.
+
+-type pool() ::
+ Name :: (atom() | pid()) |
+ {Name :: atom(), node()} |
+ {local, Name :: atom()} |
+ {global, GlobalName :: any()} |
+ {via, Module :: atom(), ViaName :: any()}.
+
+% Copied from gen:start_ret/0
+-type start_ret() :: {'ok', pid()} | 'ignore' | {'error', term()}.
+
+-record(state, {
+ supervisor :: undefined | pid(),
+ workers = [] :: [pid()],
+ waiting :: pid_queue(),
+ monitors :: ets:tid(),
+ size = 5 :: non_neg_integer(),
+ overflow = 0 :: non_neg_integer(),
+ max_overflow = 10 :: non_neg_integer(),
+ strategy = lifo :: lifo | fifo
+}).
+
+-spec checkout(Pool :: pool()) -> pid().
+checkout(Pool) ->
+ checkout(Pool, true).
+
+-spec checkout(Pool :: pool(), Block :: boolean()) -> pid() | full.
+checkout(Pool, Block) ->
+ checkout(Pool, Block, ?TIMEOUT).
+
+-spec checkout(Pool :: pool(), Block :: boolean(), Timeout :: timeout())
+ -> pid() | full.
+checkout(Pool, Block, Timeout) ->
+ CRef = make_ref(),
+ try
+ gen_server:call(Pool, {checkout, CRef, Block}, Timeout)
+ catch
+ ?EXCEPTION(Class, Reason, Stacktrace) ->
+ gen_server:cast(Pool, {cancel_waiting, CRef}),
+ erlang:raise(Class, Reason, ?GET_STACK(Stacktrace))
+ end.
+
+-spec checkin(Pool :: pool(), Worker :: pid()) -> ok.
+checkin(Pool, Worker) when is_pid(Worker) ->
+ gen_server:cast(Pool, {checkin, Worker}).
+
+-spec transaction(Pool :: pool(), Fun :: fun((Worker :: pid()) -> any()))
+ -> any().
+transaction(Pool, Fun) ->
+ transaction(Pool, Fun, ?TIMEOUT).
+
+-spec transaction(Pool :: pool(), Fun :: fun((Worker :: pid()) -> any()),
+ Timeout :: timeout()) -> any().
+transaction(Pool, Fun, Timeout) ->
+ Worker = poolboy:checkout(Pool, true, Timeout),
+ try
+ Fun(Worker)
+ after
+ ok = poolboy:checkin(Pool, Worker)
+ end.
+
+-spec child_spec(PoolId :: term(), PoolArgs :: proplists:proplist())
+ -> supervisor:child_spec().
+child_spec(PoolId, PoolArgs) ->
+ child_spec(PoolId, PoolArgs, []).
+
+-spec child_spec(PoolId :: term(),
+ PoolArgs :: proplists:proplist(),
+ WorkerArgs :: proplists:proplist())
+ -> supervisor:child_spec().
+child_spec(PoolId, PoolArgs, WorkerArgs) ->
+ {PoolId, {poolboy, start_link, [PoolArgs, WorkerArgs]},
+ permanent, 5000, worker, [poolboy]}.
+
+-spec start(PoolArgs :: proplists:proplist())
+ -> start_ret().
+start(PoolArgs) ->
+ start(PoolArgs, PoolArgs).
+
+-spec start(PoolArgs :: proplists:proplist(),
+ WorkerArgs:: proplists:proplist())
+ -> start_ret().
+start(PoolArgs, WorkerArgs) ->
+ start_pool(start, PoolArgs, WorkerArgs).
+
+-spec start_link(PoolArgs :: proplists:proplist())
+ -> start_ret().
+start_link(PoolArgs) ->
+ %% for backwards compatability, pass the pool args as the worker args as well
+ start_link(PoolArgs, PoolArgs).
+
+-spec start_link(PoolArgs :: proplists:proplist(),
+ WorkerArgs:: proplists:proplist())
+ -> start_ret().
+start_link(PoolArgs, WorkerArgs) ->
+ start_pool(start_link, PoolArgs, WorkerArgs).
+
+-spec stop(Pool :: pool()) -> ok.
+stop(Pool) ->
+ gen_server:call(Pool, stop).
+
+-spec status(Pool :: pool()) -> {atom(), integer(), integer(), integer()}.
+status(Pool) ->
+ gen_server:call(Pool, status).
+
+init({PoolArgs, WorkerArgs}) ->
+ process_flag(trap_exit, true),
+ Waiting = queue:new(),
+ Monitors = ets:new(monitors, [private]),
+ init(PoolArgs, WorkerArgs, #state{waiting = Waiting, monitors = Monitors}).
+
+init([{worker_module, Mod} | Rest], WorkerArgs, State) when is_atom(Mod) ->
+ {ok, Sup} = poolboy_sup:start_link(Mod, WorkerArgs),
+ init(Rest, WorkerArgs, State#state{supervisor = Sup});
+init([{size, Size} | Rest], WorkerArgs, State) when is_integer(Size) ->
+ init(Rest, WorkerArgs, State#state{size = Size});
+init([{max_overflow, MaxOverflow} | Rest], WorkerArgs, State) when is_integer(MaxOverflow) ->
+ init(Rest, WorkerArgs, State#state{max_overflow = MaxOverflow});
+init([{strategy, lifo} | Rest], WorkerArgs, State) ->
+ init(Rest, WorkerArgs, State#state{strategy = lifo});
+init([{strategy, fifo} | Rest], WorkerArgs, State) ->
+ init(Rest, WorkerArgs, State#state{strategy = fifo});
+init([_ | Rest], WorkerArgs, State) ->
+ init(Rest, WorkerArgs, State);
+init([], _WorkerArgs, #state{size = Size, supervisor = Sup} = State) ->
+ Workers = prepopulate(Size, Sup),
+ {ok, State#state{workers = Workers}}.
+
+handle_cast({checkin, Pid}, State = #state{monitors = Monitors}) ->
+ case ets:lookup(Monitors, Pid) of
+ [{Pid, _, MRef}] ->
+ true = erlang:demonitor(MRef),
+ true = ets:delete(Monitors, Pid),
+ NewState = handle_checkin(Pid, State),
+ {noreply, NewState};
+ [] ->
+ {noreply, State}
+ end;
+
+handle_cast({cancel_waiting, CRef}, State) ->
+ case ets:match(State#state.monitors, {'$1', CRef, '$2'}) of
+ [[Pid, MRef]] ->
+ demonitor(MRef, [flush]),
+ true = ets:delete(State#state.monitors, Pid),
+ NewState = handle_checkin(Pid, State),
+ {noreply, NewState};
+ [] ->
+ Cancel = fun({_, Ref, MRef}) when Ref =:= CRef ->
+ demonitor(MRef, [flush]),
+ false;
+ (_) ->
+ true
+ end,
+ Waiting = queue:filter(Cancel, State#state.waiting),
+ {noreply, State#state{waiting = Waiting}}
+ end;
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+handle_call({checkout, CRef, Block}, {FromPid, _} = From, State) ->
+ #state{supervisor = Sup,
+ workers = Workers,
+ monitors = Monitors,
+ overflow = Overflow,
+ max_overflow = MaxOverflow} = State,
+ case Workers of
+ [Pid | Left] ->
+ MRef = erlang:monitor(process, FromPid),
+ true = ets:insert(Monitors, {Pid, CRef, MRef}),
+ {reply, Pid, State#state{workers = Left}};
+ [] when MaxOverflow > 0, Overflow < MaxOverflow ->
+ {Pid, MRef} = new_worker(Sup, FromPid),
+ true = ets:insert(Monitors, {Pid, CRef, MRef}),
+ {reply, Pid, State#state{overflow = Overflow + 1}};
+ [] when Block =:= false ->
+ {reply, full, State};
+ [] ->
+ MRef = erlang:monitor(process, FromPid),
+ Waiting = queue:in({From, CRef, MRef}, State#state.waiting),
+ {noreply, State#state{waiting = Waiting}}
+ end;
+
+handle_call(status, _From, State) ->
+ #state{workers = Workers,
+ monitors = Monitors,
+ overflow = Overflow} = State,
+ StateName = state_name(State),
+ {reply, {StateName, length(Workers), Overflow, ets:info(Monitors, size)}, State};
+handle_call(get_avail_workers, _From, State) ->
+ Workers = State#state.workers,
+ {reply, Workers, State};
+handle_call(get_all_workers, _From, State) ->
+ Sup = State#state.supervisor,
+ WorkerList = supervisor:which_children(Sup),
+ {reply, WorkerList, State};
+handle_call(get_all_monitors, _From, State) ->
+ Monitors = ets:select(State#state.monitors,
+ [{{'$1', '_', '$2'}, [], [{{'$1', '$2'}}]}]),
+ {reply, Monitors, State};
+handle_call(stop, _From, State) ->
+ {stop, normal, ok, State};
+handle_call(_Msg, _From, State) ->
+ Reply = {error, invalid_message},
+ {reply, Reply, State}.
+
+handle_info({'DOWN', MRef, _, _, _}, State) ->
+ case ets:match(State#state.monitors, {'$1', '_', MRef}) of
+ [[Pid]] ->
+ true = ets:delete(State#state.monitors, Pid),
+ NewState = handle_checkin(Pid, State),
+ {noreply, NewState};
+ [] ->
+ Waiting = queue:filter(fun ({_, _, R}) -> R =/= MRef end, State#state.waiting),
+ {noreply, State#state{waiting = Waiting}}
+ end;
+handle_info({'EXIT', Pid, _Reason}, State) ->
+ #state{supervisor = Sup,
+ monitors = Monitors} = State,
+ case ets:lookup(Monitors, Pid) of
+ [{Pid, _, MRef}] ->
+ true = erlang:demonitor(MRef),
+ true = ets:delete(Monitors, Pid),
+ NewState = handle_worker_exit(Pid, State),
+ {noreply, NewState};
+ [] ->
+ case lists:member(Pid, State#state.workers) of
+ true ->
+ W = lists:filter(fun (P) -> P =/= Pid end, State#state.workers),
+ {noreply, State#state{workers = [new_worker(Sup) | W]}};
+ false ->
+ {noreply, State}
+ end
+ end;
+
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, State) ->
+ ok = lists:foreach(fun (W) -> unlink(W) end, State#state.workers),
+ true = exit(State#state.supervisor, shutdown),
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+start_pool(StartFun, PoolArgs, WorkerArgs) ->
+ case proplists:get_value(name, PoolArgs) of
+ undefined ->
+ gen_server:StartFun(?MODULE, {PoolArgs, WorkerArgs}, []);
+ Name ->
+ gen_server:StartFun(Name, ?MODULE, {PoolArgs, WorkerArgs}, [])
+ end.
+
+new_worker(Sup) ->
+ {ok, Pid} = supervisor:start_child(Sup, []),
+ true = link(Pid),
+ Pid.
+
+new_worker(Sup, FromPid) ->
+ Pid = new_worker(Sup),
+ Ref = erlang:monitor(process, FromPid),
+ {Pid, Ref}.
+
+dismiss_worker(Sup, Pid) ->
+ true = unlink(Pid),
+ supervisor:terminate_child(Sup, Pid).
+
+prepopulate(N, _Sup) when N < 1 ->
+ [];
+prepopulate(N, Sup) ->
+ prepopulate(N, Sup, []).
+
+prepopulate(0, _Sup, Workers) ->
+ Workers;
+prepopulate(N, Sup, Workers) ->
+ prepopulate(N-1, Sup, [new_worker(Sup) | Workers]).
+
+handle_checkin(Pid, State) ->
+ #state{supervisor = Sup,
+ waiting = Waiting,
+ monitors = Monitors,
+ overflow = Overflow,
+ strategy = Strategy} = State,
+ case queue:out(Waiting) of
+ {{value, {From, CRef, MRef}}, Left} ->
+ true = ets:insert(Monitors, {Pid, CRef, MRef}),
+ gen_server:reply(From, Pid),
+ State#state{waiting = Left};
+ {empty, Empty} when Overflow > 0 ->
+ ok = dismiss_worker(Sup, Pid),
+ State#state{waiting = Empty, overflow = Overflow - 1};
+ {empty, Empty} ->
+ Workers = case Strategy of
+ lifo -> [Pid | State#state.workers];
+ fifo -> State#state.workers ++ [Pid]
+ end,
+ State#state{workers = Workers, waiting = Empty, overflow = 0}
+ end.
+
+handle_worker_exit(Pid, State) ->
+ #state{supervisor = Sup,
+ monitors = Monitors,
+ overflow = Overflow} = State,
+ case queue:out(State#state.waiting) of
+ {{value, {From, CRef, MRef}}, LeftWaiting} ->
+ NewWorker = new_worker(State#state.supervisor),
+ true = ets:insert(Monitors, {NewWorker, CRef, MRef}),
+ gen_server:reply(From, NewWorker),
+ State#state{waiting = LeftWaiting};
+ {empty, Empty} when Overflow > 0 ->
+ State#state{overflow = Overflow - 1, waiting = Empty};
+ {empty, Empty} ->
+ Workers =
+ [new_worker(Sup)
+ | lists:filter(fun (P) -> P =/= Pid end, State#state.workers)],
+ State#state{workers = Workers, waiting = Empty}
+ end.
+
+state_name(State = #state{overflow = Overflow}) when Overflow < 1 ->
+ #state{max_overflow = MaxOverflow, workers = Workers} = State,
+ case length(Workers) == 0 of
+ true when MaxOverflow < 1 -> full;
+ true -> overflow;
+ false -> ready
+ end;
+state_name(#state{overflow = MaxOverflow, max_overflow = MaxOverflow}) ->
+ full;
+state_name(_State) ->
+ overflow.
diff --git a/server/_build/default/lib/poolboy/src/poolboy_sup.erl b/server/_build/default/lib/poolboy/src/poolboy_sup.erl
new file mode 100644
index 0000000..e6485a6
--- /dev/null
+++ b/server/_build/default/lib/poolboy/src/poolboy_sup.erl
@@ -0,0 +1,14 @@
+%% Poolboy - A hunky Erlang worker pool factory
+
+-module(poolboy_sup).
+-behaviour(supervisor).
+
+-export([start_link/2, init/1]).
+
+start_link(Mod, Args) ->
+ supervisor:start_link(?MODULE, {Mod, Args}).
+
+init({Mod, Args}) ->
+ {ok, {{simple_one_for_one, 0, 1},
+ [{Mod, {Mod, start_link, [Args]},
+ temporary, 5000, worker, [Mod]}]}}.
diff --git a/server/_build/default/lib/poolboy/src/poolboy_worker.erl b/server/_build/default/lib/poolboy/src/poolboy_worker.erl
new file mode 100644
index 0000000..4a5bfae
--- /dev/null
+++ b/server/_build/default/lib/poolboy/src/poolboy_worker.erl
@@ -0,0 +1,10 @@
+%% Poolboy - A hunky Erlang worker pool factory
+
+-module(poolboy_worker).
+
+-callback start_link(WorkerArgs) -> {ok, Pid} |
+ {error, {already_started, Pid}} |
+ {error, Reason} when
+ WorkerArgs :: proplists:proplist(),
+ Pid :: pid(),
+ Reason :: term().
diff --git a/server/_build/default/lib/ranch/LICENSE b/server/_build/default/lib/ranch/LICENSE
new file mode 100644
index 0000000..561c89d
--- /dev/null
+++ b/server/_build/default/lib/ranch/LICENSE
@@ -0,0 +1,13 @@
+Copyright (c) 2011-2018, 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/ranch/Makefile b/server/_build/default/lib/ranch/Makefile
new file mode 100644
index 0000000..dcbb443
--- /dev/null
+++ b/server/_build/default/lib/ranch/Makefile
@@ -0,0 +1,85 @@
+# See LICENSE for licensing information.
+
+PROJECT = ranch
+PROJECT_DESCRIPTION = Socket acceptor pool for TCP protocols.
+PROJECT_VERSION = 1.8.0
+PROJECT_REGISTERED = ranch_server
+
+# Options.
+
+CT_OPTS += -pa test -ct_hooks ranch_ct_hook [] # -boot start_sasl
+PLT_APPS = crypto public_key tools
+
+# Dependencies.
+
+LOCAL_DEPS = ssl
+
+DOC_DEPS = asciideck
+
+TEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) ct_helper
+dep_ct_helper = git https://github.com/ninenines/ct_helper 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-21+
+AUTO_CI_HIPE ?= OTP-LATEST
+# AUTO_CI_ERLLVM ?= OTP-LATEST
+AUTO_CI_WINDOWS ?= OTP-21+
+
+# Hex configuration.
+
+define HEX_TARBALL_EXTRA_METADATA
+#{
+ licenses => [<<"ISC">>],
+ links => #{
+ <<"User guide">> => <<"https://ninenines.eu/docs/en/ranch/1.8/guide/">>,
+ <<"Function reference">> => <<"https://ninenines.eu/docs/en/ranch/1.8/manual/">>,
+ <<"GitHub">> => <<"https://github.com/ninenines/ranch">>,
+ <<"Sponsor">> => <<"https://github.com/sponsors/essen">>
+ }
+}
+endef
+
+# Standard targets.
+
+include erlang.mk
+
+# Compile options.
+
+TEST_ERLC_OPTS += +'{parse_transform, eunit_autoexport}'
+
+# Dialyze the tests.
+
+DIALYZER_OPTS += --src -r test
+
+# Use erl_make_certs from the tested release during CI
+# and ensure that ct_helper is always recompiled.
+#
+# Note that erl_make_certs was removed from OTP-20.1. For now
+# we are fine using the most recent version from OTP-20.
+
+ci-setup:: $(DEPS_DIR)/ct_helper
+ $(gen_verbose) cp ~/.kerl/builds/$(CI_OTP_RELEASE)/otp_src_git/lib/ssl/test/erl_make_certs.erl deps/ct_helper/src/ || true
+ $(gen_verbose) $(MAKE) -C $(DEPS_DIR)/ct_helper clean app
+
+# 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 "Dependencies:"
+ $(verbose) grep ^DEPS Makefile || echo "DEPS ="
+ $(verbose) grep ^dep_ Makefile || true
diff --git a/server/_build/default/lib/ranch/README.asciidoc b/server/_build/default/lib/ranch/README.asciidoc
new file mode 100644
index 0000000..aa702b7
--- /dev/null
+++ b/server/_build/default/lib/ranch/README.asciidoc
@@ -0,0 +1,38 @@
+= Ranch
+
+Ranch is a socket acceptor pool for TCP protocols.
+
+== Goals
+
+Ranch aims to provide everything you need to accept TCP connections with
+a *small* code base and *low latency* while being easy to use directly
+as an application or to *embed* into your own.
+
+Ranch provides a *modular* design, letting you choose which transport
+and protocol are going to be used for a particular listener. Listeners
+accept and manage connections on one port, and include facilities to
+limit the number of *concurrent* connections. Connections are sorted
+into *pools*, each pool having a different configurable limit.
+
+Ranch also allows you to *upgrade* the acceptor pool without having
+to close any of the currently opened sockets.
+
+== Online documentation
+
+* https://ninenines.eu/docs/en/ranch/1.7/guide[User guide]
+* https://ninenines.eu/docs/en/ranch/1.7/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/ranch/issues[Issues tracker]
+* https://ninenines.eu/services[Commercial Support]
diff --git a/server/_build/default/lib/ranch/ebin/ranch.app b/server/_build/default/lib/ranch/ebin/ranch.app
new file mode 100644
index 0000000..291f6de
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch.app
@@ -0,0 +1,9 @@
+{application, 'ranch', [
+ {description, "Socket acceptor pool for TCP protocols."},
+ {vsn, "1.8.0"},
+ {modules, ['ranch','ranch_acceptor','ranch_acceptors_sup','ranch_app','ranch_conns_sup','ranch_crc32c','ranch_listener_sup','ranch_protocol','ranch_proxy_header','ranch_server','ranch_ssl','ranch_sup','ranch_tcp','ranch_transport']},
+ {registered, [ranch_sup,ranch_server]},
+ {applications, [kernel,stdlib,ssl]},
+ {mod, {ranch_app, []}},
+ {env, []}
+]}. \ No newline at end of file
diff --git a/server/_build/default/lib/ranch/ebin/ranch.beam b/server/_build/default/lib/ranch/ebin/ranch.beam
new file mode 100644
index 0000000..74a124f
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_acceptor.beam b/server/_build/default/lib/ranch/ebin/ranch_acceptor.beam
new file mode 100644
index 0000000..cabc6f9
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_acceptor.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_acceptors_sup.beam b/server/_build/default/lib/ranch/ebin/ranch_acceptors_sup.beam
new file mode 100644
index 0000000..4598e90
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_acceptors_sup.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_app.beam b/server/_build/default/lib/ranch/ebin/ranch_app.beam
new file mode 100644
index 0000000..eb776b7
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_app.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_conns_sup.beam b/server/_build/default/lib/ranch/ebin/ranch_conns_sup.beam
new file mode 100644
index 0000000..7cd44ea
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_conns_sup.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_crc32c.beam b/server/_build/default/lib/ranch/ebin/ranch_crc32c.beam
new file mode 100644
index 0000000..997c5bb
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_crc32c.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_listener_sup.beam b/server/_build/default/lib/ranch/ebin/ranch_listener_sup.beam
new file mode 100644
index 0000000..7948926
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_listener_sup.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_protocol.beam b/server/_build/default/lib/ranch/ebin/ranch_protocol.beam
new file mode 100644
index 0000000..b648487
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_protocol.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_proxy_header.beam b/server/_build/default/lib/ranch/ebin/ranch_proxy_header.beam
new file mode 100644
index 0000000..9bb8554
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_proxy_header.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_server.beam b/server/_build/default/lib/ranch/ebin/ranch_server.beam
new file mode 100644
index 0000000..d178d55
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_server.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_ssl.beam b/server/_build/default/lib/ranch/ebin/ranch_ssl.beam
new file mode 100644
index 0000000..52b1437
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_ssl.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_sup.beam b/server/_build/default/lib/ranch/ebin/ranch_sup.beam
new file mode 100644
index 0000000..5661720
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_sup.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_tcp.beam b/server/_build/default/lib/ranch/ebin/ranch_tcp.beam
new file mode 100644
index 0000000..50e0ee5
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_tcp.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/ebin/ranch_transport.beam b/server/_build/default/lib/ranch/ebin/ranch_transport.beam
new file mode 100644
index 0000000..f91ae1b
--- /dev/null
+++ b/server/_build/default/lib/ranch/ebin/ranch_transport.beam
Binary files differ
diff --git a/server/_build/default/lib/ranch/erlang.mk b/server/_build/default/lib/ranch/erlang.mk
new file mode 100644
index 0000000..f152c37
--- /dev/null
+++ b/server/_build/default/lib/ranch/erlang.mk
@@ -0,0 +1,8156 @@
+# 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 = d80984c
+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 = master
+
+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 = master
+
+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",
+ {ok, Conf0} = file:consult(AppSrc),
+ Bindings0 = erl_eval:new_bindings(),
+ Bindings1 = erl_eval:add_binding('CONFIG', Conf0, 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]}.
+{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)
+
+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 -arch x86_64 -Wall -Wmissing-prototypes
+ CXXFLAGS ?= -O3 -arch x86_64 -Wall
+ LDFLAGS ?= -arch x86_64 -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 -Wrace_conditions -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.
+
+.PHONY: relx-rel relx-relup distclean-relx-rel run
+
+# Configuration.
+
+RELX ?= $(ERLANG_MK_TMP)/relx
+RELX_CONFIG ?= $(CURDIR)/relx.config
+
+RELX_URL ?= https://erlang.mk/res/relx-v3.27.0
+RELX_OPTS ?=
+RELX_OUTPUT_DIR ?= _rel
+RELX_REL_EXT ?=
+RELX_TAR ?= 1
+
+ifdef SFX
+ RELX_TAR = 1
+endif
+
+ifeq ($(firstword $(RELX_OPTS)),-o)
+ RELX_OUTPUT_DIR = $(word 2,$(RELX_OPTS))
+else
+ RELX_OPTS += -o $(RELX_OUTPUT_DIR)
+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.
+
+$(RELX): | $(ERLANG_MK_TMP)
+ $(gen_verbose) $(call core_http_get,$(RELX),$(RELX_URL))
+ $(verbose) chmod +x $(RELX)
+
+relx-rel: $(RELX) rel-deps app
+ $(verbose) $(RELX) $(if $(filter 1,$V),-V 3) -c $(RELX_CONFIG) $(RELX_OPTS) release
+ $(verbose) $(MAKE) relx-post-rel
+ifeq ($(RELX_TAR),1)
+ $(verbose) $(RELX) $(if $(filter 1,$V),-V 3) -c $(RELX_CONFIG) $(RELX_OPTS) tar
+endif
+
+relx-relup: $(RELX) rel-deps app
+ $(verbose) $(RELX) $(if $(filter 1,$V),-V 3) -c $(RELX_CONFIG) $(RELX_OPTS) release
+ $(MAKE) relx-post-rel
+ $(verbose) $(RELX) $(if $(filter 1,$V),-V 3) -c $(RELX_CONFIG) $(RELX_OPTS) relup $(if $(filter 1,$(RELX_TAR)),tar)
+
+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
+
+# 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) 2016, Loïc Hoguin <essen@ninenines.eu>
+# Copyright (c) 2015, Erlang Solutions Ltd.
+# This file is part of erlang.mk and subject to the terms of the ISC License.
+
+.PHONY: xref distclean-xref
+
+# Configuration.
+
+ifeq ($(XREF_CONFIG),)
+ XREFR_ARGS :=
+else
+ XREFR_ARGS := -c $(XREF_CONFIG)
+endif
+
+XREFR ?= $(CURDIR)/xrefr
+export XREFR
+
+XREFR_URL ?= https://github.com/inaka/xref_runner/releases/download/1.1.0/xrefr
+
+# Core targets.
+
+help::
+ $(verbose) printf '%s\n' '' \
+ 'Xref targets:' \
+ ' xref Run Xrefr using $$XREF_CONFIG as config file if defined'
+
+distclean:: distclean-xref
+
+# Plugin-specific targets.
+
+$(XREFR):
+ $(gen_verbose) $(call core_http_get,$(XREFR),$(XREFR_URL))
+ $(verbose) chmod +x $(XREFR)
+
+xref: deps app $(XREFR)
+ $(gen_verbose) $(XREFR) $(XREFR_ARGS)
+
+distclean-xref:
+ $(gen_verbose) rm -rf $(XREFR)
+
+# 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/ranch/hex_metadata.config b/server/_build/default/lib/ranch/hex_metadata.config
new file mode 100644
index 0000000..1ecbc02
--- /dev/null
+++ b/server/_build/default/lib/ranch/hex_metadata.config
@@ -0,0 +1,22 @@
+{<<"app">>,<<"ranch">>}.
+{<<"build_tools">>,[<<"make">>,<<"rebar3">>]}.
+{<<"description">>,<<"Socket acceptor pool for TCP protocols.">>}.
+{<<"files">>,
+ [<<"ebin/ranch.app">>,<<"erlang.mk">>,<<"LICENSE">>,<<"Makefile">>,
+ <<"README.asciidoc">>,<<"src/ranch.erl">>,<<"src/ranch_acceptor.erl">>,
+ <<"src/ranch_acceptors_sup.erl">>,<<"src/ranch_app.erl">>,
+ <<"src/ranch_conns_sup.erl">>,<<"src/ranch_crc32c.erl">>,
+ <<"src/ranch_listener_sup.erl">>,<<"src/ranch_protocol.erl">>,
+ <<"src/ranch_proxy_header.erl">>,<<"src/ranch_server.erl">>,
+ <<"src/ranch_ssl.erl">>,<<"src/ranch_sup.erl">>,<<"src/ranch_tcp.erl">>,
+ <<"src/ranch_transport.erl">>]}.
+{<<"licenses">>,[<<"ISC">>]}.
+{<<"links">>,
+ [{<<"Function reference">>,
+ <<"https://ninenines.eu/docs/en/ranch/1.8/manual/">>},
+ {<<"GitHub">>,<<"https://github.com/ninenines/ranch">>},
+ {<<"Sponsor">>,<<"https://github.com/sponsors/essen">>},
+ {<<"User guide">>,<<"https://ninenines.eu/docs/en/ranch/1.8/guide/">>}]}.
+{<<"name">>,<<"ranch">>}.
+{<<"requirements">>,[]}.
+{<<"version">>,<<"1.8.0">>}.
diff --git a/server/_build/default/lib/ranch/src/ranch.erl b/server/_build/default/lib/ranch/src/ranch.erl
new file mode 100644
index 0000000..814e928
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch.erl
@@ -0,0 +1,504 @@
+%% Copyright (c) 2011-2018, 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(ranch).
+
+-export([start_listener/5]).
+-export([start_listener/6]).
+-export([normalize_opts/1]).
+-export([stop_listener/1]).
+-export([suspend_listener/1]).
+-export([resume_listener/1]).
+-export([child_spec/5]).
+-export([child_spec/6]).
+-export([accept_ack/1]).
+-export([handshake/1]).
+-export([handshake/2]).
+-export([recv_proxy_header/2]).
+-export([remove_connection/1]).
+-export([get_status/1]).
+-export([get_addr/1]).
+-export([get_port/1]).
+-export([get_max_connections/1]).
+-export([set_max_connections/2]).
+-export([get_transport_options/1]).
+-export([set_transport_options/2]).
+-export([get_protocol_options/1]).
+-export([set_protocol_options/2]).
+-export([info/0]).
+-export([info/1]).
+-export([procs/2]).
+-export([wait_for_connections/3]).
+-export([wait_for_connections/4]).
+-export([filter_options/3]).
+-export([set_option_default/3]).
+-export([require/1]).
+-export([log/4]).
+
+-deprecated([start_listener/6, child_spec/6, accept_ack/1]).
+
+-type max_conns() :: non_neg_integer() | infinity.
+-export_type([max_conns/0]).
+
+%% This type is deprecated and will be removed in Ranch 2.0.
+-type opt() :: {ack_timeout, timeout()}
+ | {connection_type, worker | supervisor}
+ | {max_connections, max_conns()}
+ | {num_acceptors, pos_integer()}
+ | {shutdown, timeout() | brutal_kill}
+ | {socket, any()}.
+-export_type([opt/0]).
+
+-type opts() :: any() | #{
+ connection_type => worker | supervisor,
+ handshake_timeout => timeout(),
+ max_connections => max_conns(),
+ logger => module(),
+ num_acceptors => pos_integer(),
+ shutdown => timeout() | brutal_kill,
+ socket => any(),
+ socket_opts => any()
+}.
+-export_type([opts/0]).
+
+-type ref() :: any().
+-export_type([ref/0]).
+
+-spec start_listener(ref(), module(), opts(), module(), any())
+ -> supervisor:startchild_ret().
+start_listener(Ref, Transport, TransOpts0, Protocol, ProtoOpts)
+ when is_atom(Transport), is_atom(Protocol) ->
+ TransOpts = normalize_opts(TransOpts0),
+ _ = code:ensure_loaded(Transport),
+ case erlang:function_exported(Transport, name, 0) of
+ false ->
+ {error, badarg};
+ true ->
+ Res = supervisor:start_child(ranch_sup, child_spec(Ref,
+ Transport, TransOpts, Protocol, ProtoOpts)),
+ Socket = maps:get(socket, TransOpts, undefined),
+ case Res of
+ {ok, Pid} when Socket =/= undefined ->
+ %% Give ownership of the socket to ranch_acceptors_sup
+ %% to make sure the socket stays open as long as the
+ %% listener is alive. If the socket closes however there
+ %% will be no way to recover because we don't know how
+ %% to open it again.
+ Children = supervisor:which_children(Pid),
+ {_, AcceptorsSup, _, _}
+ = lists:keyfind(ranch_acceptors_sup, 1, Children),
+ Transport:controlling_process(Socket, AcceptorsSup);
+ _ ->
+ ok
+ end,
+ maybe_started(Res)
+ end.
+
+-spec start_listener(ref(), non_neg_integer(), module(), opts(), module(), any())
+ -> supervisor:startchild_ret().
+start_listener(Ref, NumAcceptors, Transport, TransOpts0, Protocol, ProtoOpts)
+ when is_integer(NumAcceptors), is_atom(Transport), is_atom(Protocol) ->
+ TransOpts = normalize_opts(TransOpts0),
+ start_listener(Ref, Transport, TransOpts#{num_acceptors => NumAcceptors},
+ Protocol, ProtoOpts).
+
+-spec normalize_opts(opts()) -> opts().
+normalize_opts(Map) when is_map(Map) ->
+ Map;
+normalize_opts(List0) when is_list(List0) ->
+ Map0 = #{},
+ {Map1, List1} = case take(ack_timeout, List0) of
+ {value, HandshakeTimeout, Tail0} ->
+ {Map0#{handshake_timeout => HandshakeTimeout}, Tail0};
+ false ->
+ {Map0, List0}
+ end,
+ {Map, List} = lists:foldl(fun(Key, {Map2, List2}) ->
+ case take(Key, List2) of
+ {value, ConnectionType, Tail2} ->
+ {Map2#{Key => ConnectionType}, Tail2};
+ false ->
+ {Map2, List2}
+ end
+ end, {Map1, List1}, [connection_type, max_connections, num_acceptors, shutdown, socket]),
+ if
+ Map =:= #{} ->
+ ok;
+ true ->
+ log(warning,
+ "Setting Ranch options together with socket options "
+ "is deprecated. Please use the new map syntax that allows "
+ "specifying socket options separately from other options.~n",
+ [], Map)
+ end,
+ case List of
+ [] -> Map;
+ _ -> Map#{socket_opts => List}
+ end;
+normalize_opts(Any) ->
+ #{socket_opts => Any}.
+
+take(Key, List) ->
+ take(Key, List, []).
+
+take(_, [], _) ->
+ false;
+take(Key, [{Key, Value}|Tail], Acc) ->
+ {value, Value, lists:reverse(Acc, Tail)};
+take(Key, [Value|Tail], Acc) ->
+ take(Key, Tail, [Value|Acc]).
+
+maybe_started({error, {{shutdown,
+ {failed_to_start_child, ranch_acceptors_sup,
+ {listen_error, _, Reason}}}, _}} = Error) ->
+ start_error(Reason, Error);
+maybe_started(Res) ->
+ Res.
+
+start_error(E=eaddrinuse, _) -> {error, E};
+start_error(E=eacces, _) -> {error, E};
+start_error(E=no_cert, _) -> {error, E};
+start_error(_, Error) -> Error.
+
+-spec stop_listener(ref()) -> ok | {error, not_found}.
+stop_listener(Ref) ->
+ case supervisor:terminate_child(ranch_sup, {ranch_listener_sup, Ref}) of
+ ok ->
+ _ = supervisor:delete_child(ranch_sup, {ranch_listener_sup, Ref}),
+ ranch_server:cleanup_listener_opts(Ref);
+ {error, Reason} ->
+ {error, Reason}
+ end.
+
+-spec suspend_listener(ref()) -> ok | {error, any()}.
+suspend_listener(Ref) ->
+ case get_status(Ref) of
+ running ->
+ ListenerSup = ranch_server:get_listener_sup(Ref),
+ ok = ranch_server:set_addr(Ref, {undefined, undefined}),
+ supervisor:terminate_child(ListenerSup, ranch_acceptors_sup);
+ suspended ->
+ ok
+ end.
+
+-spec resume_listener(ref()) -> ok | {error, any()}.
+resume_listener(Ref) ->
+ case get_status(Ref) of
+ running ->
+ ok;
+ suspended ->
+ ListenerSup = ranch_server:get_listener_sup(Ref),
+ Res = supervisor:restart_child(ListenerSup, ranch_acceptors_sup),
+ maybe_resumed(Res)
+ end.
+
+maybe_resumed(Error={error, {listen_error, _, Reason}}) ->
+ start_error(Reason, Error);
+maybe_resumed({ok, _}) ->
+ ok;
+maybe_resumed({ok, _, _}) ->
+ ok;
+maybe_resumed(Res) ->
+ Res.
+
+-spec child_spec(ref(), module(), opts(), module(), any())
+ -> supervisor:child_spec().
+child_spec(Ref, Transport, TransOpts0, Protocol, ProtoOpts) ->
+ TransOpts = normalize_opts(TransOpts0),
+ {{ranch_listener_sup, Ref}, {ranch_listener_sup, start_link, [
+ Ref, Transport, TransOpts, Protocol, ProtoOpts
+ ]}, permanent, infinity, supervisor, [ranch_listener_sup]}.
+
+-spec child_spec(ref(), non_neg_integer(), module(), opts(), module(), any())
+ -> supervisor:child_spec().
+child_spec(Ref, NumAcceptors, Transport, TransOpts0, Protocol, ProtoOpts)
+ when is_integer(NumAcceptors), is_atom(Transport), is_atom(Protocol) ->
+ TransOpts = normalize_opts(TransOpts0),
+ child_spec(Ref, Transport, TransOpts#{num_acceptors => NumAcceptors},
+ Protocol, ProtoOpts).
+
+-spec accept_ack(ref()) -> ok.
+accept_ack(Ref) ->
+ {ok, _} = handshake(Ref),
+ ok.
+
+-spec handshake(ref()) -> {ok, ranch_transport:socket()}.
+handshake(Ref) ->
+ handshake(Ref, []).
+
+-spec handshake(ref(), any()) -> {ok, ranch_transport:socket()}.
+handshake(Ref, Opts) ->
+ receive {handshake, Ref, Transport, CSocket, HandshakeTimeout} ->
+ case Transport:handshake(CSocket, Opts, HandshakeTimeout) of
+ OK = {ok, _} ->
+ OK;
+ %% Garbage was most likely sent to the socket, don't error out.
+ {error, {tls_alert, _}} ->
+ ok = Transport:close(CSocket),
+ exit(normal);
+ %% Socket most likely stopped responding, don't error out.
+ {error, Reason} when Reason =:= timeout; Reason =:= closed ->
+ ok = Transport:close(CSocket),
+ exit(normal);
+ {error, Reason} ->
+ ok = Transport:close(CSocket),
+ error(Reason)
+ end
+ end.
+
+%% Unlike handshake/2 this function always return errors because
+%% the communication between the proxy and the server are expected
+%% to be reliable. If there is a problem while receiving the proxy
+%% header, we probably want to know about it.
+-spec recv_proxy_header(ref(), timeout())
+ -> {ok, ranch_proxy_header:proxy_info()}
+ | {error, closed | atom()}
+ | {error, protocol_error, atom()}.
+recv_proxy_header(Ref, Timeout) ->
+ receive HandshakeState={handshake, Ref, Transport, CSocket, _} ->
+ self() ! HandshakeState,
+ Transport:recv_proxy_header(CSocket, Timeout)
+ end.
+
+-spec remove_connection(ref()) -> ok.
+remove_connection(Ref) ->
+ ConnsSup = ranch_server:get_connections_sup(Ref),
+ ConnsSup ! {remove_connection, Ref, self()},
+ ok.
+
+-spec get_status(ref()) -> running | suspended.
+get_status(Ref) ->
+ ListenerSup = ranch_server:get_listener_sup(Ref),
+ Children = supervisor:which_children(ListenerSup),
+ case lists:keyfind(ranch_acceptors_sup, 1, Children) of
+ {_, undefined, _, _} ->
+ suspended;
+ _ ->
+ running
+ end.
+
+-spec get_addr(ref()) -> {inet:ip_address(), inet:port_number()} | {undefined, undefined}.
+get_addr(Ref) ->
+ ranch_server:get_addr(Ref).
+
+-spec get_port(ref()) -> inet:port_number() | undefined.
+get_port(Ref) ->
+ {_, Port} = get_addr(Ref),
+ Port.
+
+-spec get_max_connections(ref()) -> max_conns().
+get_max_connections(Ref) ->
+ ranch_server:get_max_connections(Ref).
+
+-spec set_max_connections(ref(), max_conns()) -> ok.
+set_max_connections(Ref, MaxConnections) ->
+ ranch_server:set_max_connections(Ref, MaxConnections).
+
+-spec get_transport_options(ref()) -> any().
+get_transport_options(Ref) ->
+ ranch_server:get_transport_options(Ref).
+
+-spec set_transport_options(ref(), opts()) -> ok | {error, running}.
+set_transport_options(Ref, TransOpts0) ->
+ TransOpts = normalize_opts(TransOpts0),
+ case get_status(Ref) of
+ suspended ->
+ ok = ranch_server:set_transport_options(Ref, TransOpts);
+ running ->
+ {error, running}
+ end.
+
+-spec get_protocol_options(ref()) -> opts().
+get_protocol_options(Ref) ->
+ ranch_server:get_protocol_options(Ref).
+
+-spec set_protocol_options(ref(), any()) -> ok.
+set_protocol_options(Ref, Opts) ->
+ ranch_server:set_protocol_options(Ref, Opts).
+
+-spec info() -> [{any(), [{atom(), any()}]}].
+info() ->
+ [{Ref, listener_info(Ref, Pid)}
+ || {Ref, Pid} <- ranch_server:get_listener_sups()].
+
+-spec info(ref()) -> [{atom(), any()}].
+info(Ref) ->
+ Pid = ranch_server:get_listener_sup(Ref),
+ listener_info(Ref, Pid).
+
+listener_info(Ref, Pid) ->
+ [_, Transport, _, Protocol, _] = ranch_server:get_listener_start_args(Ref),
+ ConnsSup = ranch_server:get_connections_sup(Ref),
+ Status = get_status(Ref),
+ {IP, Port} = get_addr(Ref),
+ MaxConns = get_max_connections(Ref),
+ TransOpts = ranch_server:get_transport_options(Ref),
+ ProtoOpts = get_protocol_options(Ref),
+ [
+ {pid, Pid},
+ {status, Status},
+ {ip, IP},
+ {port, Port},
+ {max_connections, MaxConns},
+ {active_connections, ranch_conns_sup:active_connections(ConnsSup)},
+ {all_connections, proplists:get_value(active, supervisor:count_children(ConnsSup))},
+ {transport, Transport},
+ {transport_options, TransOpts},
+ {protocol, Protocol},
+ {protocol_options, ProtoOpts}
+ ].
+
+-spec procs(ref(), acceptors | connections) -> [pid()].
+procs(Ref, acceptors) ->
+ procs1(Ref, ranch_acceptors_sup);
+procs(Ref, connections) ->
+ procs1(Ref, ranch_conns_sup).
+
+procs1(Ref, Sup) ->
+ ListenerSup = ranch_server:get_listener_sup(Ref),
+ {_, SupPid, _, _} = lists:keyfind(Sup, 1,
+ supervisor:which_children(ListenerSup)),
+ try
+ [Pid || {_, Pid, _, _} <- supervisor:which_children(SupPid)]
+ catch exit:{noproc, _} when Sup =:= ranch_acceptors_sup ->
+ []
+ end.
+
+-spec wait_for_connections
+ (ref(), '>' | '>=' | '==' | '=<', non_neg_integer()) -> ok;
+ (ref(), '<', pos_integer()) -> ok.
+wait_for_connections(Ref, Op, NumConns) ->
+ wait_for_connections(Ref, Op, NumConns, 1000).
+
+-spec wait_for_connections
+ (ref(), '>' | '>=' | '==' | '=<', non_neg_integer(), non_neg_integer()) -> ok;
+ (ref(), '<', pos_integer(), non_neg_integer()) -> ok.
+wait_for_connections(Ref, Op, NumConns, Interval) ->
+ validate_op(Op, NumConns),
+ validate_num_conns(NumConns),
+ validate_interval(Interval),
+ wait_for_connections_loop(Ref, Op, NumConns, Interval).
+
+validate_op('>', _) -> ok;
+validate_op('>=', _) -> ok;
+validate_op('==', _) -> ok;
+validate_op('=<', _) -> ok;
+validate_op('<', NumConns) when NumConns > 0 -> ok;
+validate_op(_, _) -> error(badarg).
+
+validate_num_conns(NumConns) when is_integer(NumConns), NumConns >= 0 -> ok;
+validate_num_conns(_) -> error(badarg).
+
+validate_interval(Interval) when is_integer(Interval), Interval >= 0 -> ok;
+validate_interval(_) -> error(badarg).
+
+wait_for_connections_loop(Ref, Op, NumConns, Interval) ->
+ CurConns = try
+ ConnsSup = ranch_server:get_connections_sup(Ref),
+ proplists:get_value(active, supervisor:count_children(ConnsSup))
+ catch _:_ ->
+ 0
+ end,
+ case erlang:Op(CurConns, NumConns) of
+ true ->
+ ok;
+ false when Interval =:= 0 ->
+ wait_for_connections_loop(Ref, Op, NumConns, Interval);
+ false ->
+ timer:sleep(Interval),
+ wait_for_connections_loop(Ref, Op, NumConns, Interval)
+ end.
+
+-spec filter_options([inet | inet6 | {atom(), any()} | {raw, any(), any(), any()}],
+ [atom()], Acc) -> Acc when Acc :: [any()].
+filter_options(UserOptions, DisallowedKeys, DefaultOptions) ->
+ AllowedOptions = filter_user_options(UserOptions, DisallowedKeys),
+ lists:foldl(fun merge_options/2, DefaultOptions, AllowedOptions).
+
+%% 2-tuple options.
+filter_user_options([Opt = {Key, _}|Tail], DisallowedKeys) ->
+ case lists:member(Key, DisallowedKeys) of
+ false ->
+ [Opt|filter_user_options(Tail, DisallowedKeys)];
+ true ->
+ filter_options_warning(Opt),
+ filter_user_options(Tail, DisallowedKeys)
+ end;
+%% Special option forms.
+filter_user_options([inet|Tail], DisallowedKeys) ->
+ [inet|filter_user_options(Tail, DisallowedKeys)];
+filter_user_options([inet6|Tail], DisallowedKeys) ->
+ [inet6|filter_user_options(Tail, DisallowedKeys)];
+filter_user_options([Opt = {raw, _, _, _}|Tail], DisallowedKeys) ->
+ [Opt|filter_user_options(Tail, DisallowedKeys)];
+filter_user_options([Opt|Tail], DisallowedKeys) ->
+ filter_options_warning(Opt),
+ filter_user_options(Tail, DisallowedKeys);
+filter_user_options([], _) ->
+ [].
+
+filter_options_warning(Opt) ->
+ Logger = case get(logger) of
+ undefined -> error_logger;
+ Logger0 -> Logger0
+ end,
+ log(warning,
+ "Transport option ~p unknown or invalid.~n",
+ [Opt], Logger).
+
+merge_options({Key, _} = Option, OptionList) ->
+ lists:keystore(Key, 1, OptionList, Option);
+merge_options(Option, OptionList) ->
+ [Option|OptionList].
+
+-spec set_option_default(Opts, atom(), any())
+ -> Opts when Opts :: [{atom(), any()}].
+set_option_default(Opts, Key, Value) ->
+ case lists:keymember(Key, 1, Opts) of
+ true -> Opts;
+ false -> [{Key, Value}|Opts]
+ end.
+
+-spec require([atom()]) -> ok.
+require([]) ->
+ ok;
+require([App|Tail]) ->
+ case application:start(App) of
+ ok -> ok;
+ {error, {already_started, App}} -> ok
+ end,
+ require(Tail).
+
+-spec log(logger:level(), io:format(), list(), module() | #{logger => module()}) -> ok.
+log(Level, Format, Args, Logger) when is_atom(Logger) ->
+ log(Level, Format, Args, #{logger => Logger});
+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/ranch/src/ranch_acceptor.erl b/server/_build/default/lib/ranch/src/ranch_acceptor.erl
new file mode 100644
index 0000000..3e426bd
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_acceptor.erl
@@ -0,0 +1,64 @@
+%% Copyright (c) 2011-2018, 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(ranch_acceptor).
+
+-export([start_link/4]).
+-export([loop/4]).
+
+-spec start_link(inet:socket(), module(), module(), pid())
+ -> {ok, pid()}.
+start_link(LSocket, Transport, Logger, ConnsSup) ->
+ Pid = spawn_link(?MODULE, loop, [LSocket, Transport, Logger, ConnsSup]),
+ {ok, Pid}.
+
+-spec loop(inet:socket(), module(), module(), pid()) -> no_return().
+loop(LSocket, Transport, Logger, ConnsSup) ->
+ _ = case Transport:accept(LSocket, infinity) of
+ {ok, CSocket} ->
+ case Transport:controlling_process(CSocket, ConnsSup) of
+ ok ->
+ %% This call will not return until process has been started
+ %% AND we are below the maximum number of connections.
+ ranch_conns_sup:start_protocol(ConnsSup, CSocket);
+ {error, _} ->
+ Transport:close(CSocket)
+ end;
+ %% Reduce the accept rate if we run out of file descriptors.
+ %% We can't accept anymore anyway, so we might as well wait
+ %% a little for the situation to resolve itself.
+ {error, emfile} ->
+ ranch:log(warning,
+ "Ranch acceptor reducing accept rate: out of file descriptors~n",
+ [], Logger),
+ receive after 100 -> ok end;
+ %% Exit if the listening socket got closed.
+ {error, closed} ->
+ exit(closed);
+ %% Continue otherwise.
+ {error, _} ->
+ ok
+ end,
+ flush(Logger),
+ ?MODULE:loop(LSocket, Transport, Logger, ConnsSup).
+
+flush(Logger) ->
+ receive Msg ->
+ ranch:log(warning,
+ "Ranch acceptor received unexpected message: ~p~n",
+ [Msg], Logger),
+ flush(Logger)
+ after 0 ->
+ ok
+ end.
diff --git a/server/_build/default/lib/ranch/src/ranch_acceptors_sup.erl b/server/_build/default/lib/ranch/src/ranch_acceptors_sup.erl
new file mode 100644
index 0000000..73dc9ea
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_acceptors_sup.erl
@@ -0,0 +1,71 @@
+%% Copyright (c) 2011-2018, 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(ranch_acceptors_sup).
+-behaviour(supervisor).
+
+-export([start_link/2]).
+-export([init/1]).
+
+-spec start_link(ranch:ref(), module())
+ -> {ok, pid()}.
+start_link(Ref, Transport) ->
+ supervisor:start_link(?MODULE, [Ref, Transport]).
+
+init([Ref, Transport]) ->
+ ConnsSup = ranch_server:get_connections_sup(Ref),
+ TransOpts = ranch_server:get_transport_options(Ref),
+ NumAcceptors = maps:get(num_acceptors, TransOpts, 10),
+ Logger = maps:get(logger, TransOpts, error_logger),
+ LSocket = case maps:get(socket, TransOpts, undefined) of
+ undefined ->
+ SocketOpts = maps:get(socket_opts, TransOpts, []),
+ %% We temporarily put the logger in the process dictionary
+ %% so that it can be used from ranch:filter_options. The
+ %% interface as it currently is does not allow passing it
+ %% down otherwise.
+ put(logger, Logger),
+ case Transport:listen(SocketOpts) of
+ {ok, Socket} ->
+ erase(logger),
+ Socket;
+ {error, Reason} ->
+ listen_error(Ref, Transport, SocketOpts, Reason, Logger)
+ end;
+ Socket ->
+ Socket
+ end,
+ {ok, Addr} = Transport:sockname(LSocket),
+ ranch_server:set_addr(Ref, Addr),
+ Procs = [
+ {{acceptor, self(), N}, {ranch_acceptor, start_link, [
+ LSocket, Transport, Logger, ConnsSup
+ ]}, permanent, brutal_kill, worker, []}
+ || N <- lists:seq(1, NumAcceptors)],
+ {ok, {{one_for_one, 1, 5}, Procs}}.
+
+-spec listen_error(any(), module(), any(), atom(), module()) -> no_return().
+listen_error(Ref, Transport, SocketOpts0, Reason, Logger) ->
+ SocketOpts1 = [{cert, '...'}|proplists:delete(cert, SocketOpts0)],
+ SocketOpts2 = [{key, '...'}|proplists:delete(key, SocketOpts1)],
+ SocketOpts = [{cacerts, '...'}|proplists:delete(cacerts, SocketOpts2)],
+ ranch:log(error,
+ "Failed to start Ranch listener ~p in ~p:listen(~999999p) for reason ~p (~s)~n",
+ [Ref, Transport, SocketOpts, Reason, format_error(Reason)], Logger),
+ exit({listen_error, Ref, Reason}).
+
+format_error(no_cert) ->
+ "no certificate provided; see cert, certfile, sni_fun or sni_hosts options";
+format_error(Reason) ->
+ inet:format_error(Reason).
diff --git a/server/_build/default/lib/ranch/src/ranch_app.erl b/server/_build/default/lib/ranch/src/ranch_app.erl
new file mode 100644
index 0000000..8ac470e
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_app.erl
@@ -0,0 +1,44 @@
+%% Copyright (c) 2011-2018, 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(ranch_app).
+-behaviour(application).
+
+-export([start/2]).
+-export([stop/1]).
+-export([profile_output/0]).
+
+start(_, _) ->
+ _ = consider_profiling(),
+ ranch_sup:start_link().
+
+stop(_) ->
+ ok.
+
+-spec profile_output() -> ok.
+profile_output() ->
+ eprof:stop_profiling(),
+ eprof:log("procs.profile"),
+ eprof:analyze(procs),
+ eprof:log("total.profile"),
+ eprof:analyze(total).
+
+consider_profiling() ->
+ case application:get_env(profile) of
+ {ok, true} ->
+ {ok, _Pid} = eprof:start(),
+ eprof:start_profiling([self()]);
+ _ ->
+ not_profiling
+ end.
diff --git a/server/_build/default/lib/ranch/src/ranch_conns_sup.erl b/server/_build/default/lib/ranch/src/ranch_conns_sup.erl
new file mode 100644
index 0000000..fe2237a
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_conns_sup.erl
@@ -0,0 +1,325 @@
+%% Copyright (c) 2011-2018, 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.
+
+%% Make sure to never reload this module outside a release upgrade,
+%% as calling l(ranch_conns_sup) twice will kill the process and all
+%% the currently open connections.
+-module(ranch_conns_sup).
+
+%% API.
+-export([start_link/3]).
+-export([start_protocol/2]).
+-export([active_connections/1]).
+
+%% Supervisor internals.
+-export([init/4]).
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+-type conn_type() :: worker | supervisor.
+-type shutdown() :: brutal_kill | timeout().
+
+-record(state, {
+ parent = undefined :: pid(),
+ ref :: ranch:ref(),
+ conn_type :: conn_type(),
+ shutdown :: shutdown(),
+ transport = undefined :: module(),
+ protocol = undefined :: module(),
+ opts :: any(),
+ handshake_timeout :: timeout(),
+ max_conns = undefined :: ranch:max_conns(),
+ logger = undefined :: module()
+}).
+
+%% API.
+
+-spec start_link(ranch:ref(), module(), module()) -> {ok, pid()}.
+start_link(Ref, Transport, Protocol) ->
+ proc_lib:start_link(?MODULE, init,
+ [self(), Ref, Transport, Protocol]).
+
+%% We can safely assume we are on the same node as the supervisor.
+%%
+%% We can also safely avoid having a monitor and a timeout here
+%% because only three things can happen:
+%% * The supervisor died; rest_for_one strategy killed all acceptors
+%% so this very calling process is going to di--
+%% * There's too many connections, the supervisor will resume the
+%% acceptor only when we get below the limit again.
+%% * The supervisor is overloaded, there's either too many acceptors
+%% or the max_connections limit is too large. It's better if we
+%% don't keep accepting connections because this leaves
+%% more room for the situation to be resolved.
+%%
+%% We do not need the reply, we only need the ok from the supervisor
+%% to continue. The supervisor sends its own pid when the acceptor can
+%% continue.
+-spec start_protocol(pid(), inet:socket()) -> ok.
+start_protocol(SupPid, Socket) ->
+ SupPid ! {?MODULE, start_protocol, self(), Socket},
+ receive SupPid -> ok end.
+
+%% We can't make the above assumptions here. This function might be
+%% called from anywhere.
+-spec active_connections(pid()) -> non_neg_integer().
+active_connections(SupPid) ->
+ Tag = erlang:monitor(process, SupPid),
+ catch erlang:send(SupPid, {?MODULE, active_connections, self(), Tag},
+ [noconnect]),
+ receive
+ {Tag, Ret} ->
+ erlang:demonitor(Tag, [flush]),
+ Ret;
+ {'DOWN', Tag, _, _, noconnection} ->
+ exit({nodedown, node(SupPid)});
+ {'DOWN', Tag, _, _, Reason} ->
+ exit(Reason)
+ after 5000 ->
+ erlang:demonitor(Tag, [flush]),
+ exit(timeout)
+ end.
+
+%% Supervisor internals.
+
+-spec init(pid(), ranch:ref(), module(), module()) -> no_return().
+init(Parent, Ref, Transport, Protocol) ->
+ process_flag(trap_exit, true),
+ ok = ranch_server:set_connections_sup(Ref, self()),
+ MaxConns = ranch_server:get_max_connections(Ref),
+ TransOpts = ranch_server:get_transport_options(Ref),
+ ConnType = maps:get(connection_type, TransOpts, worker),
+ Shutdown = maps:get(shutdown, TransOpts, 5000),
+ HandshakeTimeout = maps:get(handshake_timeout, TransOpts, 5000),
+ Logger = maps:get(logger, TransOpts, error_logger),
+ ProtoOpts = ranch_server:get_protocol_options(Ref),
+ ok = proc_lib:init_ack(Parent, {ok, self()}),
+ loop(#state{parent=Parent, ref=Ref, conn_type=ConnType,
+ shutdown=Shutdown, transport=Transport, protocol=Protocol,
+ opts=ProtoOpts, handshake_timeout=HandshakeTimeout,
+ max_conns=MaxConns, logger=Logger}, 0, 0, []).
+
+loop(State=#state{parent=Parent, ref=Ref, conn_type=ConnType,
+ transport=Transport, protocol=Protocol, opts=Opts,
+ max_conns=MaxConns, logger=Logger}, CurConns, NbChildren, Sleepers) ->
+ receive
+ {?MODULE, start_protocol, To, Socket} ->
+ try Protocol:start_link(Ref, Socket, Transport, Opts) of
+ {ok, Pid} ->
+ handshake(State, CurConns, NbChildren, Sleepers, To, Socket, Pid, Pid);
+ {ok, SupPid, ProtocolPid} when ConnType =:= supervisor ->
+ handshake(State, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid);
+ Ret ->
+ To ! self(),
+ ranch:log(error,
+ "Ranch listener ~p connection process start failure; "
+ "~p:start_link/4 returned: ~999999p~n",
+ [Ref, Protocol, Ret], Logger),
+ Transport:close(Socket),
+ loop(State, CurConns, NbChildren, Sleepers)
+ catch Class:Reason ->
+ To ! self(),
+ ranch:log(error,
+ "Ranch listener ~p connection process start failure; "
+ "~p:start_link/4 crashed with reason: ~p:~999999p~n",
+ [Ref, Protocol, Class, Reason], Logger),
+ loop(State, CurConns, NbChildren, Sleepers)
+ end;
+ {?MODULE, active_connections, To, Tag} ->
+ To ! {Tag, CurConns},
+ loop(State, CurConns, NbChildren, Sleepers);
+ %% Remove a connection from the count of connections.
+ {remove_connection, Ref, Pid} ->
+ case put(Pid, removed) of
+ active ->
+ loop(State, CurConns - 1, NbChildren, Sleepers);
+ remove ->
+ loop(State, CurConns, NbChildren, Sleepers);
+ undefined ->
+ _ = erase(Pid),
+ loop(State, CurConns, NbChildren, Sleepers)
+ end;
+ %% Upgrade the max number of connections allowed concurrently.
+ %% We resume all sleeping acceptors if this number increases.
+ {set_max_conns, MaxConns2} when MaxConns2 > MaxConns ->
+ _ = [To ! self() || To <- Sleepers],
+ loop(State#state{max_conns=MaxConns2},
+ CurConns, NbChildren, []);
+ {set_max_conns, MaxConns2} ->
+ loop(State#state{max_conns=MaxConns2},
+ CurConns, NbChildren, Sleepers);
+ %% Upgrade the protocol options.
+ {set_opts, Opts2} ->
+ loop(State#state{opts=Opts2},
+ CurConns, NbChildren, Sleepers);
+ {'EXIT', Parent, Reason} ->
+ terminate(State, Reason, NbChildren);
+ {'EXIT', Pid, Reason} when Sleepers =:= [] ->
+ case erase(Pid) of
+ active ->
+ report_error(Logger, Ref, Protocol, Pid, Reason),
+ loop(State, CurConns - 1, NbChildren - 1, Sleepers);
+ removed ->
+ report_error(Logger, Ref, Protocol, Pid, Reason),
+ loop(State, CurConns, NbChildren - 1, Sleepers);
+ undefined ->
+ loop(State, CurConns, NbChildren, Sleepers)
+ end;
+ %% Resume a sleeping acceptor if needed.
+ {'EXIT', Pid, Reason} ->
+ case erase(Pid) of
+ active when CurConns > MaxConns ->
+ report_error(Logger, Ref, Protocol, Pid, Reason),
+ loop(State, CurConns - 1, NbChildren - 1, Sleepers);
+ active ->
+ report_error(Logger, Ref, Protocol, Pid, Reason),
+ [To|Sleepers2] = Sleepers,
+ To ! self(),
+ loop(State, CurConns - 1, NbChildren - 1, Sleepers2);
+ removed ->
+ report_error(Logger, Ref, Protocol, Pid, Reason),
+ loop(State, CurConns, NbChildren - 1, Sleepers);
+ undefined ->
+ loop(State, CurConns, NbChildren, Sleepers)
+ end;
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [],
+ {State, CurConns, NbChildren, Sleepers});
+ %% Calls from the supervisor module.
+ {'$gen_call', {To, Tag}, which_children} ->
+ Children = [{Protocol, Pid, ConnType, [Protocol]}
+ || {Pid, Type} <- get(),
+ Type =:= active orelse Type =:= removed],
+ To ! {Tag, Children},
+ loop(State, CurConns, NbChildren, Sleepers);
+ {'$gen_call', {To, Tag}, count_children} ->
+ Counts = case ConnType of
+ worker -> [{supervisors, 0}, {workers, NbChildren}];
+ supervisor -> [{supervisors, NbChildren}, {workers, 0}]
+ end,
+ Counts2 = [{specs, 1}, {active, NbChildren}|Counts],
+ To ! {Tag, Counts2},
+ loop(State, CurConns, NbChildren, Sleepers);
+ {'$gen_call', {To, Tag}, _} ->
+ To ! {Tag, {error, ?MODULE}},
+ loop(State, CurConns, NbChildren, Sleepers);
+ Msg ->
+ ranch:log(error,
+ "Ranch listener ~p received unexpected message ~p~n",
+ [Ref, Msg], Logger),
+ loop(State, CurConns, NbChildren, Sleepers)
+ end.
+
+handshake(State=#state{ref=Ref, transport=Transport, handshake_timeout=HandshakeTimeout,
+ max_conns=MaxConns}, CurConns, NbChildren, Sleepers, To, Socket, SupPid, ProtocolPid) ->
+ case Transport:controlling_process(Socket, ProtocolPid) of
+ ok ->
+ ProtocolPid ! {handshake, Ref, Transport, Socket, HandshakeTimeout},
+ put(SupPid, active),
+ CurConns2 = CurConns + 1,
+ if CurConns2 < MaxConns ->
+ To ! self(),
+ loop(State, CurConns2, NbChildren + 1, Sleepers);
+ true ->
+ loop(State, CurConns2, NbChildren + 1, [To|Sleepers])
+ end;
+ {error, _} ->
+ Transport:close(Socket),
+ %% Only kill the supervised pid, because the connection's pid,
+ %% when different, is supposed to be sitting under it and linked.
+ exit(SupPid, kill),
+ To ! self(),
+ loop(State, CurConns, NbChildren, Sleepers)
+ end.
+
+-spec terminate(#state{}, any(), non_neg_integer()) -> no_return().
+terminate(#state{shutdown=brutal_kill}, Reason, _) ->
+ kill_children(get_keys(active)),
+ kill_children(get_keys(removed)),
+ exit(Reason);
+%% Attempt to gracefully shutdown all children.
+terminate(#state{shutdown=Shutdown}, Reason, NbChildren) ->
+ shutdown_children(get_keys(active)),
+ shutdown_children(get_keys(removed)),
+ _ = if
+ Shutdown =:= infinity ->
+ ok;
+ true ->
+ erlang:send_after(Shutdown, self(), kill)
+ end,
+ wait_children(NbChildren),
+ exit(Reason).
+
+%% Kill all children and then exit. We unlink first to avoid
+%% getting a message for each child getting killed.
+kill_children(Pids) ->
+ _ = [begin
+ unlink(P),
+ exit(P, kill)
+ end || P <- Pids],
+ ok.
+
+%% Monitor processes so we can know which ones have shutdown
+%% before the timeout. Unlink so we avoid receiving an extra
+%% message. Then send a shutdown exit signal.
+shutdown_children(Pids) ->
+ _ = [begin
+ monitor(process, P),
+ unlink(P),
+ exit(P, shutdown)
+ end || P <- Pids],
+ ok.
+
+wait_children(0) ->
+ ok;
+wait_children(NbChildren) ->
+ receive
+ {'DOWN', _, process, Pid, _} ->
+ case erase(Pid) of
+ active -> wait_children(NbChildren - 1);
+ removed -> wait_children(NbChildren - 1);
+ _ -> wait_children(NbChildren)
+ end;
+ kill ->
+ Active = get_keys(active),
+ _ = [exit(P, kill) || P <- Active],
+ Removed = get_keys(removed),
+ _ = [exit(P, kill) || P <- Removed],
+ ok
+ end.
+
+system_continue(_, _, {State, CurConns, NbChildren, Sleepers}) ->
+ loop(State, CurConns, NbChildren, Sleepers).
+
+-spec system_terminate(any(), _, _, _) -> no_return().
+system_terminate(Reason, _, _, {State, _, NbChildren, _}) ->
+ terminate(State, Reason, NbChildren).
+
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
+
+%% We use ~999999p here instead of ~w because the latter doesn't
+%% support printable strings.
+report_error(_, _, _, _, normal) ->
+ ok;
+report_error(_, _, _, _, shutdown) ->
+ ok;
+report_error(_, _, _, _, {shutdown, _}) ->
+ ok;
+report_error(Logger, Ref, Protocol, Pid, Reason) ->
+ ranch:log(error,
+ "Ranch listener ~p had connection process started with "
+ "~p:start_link/4 at ~p exit with reason: ~999999p~n",
+ [Ref, Protocol, Pid, Reason], Logger).
diff --git a/server/_build/default/lib/ranch/src/ranch_crc32c.erl b/server/_build/default/lib/ranch/src/ranch_crc32c.erl
new file mode 100644
index 0000000..fc9be35
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_crc32c.erl
@@ -0,0 +1,115 @@
+%% Copyright (c) 2018, 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(ranch_crc32c).
+
+-export([crc32c/1]).
+-export([crc32c/2]).
+
+-define(CRC32C_TABLE, {
+ 16#00000000, 16#F26B8303, 16#E13B70F7, 16#1350F3F4,
+ 16#C79A971F, 16#35F1141C, 16#26A1E7E8, 16#D4CA64EB,
+ 16#8AD958CF, 16#78B2DBCC, 16#6BE22838, 16#9989AB3B,
+ 16#4D43CFD0, 16#BF284CD3, 16#AC78BF27, 16#5E133C24,
+ 16#105EC76F, 16#E235446C, 16#F165B798, 16#030E349B,
+ 16#D7C45070, 16#25AFD373, 16#36FF2087, 16#C494A384,
+ 16#9A879FA0, 16#68EC1CA3, 16#7BBCEF57, 16#89D76C54,
+ 16#5D1D08BF, 16#AF768BBC, 16#BC267848, 16#4E4DFB4B,
+ 16#20BD8EDE, 16#D2D60DDD, 16#C186FE29, 16#33ED7D2A,
+ 16#E72719C1, 16#154C9AC2, 16#061C6936, 16#F477EA35,
+ 16#AA64D611, 16#580F5512, 16#4B5FA6E6, 16#B93425E5,
+ 16#6DFE410E, 16#9F95C20D, 16#8CC531F9, 16#7EAEB2FA,
+ 16#30E349B1, 16#C288CAB2, 16#D1D83946, 16#23B3BA45,
+ 16#F779DEAE, 16#05125DAD, 16#1642AE59, 16#E4292D5A,
+ 16#BA3A117E, 16#4851927D, 16#5B016189, 16#A96AE28A,
+ 16#7DA08661, 16#8FCB0562, 16#9C9BF696, 16#6EF07595,
+ 16#417B1DBC, 16#B3109EBF, 16#A0406D4B, 16#522BEE48,
+ 16#86E18AA3, 16#748A09A0, 16#67DAFA54, 16#95B17957,
+ 16#CBA24573, 16#39C9C670, 16#2A993584, 16#D8F2B687,
+ 16#0C38D26C, 16#FE53516F, 16#ED03A29B, 16#1F682198,
+ 16#5125DAD3, 16#A34E59D0, 16#B01EAA24, 16#42752927,
+ 16#96BF4DCC, 16#64D4CECF, 16#77843D3B, 16#85EFBE38,
+ 16#DBFC821C, 16#2997011F, 16#3AC7F2EB, 16#C8AC71E8,
+ 16#1C661503, 16#EE0D9600, 16#FD5D65F4, 16#0F36E6F7,
+ 16#61C69362, 16#93AD1061, 16#80FDE395, 16#72966096,
+ 16#A65C047D, 16#5437877E, 16#4767748A, 16#B50CF789,
+ 16#EB1FCBAD, 16#197448AE, 16#0A24BB5A, 16#F84F3859,
+ 16#2C855CB2, 16#DEEEDFB1, 16#CDBE2C45, 16#3FD5AF46,
+ 16#7198540D, 16#83F3D70E, 16#90A324FA, 16#62C8A7F9,
+ 16#B602C312, 16#44694011, 16#5739B3E5, 16#A55230E6,
+ 16#FB410CC2, 16#092A8FC1, 16#1A7A7C35, 16#E811FF36,
+ 16#3CDB9BDD, 16#CEB018DE, 16#DDE0EB2A, 16#2F8B6829,
+ 16#82F63B78, 16#709DB87B, 16#63CD4B8F, 16#91A6C88C,
+ 16#456CAC67, 16#B7072F64, 16#A457DC90, 16#563C5F93,
+ 16#082F63B7, 16#FA44E0B4, 16#E9141340, 16#1B7F9043,
+ 16#CFB5F4A8, 16#3DDE77AB, 16#2E8E845F, 16#DCE5075C,
+ 16#92A8FC17, 16#60C37F14, 16#73938CE0, 16#81F80FE3,
+ 16#55326B08, 16#A759E80B, 16#B4091BFF, 16#466298FC,
+ 16#1871A4D8, 16#EA1A27DB, 16#F94AD42F, 16#0B21572C,
+ 16#DFEB33C7, 16#2D80B0C4, 16#3ED04330, 16#CCBBC033,
+ 16#A24BB5A6, 16#502036A5, 16#4370C551, 16#B11B4652,
+ 16#65D122B9, 16#97BAA1BA, 16#84EA524E, 16#7681D14D,
+ 16#2892ED69, 16#DAF96E6A, 16#C9A99D9E, 16#3BC21E9D,
+ 16#EF087A76, 16#1D63F975, 16#0E330A81, 16#FC588982,
+ 16#B21572C9, 16#407EF1CA, 16#532E023E, 16#A145813D,
+ 16#758FE5D6, 16#87E466D5, 16#94B49521, 16#66DF1622,
+ 16#38CC2A06, 16#CAA7A905, 16#D9F75AF1, 16#2B9CD9F2,
+ 16#FF56BD19, 16#0D3D3E1A, 16#1E6DCDEE, 16#EC064EED,
+ 16#C38D26C4, 16#31E6A5C7, 16#22B65633, 16#D0DDD530,
+ 16#0417B1DB, 16#F67C32D8, 16#E52CC12C, 16#1747422F,
+ 16#49547E0B, 16#BB3FFD08, 16#A86F0EFC, 16#5A048DFF,
+ 16#8ECEE914, 16#7CA56A17, 16#6FF599E3, 16#9D9E1AE0,
+ 16#D3D3E1AB, 16#21B862A8, 16#32E8915C, 16#C083125F,
+ 16#144976B4, 16#E622F5B7, 16#F5720643, 16#07198540,
+ 16#590AB964, 16#AB613A67, 16#B831C993, 16#4A5A4A90,
+ 16#9E902E7B, 16#6CFBAD78, 16#7FAB5E8C, 16#8DC0DD8F,
+ 16#E330A81A, 16#115B2B19, 16#020BD8ED, 16#F0605BEE,
+ 16#24AA3F05, 16#D6C1BC06, 16#C5914FF2, 16#37FACCF1,
+ 16#69E9F0D5, 16#9B8273D6, 16#88D28022, 16#7AB90321,
+ 16#AE7367CA, 16#5C18E4C9, 16#4F48173D, 16#BD23943E,
+ 16#F36E6F75, 16#0105EC76, 16#12551F82, 16#E03E9C81,
+ 16#34F4F86A, 16#C69F7B69, 16#D5CF889D, 16#27A40B9E,
+ 16#79B737BA, 16#8BDCB4B9, 16#988C474D, 16#6AE7C44E,
+ 16#BE2DA0A5, 16#4C4623A6, 16#5F16D052, 16#AD7D5351
+}).
+
+%% The interface mirrors erlang:crc32/1,2.
+-spec crc32c(iodata()) -> non_neg_integer().
+crc32c(Data) ->
+ do_crc32c(16#ffffffff, iolist_to_binary(Data)).
+
+-spec crc32c(CRC, iodata()) -> CRC when CRC::non_neg_integer().
+crc32c(OldCrc, Data) ->
+ do_crc32c(OldCrc bxor 16#ffffffff, iolist_to_binary(Data)).
+
+do_crc32c(OldCrc, <<C, Rest/bits>>) ->
+ do_crc32c((OldCrc bsr 8) bxor element(1 + ((OldCrc bxor C) band 16#ff), ?CRC32C_TABLE),
+ Rest);
+do_crc32c(OldCrc, <<>>) ->
+ OldCrc bxor 16#ffffffff.
+
+-ifdef(TEST).
+crc32c_test_() ->
+ Tests = [
+ %% Tests from RFC3720 B.4.
+ {<<0:32/unit:8>>, 16#8a9136aa},
+ {iolist_to_binary([16#ff || _ <- lists:seq(1, 32)]), 16#62a8ab43},
+ {iolist_to_binary([N || N <- lists:seq(0, 16#1f)]), 16#46dd794e},
+ {iolist_to_binary([N || N <- lists:seq(16#1f, 0, -1)]), 16#113fdb5c},
+ {<<16#01c00000:32, 0:32, 0:32, 0:32, 16#14000000:32, 16#00000400:32, 16#00000014:32,
+ 16#00000018:32, 16#28000000:32, 0:32, 16#02000000:32, 0:32>>, 16#d9963a56}
+ ],
+ [{iolist_to_binary(io_lib:format("16#~8.16.0b", [R])),
+ fun() -> R = crc32c(V) end} || {V, R} <- Tests].
+-endif.
diff --git a/server/_build/default/lib/ranch/src/ranch_listener_sup.erl b/server/_build/default/lib/ranch/src/ranch_listener_sup.erl
new file mode 100644
index 0000000..3853425
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_listener_sup.erl
@@ -0,0 +1,41 @@
+%% Copyright (c) 2011-2018, 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(ranch_listener_sup).
+-behaviour(supervisor).
+
+-export([start_link/5]).
+-export([init/1]).
+
+-spec start_link(ranch:ref(), module(), any(), module(), any())
+ -> {ok, pid()}.
+start_link(Ref, Transport, TransOpts, Protocol, ProtoOpts) ->
+ MaxConns = maps:get(max_connections, TransOpts, 1024),
+ ranch_server:set_new_listener_opts(Ref, MaxConns, TransOpts, ProtoOpts,
+ [Ref, Transport, TransOpts, Protocol, ProtoOpts]),
+ supervisor:start_link(?MODULE, {
+ Ref, Transport, Protocol
+ }).
+
+init({Ref, Transport, Protocol}) ->
+ ok = ranch_server:set_listener_sup(Ref, self()),
+ ChildSpecs = [
+ {ranch_conns_sup, {ranch_conns_sup, start_link,
+ [Ref, Transport, Protocol]},
+ permanent, infinity, supervisor, [ranch_conns_sup]},
+ {ranch_acceptors_sup, {ranch_acceptors_sup, start_link,
+ [Ref, Transport]},
+ permanent, infinity, supervisor, [ranch_acceptors_sup]}
+ ],
+ {ok, {{rest_for_one, 1, 5}, ChildSpecs}}.
diff --git a/server/_build/default/lib/ranch/src/ranch_protocol.erl b/server/_build/default/lib/ranch/src/ranch_protocol.erl
new file mode 100644
index 0000000..30a5b51
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_protocol.erl
@@ -0,0 +1,24 @@
+%% Copyright (c) 2012-2018, 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(ranch_protocol).
+
+%% Start a new connection process for the given socket.
+-callback start_link(
+ Ref::ranch:ref(),
+ Socket::any(),
+ Transport::module(),
+ ProtocolOptions::any())
+ -> {ok, ConnectionPid::pid()}
+ | {ok, SupPid::pid(), ConnectionPid::pid()}.
diff --git a/server/_build/default/lib/ranch/src/ranch_proxy_header.erl b/server/_build/default/lib/ranch/src/ranch_proxy_header.erl
new file mode 100644
index 0000000..081157f
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_proxy_header.erl
@@ -0,0 +1,880 @@
+%% Copyright (c) 2018, 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(ranch_proxy_header).
+
+-export([parse/1]).
+-export([header/1]).
+-export([header/2]).
+
+-type proxy_info() :: #{
+ %% Mandatory part.
+ version := 1 | 2,
+ command := local | proxy,
+ transport_family => undefined | ipv4 | ipv6 | unix,
+ transport_protocol => undefined | stream | dgram,
+ %% Addresses.
+ src_address => inet:ip_address() | binary(),
+ src_port => inet:port_number(),
+ dest_address => inet:ip_address() | binary(),
+ dest_port => inet:port_number(),
+ %% Extra TLV-encoded data.
+ alpn => binary(), %% US-ASCII.
+ authority => binary(), %% UTF-8.
+ ssl => #{
+ client := [ssl | cert_conn | cert_sess],
+ verified := boolean(),
+ version => binary(), %% US-ASCII.
+ cipher => binary(), %% US-ASCII.
+ sig_alg => binary(), %% US-ASCII.
+ key_alg => binary(), %% US-ASCII.
+ cn => binary() %% UTF-8.
+ },
+ netns => binary(), %% US-ASCII.
+ %% Unknown TLVs can't be parsed so the raw data is given.
+ raw_tlvs => [{0..255, binary()}]
+}.
+-export_type([proxy_info/0]).
+
+-type build_opts() :: #{
+ checksum => crc32c,
+ padding => pos_integer() %% >= 3
+}.
+
+%% Parsing.
+
+-spec parse(Data) -> {ok, proxy_info(), Data} | {error, atom()} when Data::binary().
+parse(<<"\r\n\r\n\0\r\nQUIT\n", Rest/bits>>) ->
+ parse_v2(Rest);
+parse(<<"PROXY ", Rest/bits>>) ->
+ parse_v1(Rest);
+parse(_) ->
+ {error, 'The PROXY protocol header signature was not recognized. (PP 2.1, PP 2.2)'}.
+
+-ifdef(TEST).
+parse_unrecognized_header_test() ->
+ {error, _} = parse(<<"GET / HTTP/1.1\r\n">>),
+ ok.
+-endif.
+
+%% Human-readable header format (Version 1).
+parse_v1(<<"TCP4 ", Rest/bits>>) ->
+ parse_v1(Rest, ipv4);
+parse_v1(<<"TCP6 ", Rest/bits>>) ->
+ parse_v1(Rest, ipv6);
+parse_v1(<<"UNKNOWN\r\n", Rest/bits>>) ->
+ {ok, #{
+ version => 1,
+ command => proxy,
+ transport_family => undefined,
+ transport_protocol => undefined
+ }, Rest};
+parse_v1(<<"UNKNOWN ", Rest0/bits>>) ->
+ case binary:split(Rest0, <<"\r\n">>) of
+ [_, Rest] ->
+ {ok, #{
+ version => 1,
+ command => proxy,
+ transport_family => undefined,
+ transport_protocol => undefined
+ }, Rest};
+ [_] ->
+ {error, 'Malformed or incomplete PROXY protocol header line. (PP 2.1)'}
+ end;
+parse_v1(_) ->
+ {error, 'The INET protocol and family string was not recognized. (PP 2.1)'}.
+
+parse_v1(Rest0, Family) ->
+ try
+ {ok, SrcAddr, Rest1} = parse_ip(Rest0, Family),
+ {ok, DestAddr, Rest2} = parse_ip(Rest1, Family),
+ {ok, SrcPort, Rest3} = parse_port(Rest2, $\s),
+ {ok, DestPort, Rest4} = parse_port(Rest3, $\r),
+ <<"\n", Rest/bits>> = Rest4,
+ {ok, #{
+ version => 1,
+ command => proxy,
+ transport_family => Family,
+ transport_protocol => stream,
+ src_address => SrcAddr,
+ src_port => SrcPort,
+ dest_address => DestAddr,
+ dest_port => DestPort
+ }, Rest}
+ catch
+ throw:parse_ipv4_error ->
+ {error, 'Failed to parse an IPv4 address in the PROXY protocol header line. (PP 2.1)'};
+ throw:parse_ipv6_error ->
+ {error, 'Failed to parse an IPv6 address in the PROXY protocol header line. (PP 2.1)'};
+ throw:parse_port_error ->
+ {error, 'Failed to parse a port number in the PROXY protocol header line. (PP 2.1)'};
+ _:_ ->
+ {error, 'Malformed or incomplete PROXY protocol header line. (PP 2.1)'}
+ end.
+
+parse_ip(<<Addr:7/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
+parse_ip(<<Addr:8/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
+parse_ip(<<Addr:9/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
+parse_ip(<<Addr:10/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
+parse_ip(<<Addr:11/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
+parse_ip(<<Addr:12/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
+parse_ip(<<Addr:13/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
+parse_ip(<<Addr:14/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
+parse_ip(<<Addr:15/binary, $\s, Rest/binary>>, ipv4) -> parse_ipv4(Addr, Rest);
+parse_ip(Data, ipv6) ->
+ [Addr, Rest] = binary:split(Data, <<$\s>>),
+ parse_ipv6(Addr, Rest).
+
+parse_ipv4(Addr0, Rest) ->
+ case inet:parse_ipv4strict_address(binary_to_list(Addr0)) of
+ {ok, Addr} -> {ok, Addr, Rest};
+ {error, einval} -> throw(parse_ipv4_error)
+ end.
+
+parse_ipv6(Addr0, Rest) ->
+ case inet:parse_ipv6strict_address(binary_to_list(Addr0)) of
+ {ok, Addr} -> {ok, Addr, Rest};
+ {error, einval} -> throw(parse_ipv6_error)
+ end.
+
+parse_port(<<Port:1/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest);
+parse_port(<<Port:2/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest);
+parse_port(<<Port:3/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest);
+parse_port(<<Port:4/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest);
+parse_port(<<Port:5/binary, C, Rest/bits>>, C) -> parse_port(Port, Rest);
+
+parse_port(Port0, Rest) ->
+ try binary_to_integer(Port0) of
+ Port when Port > 0, Port =< 65535 ->
+ {ok, Port, Rest};
+ _ ->
+ throw(parse_port_error)
+ catch _:_ ->
+ throw(parse_port_error)
+ end.
+
+-ifdef(TEST).
+parse_v1_test() ->
+ %% Examples taken from the PROXY protocol header specification.
+ {ok, #{
+ version := 1,
+ command := proxy,
+ transport_family := ipv4,
+ transport_protocol := stream,
+ src_address := {255, 255, 255, 255},
+ src_port := 65535,
+ dest_address := {255, 255, 255, 255},
+ dest_port := 65535
+ }, <<>>} = parse(<<"PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n">>),
+ {ok, #{
+ version := 1,
+ command := proxy,
+ transport_family := ipv6,
+ transport_protocol := stream,
+ src_address := {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535},
+ src_port := 65535,
+ dest_address := {65535, 65535, 65535, 65535, 65535, 65535, 65535, 65535},
+ dest_port := 65535
+ }, <<>>} = parse(<<"PROXY TCP6 "
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff "
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n">>),
+ {ok, #{
+ version := 1,
+ command := proxy,
+ transport_family := undefined,
+ transport_protocol := undefined
+ }, <<>>} = parse(<<"PROXY UNKNOWN\r\n">>),
+ {ok, #{
+ version := 1,
+ command := proxy,
+ transport_family := undefined,
+ transport_protocol := undefined
+ }, <<>>} = parse(<<"PROXY UNKNOWN "
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff "
+ "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff 65535 65535\r\n">>),
+ {ok, #{
+ version := 1,
+ command := proxy,
+ transport_family := ipv4,
+ transport_protocol := stream,
+ src_address := {192, 168, 0, 1},
+ src_port := 56324,
+ dest_address := {192, 168, 0, 11},
+ dest_port := 443
+ }, <<"GET / HTTP/1.1\r\nHost: 192.168.0.11\r\n\r\n">>} = parse(<<
+ "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n"
+ "GET / HTTP/1.1\r\n"
+ "Host: 192.168.0.11\r\n"
+ "\r\n">>),
+ %% Test cases taken from tomciopp/proxy_protocol.
+ {ok, #{
+ version := 1,
+ command := proxy,
+ transport_family := ipv4,
+ transport_protocol := stream,
+ src_address := {192, 168, 0, 1},
+ src_port := 56324,
+ dest_address := {192, 168, 0, 11},
+ dest_port := 443
+ }, <<"GET / HTTP/1.1\r">>} = parse(<<
+ "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\nGET / HTTP/1.1\r">>),
+ {error, _} = parse(<<"PROXY TCP4 192.1638.0.1 192.168.0.11 56324 443\r\nGET / HTTP/1.1\r">>),
+ {error, _} = parse(<<"PROXY TCP4 192.168.0.1 192.168.0.11 1111111 443\r\nGET / HTTP/1.1\r">>),
+ {ok, #{
+ version := 1,
+ command := proxy,
+ transport_family := ipv6,
+ transport_protocol := stream,
+ src_address := {8193, 3512, 0, 66, 0, 35374, 880, 29492},
+ src_port := 4124,
+ dest_address := {8193, 3512, 0, 66, 0, 35374, 880, 29493},
+ dest_port := 443
+ }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY TCP6 "
+ "2001:0db8:0000:0042:0000:8a2e:0370:7334 "
+ "2001:0db8:0000:0042:0000:8a2e:0370:7335 4124 443\r\nGET / HTTP/1.1\r">>),
+ {error, _} = parse(<<"PROXY TCP6 "
+ "2001:0db8:0000:0042:0000:8a2e:0370:7334 "
+ "2001:0db8:00;0:0042:0000:8a2e:0370:7335 4124 443\r\nGET / HTTP/1.1\r">>),
+ {error, _} = parse(<<"PROXY TCP6 "
+ "2001:0db8:0000:0042:0000:8a2e:0370:7334 "
+ "2001:0db8:0000:0042:0000:8a2e:0370:7335 4124 foo\r\nGET / HTTP/1.1\r">>),
+ {ok, #{
+ version := 1,
+ command := proxy,
+ transport_family := undefined,
+ transport_protocol := undefined
+ }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY UNKNOWN 4124 443\r\nGET / HTTP/1.1\r">>),
+ {ok, #{
+ version := 1,
+ command := proxy,
+ transport_family := undefined,
+ transport_protocol := undefined
+ }, <<"GET / HTTP/1.1\r">>} = parse(<<"PROXY UNKNOWN\r\nGET / HTTP/1.1\r">>),
+ ok.
+-endif.
+
+%% Binary header format (version 2).
+
+%% LOCAL.
+parse_v2(<<2:4, 0:4, _:8, Len:16, Rest0/bits>>) ->
+ case Rest0 of
+ <<_:Len/binary, Rest/bits>> ->
+ {ok, #{
+ version => 2,
+ command => local
+ }, Rest};
+ _ ->
+ {error, 'Missing data in the PROXY protocol binary header. (PP 2.2)'}
+ end;
+%% PROXY.
+parse_v2(<<2:4, 1:4, Family:4, Protocol:4, Len:16, Rest/bits>>)
+ when Family =< 3, Protocol =< 2 ->
+ case Rest of
+ <<Header:Len/binary, _/bits>> ->
+ parse_v2(Rest, Len, parse_family(Family), parse_protocol(Protocol),
+ <<Family:4, Protocol:4, Len:16, Header:Len/binary>>);
+ _ ->
+ {error, 'Missing data in the PROXY protocol binary header. (PP 2.2)'}
+ end;
+%% Errors.
+parse_v2(<<Version:4, _/bits>>) when Version =/= 2 ->
+ {error, 'Invalid version in the PROXY protocol binary header. (PP 2.2)'};
+parse_v2(<<_:4, Command:4, _/bits>>) when Command > 1 ->
+ {error, 'Invalid command in the PROXY protocol binary header. (PP 2.2)'};
+parse_v2(<<_:8, Family:4, _/bits>>) when Family > 3 ->
+ {error, 'Invalid address family in the PROXY protocol binary header. (PP 2.2)'};
+parse_v2(<<_:12, Protocol:4, _/bits>>) when Protocol > 2 ->
+ {error, 'Invalid transport protocol in the PROXY protocol binary header. (PP 2.2)'}.
+
+parse_family(0) -> undefined;
+parse_family(1) -> ipv4;
+parse_family(2) -> ipv6;
+parse_family(3) -> unix.
+
+parse_protocol(0) -> undefined;
+parse_protocol(1) -> stream;
+parse_protocol(2) -> dgram.
+
+parse_v2(Data, Len, Family, Protocol, _)
+ when Family =:= undefined; Protocol =:= undefined ->
+ <<_:Len/binary, Rest/bits>> = Data,
+ {ok, #{
+ version => 2,
+ command => proxy,
+ %% In case only one value was undefined, we set both explicitly.
+ %% It doesn't make sense to have only one known value.
+ transport_family => undefined,
+ transport_protocol => undefined
+ }, Rest};
+parse_v2(<<
+ S1, S2, S3, S4,
+ D1, D2, D3, D4,
+ SrcPort:16, DestPort:16, Rest/bits>>, Len, Family=ipv4, Protocol, Header)
+ when Len >= 12 ->
+ parse_tlv(Rest, Len - 12, #{
+ version => 2,
+ command => proxy,
+ transport_family => Family,
+ transport_protocol => Protocol,
+ src_address => {S1, S2, S3, S4},
+ src_port => SrcPort,
+ dest_address => {D1, D2, D3, D4},
+ dest_port => DestPort
+ }, Header);
+parse_v2(<<
+ S1:16, S2:16, S3:16, S4:16, S5:16, S6:16, S7:16, S8:16,
+ D1:16, D2:16, D3:16, D4:16, D5:16, D6:16, D7:16, D8:16,
+ SrcPort:16, DestPort:16, Rest/bits>>, Len, Family=ipv6, Protocol, Header)
+ when Len >= 36 ->
+ parse_tlv(Rest, Len - 36, #{
+ version => 2,
+ command => proxy,
+ transport_family => Family,
+ transport_protocol => Protocol,
+ src_address => {S1, S2, S3, S4, S5, S6, S7, S8},
+ src_port => SrcPort,
+ dest_address => {D1, D2, D3, D4, D5, D6, D7, D8},
+ dest_port => DestPort
+ }, Header);
+parse_v2(<<SrcAddr0:108/binary, DestAddr0:108/binary, Rest/bits>>,
+ Len, Family=unix, Protocol, Header)
+ when Len >= 216 ->
+ try
+ [SrcAddr, _] = binary:split(SrcAddr0, <<0>>),
+ true = byte_size(SrcAddr) > 0,
+ [DestAddr, _] = binary:split(DestAddr0, <<0>>),
+ true = byte_size(DestAddr) > 0,
+ parse_tlv(Rest, Len - 216, #{
+ version => 2,
+ command => proxy,
+ transport_family => Family,
+ transport_protocol => Protocol,
+ src_address => SrcAddr,
+ dest_address => DestAddr
+ }, Header)
+ catch _:_ ->
+ {error, 'Invalid UNIX address in PROXY protocol binary header. (PP 2.2)'}
+ end;
+parse_v2(_, _, _, _, _) ->
+ {error, 'Invalid length in the PROXY protocol binary header. (PP 2.2)'}.
+
+-ifdef(TEST).
+parse_v2_test() ->
+ %% Test cases taken from tomciopp/proxy_protocol.
+ {ok, #{
+ version := 2,
+ command := proxy,
+ transport_family := ipv4,
+ transport_protocol := stream,
+ src_address := {127, 0, 0, 1},
+ src_port := 444,
+ dest_address := {192, 168, 0, 1},
+ dest_port := 443
+ }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
+ 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature.
+ 33, %% Version and command.
+ 17, %% Family and protocol.
+ 0, 12, %% Length.
+ 127, 0, 0, 1, %% Source address.
+ 192, 168, 0, 1, %% Destination address.
+ 1, 188, %% Source port.
+ 1, 187, %% Destination port.
+ "GET / HTTP/1.1\r\n">>),
+ {ok, #{
+ version := 2,
+ command := proxy,
+ transport_family := ipv4,
+ transport_protocol := dgram,
+ src_address := {127, 0, 0, 1},
+ src_port := 444,
+ dest_address := {192, 168, 0, 1},
+ dest_port := 443
+ }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
+ 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature.
+ 33, %% Version and command.
+ 18, %% Family and protocol.
+ 0, 12, %% Length.
+ 127, 0, 0, 1, %% Source address.
+ 192, 168, 0, 1, %% Destination address.
+ 1, 188, %% Source port.
+ 1, 187, %% Destination port.
+ "GET / HTTP/1.1\r\n">>),
+ {ok, #{
+ version := 2,
+ command := proxy,
+ transport_family := ipv6,
+ transport_protocol := stream,
+ src_address := {5532, 4240, 1, 0, 0, 0, 0, 0},
+ src_port := 444,
+ dest_address := {8193, 3512, 1, 0, 0, 0, 0, 0},
+ dest_port := 443
+ }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
+ 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature.
+ 33, %% Version and command.
+ 33, %% Family and protocol.
+ 0, 36, %% Length.
+ 21, 156, 16, 144, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Source address.
+ 32, 1, 13, 184, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Destination address.
+ 1, 188, %% Source port.
+ 1, 187, %% Destination port.
+ "GET / HTTP/1.1\r\n">>),
+ {ok, #{
+ version := 2,
+ command := proxy,
+ transport_family := ipv6,
+ transport_protocol := dgram,
+ src_address := {5532, 4240, 1, 0, 0, 0, 0, 0},
+ src_port := 444,
+ dest_address := {8193, 3512, 1, 0, 0, 0, 0, 0},
+ dest_port := 443
+ }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
+ 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, %% Signature.
+ 33, %% Version and command.
+ 34, %% Family and protocol.
+ 0, 36, %% Length.
+ 21, 156, 16, 144, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Source address.
+ 32, 1, 13, 184, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, %% Destination address.
+ 1, 188, %% Source port.
+ 1, 187, %% Destination port.
+ "GET / HTTP/1.1\r\n">>),
+ Path = <<"/var/pgsql_sock">>,
+ Len = byte_size(Path),
+ Padding = 8 * (108 - Len),
+ {ok, #{
+ version := 2,
+ command := proxy,
+ transport_family := unix,
+ transport_protocol := stream,
+ src_address := Path,
+ dest_address := Path
+ }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
+ 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10,
+ 33,
+ 49,
+ 0, 216,
+ Path/binary, 0:Padding,
+ Path/binary, 0:Padding,
+ "GET / HTTP/1.1\r\n">>),
+ {ok, #{
+ version := 2,
+ command := proxy,
+ transport_family := unix,
+ transport_protocol := dgram,
+ src_address := Path,
+ dest_address := Path
+ }, <<"GET / HTTP/1.1\r\n">>} = parse(<<
+ 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10,
+ 33,
+ 50,
+ 0, 216,
+ Path/binary, 0:Padding,
+ Path/binary, 0:Padding,
+ "GET / HTTP/1.1\r\n">>),
+ ok.
+
+parse_v2_regression_test() ->
+ %% Real packet received from AWS. We confirm that the CRC32C
+ %% check succeeds only (in other words that ok is returned).
+ {ok, _, <<>>} = parse(<<
+ 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, 33, 17, 0, 84,
+ 172, 31, 7, 113, 172, 31, 10, 31, 200, 242, 0, 80, 3, 0, 4,
+ 232, 214, 137, 45, 234, 0, 23, 1, 118, 112, 99, 101, 45, 48,
+ 56, 100, 50, 98, 102, 49, 53, 102, 97, 99, 53, 48, 48, 49, 99,
+ 57, 4, 0, 36, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0>>),
+ ok.
+-endif.
+
+parse_tlv(Rest, 0, Info, _) ->
+ {ok, Info, Rest};
+%% PP2_TYPE_ALPN.
+parse_tlv(<<16#1, TLVLen:16, ALPN:TLVLen/binary, Rest/bits>>, Len, Info, Header) ->
+ parse_tlv(Rest, Len - TLVLen - 3, Info#{alpn => ALPN}, Header);
+%% PP2_TYPE_AUTHORITY.
+parse_tlv(<<16#2, TLVLen:16, Authority:TLVLen/binary, Rest/bits>>, Len, Info, Header) ->
+ parse_tlv(Rest, Len - TLVLen - 3, Info#{authority => Authority}, Header);
+%% PP2_TYPE_CRC32C.
+parse_tlv(<<16#3, TLVLen:16, CRC32C:32, Rest/bits>>, Len0, Info, Header) when TLVLen =:= 4 ->
+ Len = Len0 - TLVLen - 3,
+ BeforeLen = byte_size(Header) - Len - TLVLen,
+ <<Before:BeforeLen/binary, _:32, After:Len/binary>> = Header,
+ %% The initial CRC is ranch_crc32c:crc32c(<<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4>>).
+ case ranch_crc32c:crc32c(2900412422, [Before, <<0:32>>, After]) of
+ CRC32C ->
+ parse_tlv(Rest, Len, Info, Header);
+ _ ->
+ {error, 'Failed CRC32C verification in PROXY protocol binary header. (PP 2.2)'}
+ end;
+%% PP2_TYPE_NOOP.
+parse_tlv(<<16#4, TLVLen:16, _:TLVLen/binary, Rest/bits>>, Len, Info, Header) ->
+ parse_tlv(Rest, Len - TLVLen - 3, Info, Header);
+%% PP2_TYPE_SSL.
+parse_tlv(<<16#20, TLVLen:16, Client, Verify:32, Rest0/bits>>, Len, Info, Header) ->
+ SubsLen = TLVLen - 5,
+ case Rest0 of
+ <<Subs:SubsLen/binary, Rest/bits>> ->
+ SSL0 = #{
+ client => parse_client(<<Client>>),
+ verified => Verify =:= 0
+ },
+ case parse_ssl_tlv(Subs, SubsLen, SSL0) of
+ {ok, SSL, <<>>} ->
+ parse_tlv(Rest, Len - TLVLen - 3, Info#{ssl => SSL}, Header);
+ Error={error, _} ->
+ Error
+ end;
+ _ ->
+ {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'}
+ end;
+%% PP2_TYPE_NETNS.
+parse_tlv(<<16#30, TLVLen:16, NetNS:TLVLen/binary, Rest/bits>>, Len, Info, Header) ->
+ parse_tlv(Rest, Len - TLVLen - 3, Info#{netns => NetNS}, Header);
+%% Unknown TLV.
+parse_tlv(<<TLVType, TLVLen:16, TLVValue:TLVLen/binary, Rest/bits>>, Len, Info, Header) ->
+ RawTLVs = maps:get(raw_tlvs, Info, []),
+ parse_tlv(Rest, Len - TLVLen - 3, Info#{raw_tlvs => [{TLVType, TLVValue}|RawTLVs]}, Header);
+%% Invalid TLV length.
+parse_tlv(_, _, _, _) ->
+ {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'}.
+
+parse_client(<<_:5, ClientCertSess:1, ClientCertConn:1, ClientSSL:1>>) ->
+ Client0 = case ClientCertSess of
+ 0 -> [];
+ 1 -> [cert_sess]
+ end,
+ Client1 = case ClientCertConn of
+ 0 -> Client0;
+ 1 -> [cert_conn|Client0]
+ end,
+ case ClientSSL of
+ 0 -> Client1;
+ 1 -> [ssl|Client1]
+ end.
+
+parse_ssl_tlv(Rest, 0, Info) ->
+ {ok, Info, Rest};
+%% Valid TLVs.
+parse_ssl_tlv(<<TLVType, TLVLen:16, TLVValue:TLVLen/binary, Rest/bits>>, Len, Info) ->
+ case ssl_subtype(TLVType) of
+ undefined ->
+ {error, 'Invalid TLV subtype for PP2_TYPE_SSL in PROXY protocol binary header. (PP 2.2)'};
+ Type ->
+ parse_ssl_tlv(Rest, Len - TLVLen - 3, Info#{Type => TLVValue})
+ end;
+%% Invalid TLV length.
+parse_ssl_tlv(_, _, _) ->
+ {error, 'Invalid TLV length in the PROXY protocol binary header. (PP 2.2)'}.
+
+ssl_subtype(16#21) -> version;
+ssl_subtype(16#22) -> cn;
+ssl_subtype(16#23) -> cipher;
+ssl_subtype(16#24) -> sig_alg;
+ssl_subtype(16#25) -> key_alg;
+ssl_subtype(_) -> undefined.
+
+%% Building.
+
+-spec header(proxy_info()) -> iodata().
+header(ProxyInfo) ->
+ header(ProxyInfo, #{}).
+
+-spec header(proxy_info(), build_opts()) -> iodata().
+header(#{version := 2, command := local}, _) ->
+ <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 0:28>>;
+header(#{version := 2, command := proxy,
+ transport_family := Family,
+ transport_protocol := Protocol}, _)
+ when Family =:= undefined; Protocol =:= undefined ->
+ <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4, 0:24>>;
+header(ProxyInfo=#{version := 2, command := proxy,
+ transport_family := Family,
+ transport_protocol := Protocol}, Opts) ->
+ Addresses = addresses(ProxyInfo),
+ TLVs = tlvs(ProxyInfo, Opts),
+ ExtraLen = case Opts of
+ #{checksum := crc32c} -> 7;
+ _ -> 0
+ end,
+ Len = iolist_size(Addresses) + iolist_size(TLVs) + ExtraLen,
+ Header = [
+ <<"\r\n\r\n\0\r\nQUIT\n", 2:4, 1:4>>,
+ <<(family(Family)):4, (protocol(Protocol)):4>>,
+ <<Len:16>>,
+ Addresses,
+ TLVs
+ ],
+ case Opts of
+ #{checksum := crc32c} ->
+ CRC32C = ranch_crc32c:crc32c([Header, <<16#3, 4:16, 0:32>>]),
+ [Header, <<16#3, 4:16, CRC32C:32>>];
+ _ ->
+ Header
+ end;
+header(#{version := 1, command := proxy,
+ transport_family := undefined,
+ transport_protocol := undefined}, _) ->
+ <<"PROXY UNKNOWN\r\n">>;
+header(#{version := 1, command := proxy,
+ transport_family := Family0,
+ transport_protocol := stream,
+ src_address := SrcAddress, src_port := SrcPort,
+ dest_address := DestAddress, dest_port := DestPort}, _)
+ when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 ->
+ [
+ <<"PROXY ">>,
+ case Family0 of
+ ipv4 when tuple_size(SrcAddress) =:= 4, tuple_size(DestAddress) =:= 4 ->
+ [<<"TCP4 ">>, inet:ntoa(SrcAddress), $\s, inet:ntoa(DestAddress)];
+ ipv6 when tuple_size(SrcAddress) =:= 8, tuple_size(DestAddress) =:= 8 ->
+ [<<"TCP6 ">>, inet:ntoa(SrcAddress), $\s, inet:ntoa(DestAddress)]
+ end,
+ $\s,
+ integer_to_binary(SrcPort),
+ $\s,
+ integer_to_binary(DestPort),
+ $\r, $\n
+ ].
+
+family(ipv4) -> 1;
+family(ipv6) -> 2;
+family(unix) -> 3.
+
+protocol(stream) -> 1;
+protocol(dgram) -> 2.
+
+addresses(#{transport_family := ipv4,
+ src_address := {S1, S2, S3, S4}, src_port := SrcPort,
+ dest_address := {D1, D2, D3, D4}, dest_port := DestPort})
+ when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 ->
+ <<S1, S2, S3, S4, D1, D2, D3, D4, SrcPort:16, DestPort:16>>;
+addresses(#{transport_family := ipv6,
+ src_address := {S1, S2, S3, S4, S5, S6, S7, S8}, src_port := SrcPort,
+ dest_address := {D1, D2, D3, D4, D5, D6, D7, D8}, dest_port := DestPort})
+ when SrcPort > 0, SrcPort =< 65535, DestPort > 0, DestPort =< 65535 ->
+ <<
+ S1:16, S2:16, S3:16, S4:16, S5:16, S6:16, S7:16, S8:16,
+ D1:16, D2:16, D3:16, D4:16, D5:16, D6:16, D7:16, D8:16,
+ SrcPort:16, DestPort:16
+ >>;
+addresses(#{transport_family := unix,
+ src_address := SrcAddress, dest_address := DestAddress})
+ when byte_size(SrcAddress) =< 108, byte_size(DestAddress) =< 108 ->
+ SrcPadding = 8 * (108 - byte_size(SrcAddress)),
+ DestPadding = 8 * (108 - byte_size(DestAddress)),
+ <<
+ SrcAddress/binary, 0:SrcPadding,
+ DestAddress/binary, 0:DestPadding
+ >>.
+
+tlvs(ProxyInfo, Opts) ->
+ [
+ binary_tlv(ProxyInfo, alpn, 16#1),
+ binary_tlv(ProxyInfo, authority, 16#2),
+ ssl_tlv(ProxyInfo),
+ binary_tlv(ProxyInfo, netns, 16#30),
+ raw_tlvs(ProxyInfo),
+ noop_tlv(Opts)
+ ].
+
+binary_tlv(Info, Key, Type) ->
+ case Info of
+ #{Key := Bin} ->
+ Len = byte_size(Bin),
+ <<Type, Len:16, Bin/binary>>;
+ _ ->
+ <<>>
+ end.
+
+noop_tlv(#{padding := Len0}) when Len0 >= 3 ->
+ Len = Len0 - 3,
+ <<16#4, Len:16, 0:Len/unit:8>>;
+noop_tlv(_) ->
+ <<>>.
+
+ssl_tlv(#{ssl := Info=#{client := Client0, verified := Verify0}}) ->
+ Client = client(Client0, 0),
+ Verify = if
+ Verify0 -> 0;
+ not Verify0 -> 1
+ end,
+ TLVs = [
+ binary_tlv(Info, version, 16#21),
+ binary_tlv(Info, cn, 16#22),
+ binary_tlv(Info, cipher, 16#23),
+ binary_tlv(Info, sig_alg, 16#24),
+ binary_tlv(Info, key_alg, 16#25)
+ ],
+ Len = iolist_size(TLVs) + 5,
+ [<<16#20, Len:16, Client, Verify:32>>, TLVs];
+ssl_tlv(_) ->
+ <<>>.
+
+client([], Client) -> Client;
+client([ssl|Tail], Client) -> client(Tail, Client bor 16#1);
+client([cert_conn|Tail], Client) -> client(Tail, Client bor 16#2);
+client([cert_sess|Tail], Client) -> client(Tail, Client bor 16#4).
+
+raw_tlvs(Info) ->
+ [begin
+ Len = byte_size(Bin),
+ <<Type, Len:16, Bin/binary>>
+ end || {Type, Bin} <- maps:get(raw_tlvs, Info, [])].
+
+-ifdef(TEST).
+v1_test() ->
+ Test1 = #{
+ version => 1,
+ command => proxy,
+ transport_family => undefined,
+ transport_protocol => undefined
+ },
+ {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))),
+ Test2 = #{
+ version => 1,
+ command => proxy,
+ transport_family => ipv4,
+ transport_protocol => stream,
+ src_address => {127, 0, 0, 1},
+ src_port => 1234,
+ dest_address => {10, 11, 12, 13},
+ dest_port => 23456
+ },
+ {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))),
+ Test3 = #{
+ version => 1,
+ command => proxy,
+ transport_family => ipv6,
+ transport_protocol => stream,
+ src_address => {1, 2, 3, 4, 5, 6, 7, 8},
+ src_port => 1234,
+ dest_address => {65535, 55555, 2222, 333, 1, 9999, 777, 8},
+ dest_port => 23456
+ },
+ {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))),
+ ok.
+
+v2_test() ->
+ Test0 = #{
+ version => 2,
+ command => local
+ },
+ {ok, Test0, <<>>} = parse(iolist_to_binary(header(Test0))),
+ Test1 = #{
+ version => 2,
+ command => proxy,
+ transport_family => undefined,
+ transport_protocol => undefined
+ },
+ {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))),
+ Test2 = #{
+ version => 2,
+ command => proxy,
+ transport_family => ipv4,
+ transport_protocol => stream,
+ src_address => {127, 0, 0, 1},
+ src_port => 1234,
+ dest_address => {10, 11, 12, 13},
+ dest_port => 23456
+ },
+ {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))),
+ Test3 = #{
+ version => 2,
+ command => proxy,
+ transport_family => ipv6,
+ transport_protocol => stream,
+ src_address => {1, 2, 3, 4, 5, 6, 7, 8},
+ src_port => 1234,
+ dest_address => {65535, 55555, 2222, 333, 1, 9999, 777, 8},
+ dest_port => 23456
+ },
+ {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))),
+ Test4 = #{
+ version => 2,
+ command => proxy,
+ transport_family => unix,
+ transport_protocol => dgram,
+ src_address => <<"/run/source.sock">>,
+ dest_address => <<"/run/destination.sock">>
+ },
+ {ok, Test4, <<>>} = parse(iolist_to_binary(header(Test4))),
+ ok.
+
+v2_tlvs_test() ->
+ Common = #{
+ version => 2,
+ command => proxy,
+ transport_family => ipv4,
+ transport_protocol => stream,
+ src_address => {127, 0, 0, 1},
+ src_port => 1234,
+ dest_address => {10, 11, 12, 13},
+ dest_port => 23456
+ },
+ Test1 = Common#{alpn => <<"h2">>},
+ {ok, Test1, <<>>} = parse(iolist_to_binary(header(Test1))),
+ Test2 = Common#{authority => <<"internal.example.org">>},
+ {ok, Test2, <<>>} = parse(iolist_to_binary(header(Test2))),
+ Test3 = Common#{netns => <<"/var/run/netns/example">>},
+ {ok, Test3, <<>>} = parse(iolist_to_binary(header(Test3))),
+ Test4 = Common#{ssl => #{
+ client => [ssl, cert_conn, cert_sess],
+ verified => true,
+ version => <<"TLSv1.3">>, %% Note that I'm not sure this example value is correct.
+ cipher => <<"ECDHE-RSA-AES128-GCM-SHA256">>,
+ sig_alg => <<"SHA256">>,
+ key_alg => <<"RSA2048">>,
+ cn => <<"example.com">>
+ }},
+ {ok, Test4, <<>>} = parse(iolist_to_binary(header(Test4))),
+ %% Note that the raw_tlvs order is not relevant and therefore
+ %% the parser does not reverse the list it builds.
+ Test5In = Common#{raw_tlvs => RawTLVs=[
+ %% The only custom TLV I am aware of is defined at:
+ %% https://docs.aws.amazon.com/elasticloadbalancing/latest/network/load-balancer-target-groups.html#proxy-protocol
+ {16#ea, <<16#1, "instance-id">>},
+ %% This TLV is entirely fictional.
+ {16#ff, <<1, 2, 3, 4, 5, 6, 7, 8, 9, 0>>}
+ ]},
+ Test5Out = Test5In#{raw_tlvs => lists:reverse(RawTLVs)},
+ {ok, Test5Out, <<>>} = parse(iolist_to_binary(header(Test5In))),
+ ok.
+
+v2_checksum_test() ->
+ Test = #{
+ version => 2,
+ command => proxy,
+ transport_family => ipv4,
+ transport_protocol => stream,
+ src_address => {127, 0, 0, 1},
+ src_port => 1234,
+ dest_address => {10, 11, 12, 13},
+ dest_port => 23456
+ },
+ {ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{checksum => crc32c}))),
+ ok.
+
+v2_padding_test() ->
+ Test = #{
+ version => 2,
+ command => proxy,
+ transport_family => ipv4,
+ transport_protocol => stream,
+ src_address => {127, 0, 0, 1},
+ src_port => 1234,
+ dest_address => {10, 11, 12, 13},
+ dest_port => 23456
+ },
+ {ok, Test, <<>>} = parse(iolist_to_binary(header(Test, #{padding => 123}))),
+ ok.
+-endif.
diff --git a/server/_build/default/lib/ranch/src/ranch_server.erl b/server/_build/default/lib/ranch/src/ranch_server.erl
new file mode 100644
index 0000000..a767cd8
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_server.erl
@@ -0,0 +1,233 @@
+%% Copyright (c) 2012-2018, 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(ranch_server).
+-behaviour(gen_server).
+
+%% API.
+-export([start_link/0]).
+-export([set_new_listener_opts/5]).
+-export([cleanup_listener_opts/1]).
+-export([set_connections_sup/2]).
+-export([get_connections_sup/1]).
+-export([get_connections_sups/0]).
+-export([set_listener_sup/2]).
+-export([get_listener_sup/1]).
+-export([get_listener_sups/0]).
+-export([set_addr/2]).
+-export([get_addr/1]).
+-export([set_max_connections/2]).
+-export([get_max_connections/1]).
+-export([set_transport_options/2]).
+-export([get_transport_options/1]).
+-export([set_protocol_options/2]).
+-export([get_protocol_options/1]).
+-export([get_listener_start_args/1]).
+-export([count_connections/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]).
+
+-define(TAB, ?MODULE).
+
+-type monitors() :: [{{reference(), pid()}, any()}].
+-record(state, {
+ monitors = [] :: monitors()
+}).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+-spec set_new_listener_opts(ranch:ref(), ranch:max_conns(), any(), any(), [any()]) -> ok.
+set_new_listener_opts(Ref, MaxConns, TransOpts, ProtoOpts, StartArgs) ->
+ gen_server:call(?MODULE, {set_new_listener_opts, Ref, MaxConns, TransOpts, ProtoOpts, StartArgs}).
+
+-spec cleanup_listener_opts(ranch:ref()) -> ok.
+cleanup_listener_opts(Ref) ->
+ _ = ets:delete(?TAB, {addr, Ref}),
+ _ = ets:delete(?TAB, {max_conns, Ref}),
+ _ = ets:delete(?TAB, {trans_opts, Ref}),
+ _ = ets:delete(?TAB, {proto_opts, Ref}),
+ _ = ets:delete(?TAB, {listener_start_args, Ref}),
+ %% We also remove the pid of the connections supervisor.
+ %% Depending on the timing, it might already have been deleted
+ %% when we handled the monitor DOWN message. However, in some
+ %% cases when calling stop_listener followed by get_connections_sup,
+ %% we could end up with the pid still being returned, when we
+ %% expected a crash (because the listener was stopped).
+ %% Deleting it explictly here removes any possible confusion.
+ _ = ets:delete(?TAB, {conns_sup, Ref}),
+ %% Ditto for the listener supervisor.
+ _ = ets:delete(?TAB, {listener_sup, Ref}),
+ ok.
+
+-spec set_connections_sup(ranch:ref(), pid()) -> ok.
+set_connections_sup(Ref, Pid) ->
+ gen_server:call(?MODULE, {set_connections_sup, Ref, Pid}).
+
+-spec get_connections_sup(ranch:ref()) -> pid().
+get_connections_sup(Ref) ->
+ ets:lookup_element(?TAB, {conns_sup, Ref}, 2).
+
+-spec get_connections_sups() -> [{ranch:ref(), pid()}].
+get_connections_sups() ->
+ [{Ref, Pid} || [Ref, Pid] <- ets:match(?TAB, {{conns_sup, '$1'}, '$2'})].
+
+-spec set_listener_sup(ranch:ref(), pid()) -> ok.
+set_listener_sup(Ref, Pid) ->
+ gen_server:call(?MODULE, {set_listener_sup, Ref, Pid}).
+
+-spec get_listener_sup(ranch:ref()) -> pid().
+get_listener_sup(Ref) ->
+ ets:lookup_element(?TAB, {listener_sup, Ref}, 2).
+
+-spec get_listener_sups() -> [{ranch:ref(), pid()}].
+get_listener_sups() ->
+ [{Ref, Pid} || [Ref, Pid] <- ets:match(?TAB, {{listener_sup, '$1'}, '$2'})].
+
+-spec set_addr(ranch:ref(), {inet:ip_address(), inet:port_number()} | {undefined, undefined}) -> ok.
+set_addr(Ref, Addr) ->
+ gen_server:call(?MODULE, {set_addr, Ref, Addr}).
+
+-spec get_addr(ranch:ref()) -> {inet:ip_address(), inet:port_number()} | {undefined, undefined}.
+get_addr(Ref) ->
+ ets:lookup_element(?TAB, {addr, Ref}, 2).
+
+-spec set_max_connections(ranch:ref(), ranch:max_conns()) -> ok.
+set_max_connections(Ref, MaxConnections) ->
+ gen_server:call(?MODULE, {set_max_conns, Ref, MaxConnections}).
+
+-spec get_max_connections(ranch:ref()) -> ranch:max_conns().
+get_max_connections(Ref) ->
+ ets:lookup_element(?TAB, {max_conns, Ref}, 2).
+
+-spec set_transport_options(ranch:ref(), any()) -> ok.
+set_transport_options(Ref, TransOpts) ->
+ gen_server:call(?MODULE, {set_trans_opts, Ref, TransOpts}).
+
+-spec get_transport_options(ranch:ref()) -> any().
+get_transport_options(Ref) ->
+ ets:lookup_element(?TAB, {trans_opts, Ref}, 2).
+
+-spec set_protocol_options(ranch:ref(), any()) -> ok.
+set_protocol_options(Ref, ProtoOpts) ->
+ gen_server:call(?MODULE, {set_proto_opts, Ref, ProtoOpts}).
+
+-spec get_protocol_options(ranch:ref()) -> any().
+get_protocol_options(Ref) ->
+ ets:lookup_element(?TAB, {proto_opts, Ref}, 2).
+
+-spec get_listener_start_args(ranch:ref()) -> [any()].
+get_listener_start_args(Ref) ->
+ ets:lookup_element(?TAB, {listener_start_args, Ref}, 2).
+
+-spec count_connections(ranch:ref()) -> non_neg_integer().
+count_connections(Ref) ->
+ ranch_conns_sup:active_connections(get_connections_sup(Ref)).
+
+%% gen_server.
+
+init([]) ->
+ ConnMonitors = [{{erlang:monitor(process, Pid), Pid}, {conns_sup, Ref}} ||
+ [Ref, Pid] <- ets:match(?TAB, {{conns_sup, '$1'}, '$2'})],
+ ListenerMonitors = [{{erlang:monitor(process, Pid), Pid}, {listener_sup, Ref}} ||
+ [Ref, Pid] <- ets:match(?TAB, {{listener_sup, '$1'}, '$2'})],
+ {ok, #state{monitors=ConnMonitors++ListenerMonitors}}.
+
+handle_call({set_new_listener_opts, Ref, MaxConns, TransOpts, ProtoOpts, StartArgs}, _, State) ->
+ ets:insert_new(?TAB, {{max_conns, Ref}, MaxConns}),
+ ets:insert_new(?TAB, {{trans_opts, Ref}, TransOpts}),
+ ets:insert_new(?TAB, {{proto_opts, Ref}, ProtoOpts}),
+ ets:insert_new(?TAB, {{listener_start_args, Ref}, StartArgs}),
+ {reply, ok, State};
+handle_call({set_connections_sup, Ref, Pid}, _, State0) ->
+ State = set_monitored_process({conns_sup, Ref}, Pid, State0),
+ {reply, ok, State};
+handle_call({set_listener_sup, Ref, Pid}, _, State0) ->
+ State = set_monitored_process({listener_sup, Ref}, Pid, State0),
+ {reply, ok, State};
+handle_call({set_addr, Ref, Addr}, _, State) ->
+ true = ets:insert(?TAB, {{addr, Ref}, Addr}),
+ {reply, ok, State};
+handle_call({set_max_conns, Ref, MaxConns}, _, State) ->
+ ets:insert(?TAB, {{max_conns, Ref}, MaxConns}),
+ ConnsSup = get_connections_sup(Ref),
+ ConnsSup ! {set_max_conns, MaxConns},
+ {reply, ok, State};
+handle_call({set_trans_opts, Ref, Opts}, _, State) ->
+ ets:insert(?TAB, {{trans_opts, Ref}, Opts}),
+ {reply, ok, State};
+handle_call({set_proto_opts, Ref, Opts}, _, State) ->
+ ets:insert(?TAB, {{proto_opts, Ref}, Opts}),
+ ConnsSup = get_connections_sup(Ref),
+ ConnsSup ! {set_opts, Opts},
+ {reply, ok, State};
+handle_call(_Request, _From, State) ->
+ {reply, ignore, State}.
+
+handle_cast(_Request, State) ->
+ {noreply, State}.
+
+handle_info({'DOWN', MonitorRef, process, Pid, Reason},
+ State=#state{monitors=Monitors}) ->
+ {_, TypeRef} = lists:keyfind({MonitorRef, Pid}, 1, Monitors),
+ ok = case {TypeRef, Reason} of
+ {{listener_sup, Ref}, normal} ->
+ cleanup_listener_opts(Ref);
+ {{listener_sup, Ref}, shutdown} ->
+ cleanup_listener_opts(Ref);
+ {{listener_sup, Ref}, {shutdown, _}} ->
+ cleanup_listener_opts(Ref);
+ _ ->
+ _ = ets:delete(?TAB, TypeRef),
+ ok
+ end,
+ Monitors2 = lists:keydelete({MonitorRef, Pid}, 1, Monitors),
+ {noreply, State#state{monitors=Monitors2}};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+terminate(_Reason, _State) ->
+ ok.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+%% Internal.
+
+set_monitored_process(Key, Pid, State=#state{monitors=Monitors0}) ->
+ %% First we cleanup the monitor if a residual one exists.
+ %% This can happen during crashes when the restart is faster
+ %% than the cleanup.
+ Monitors = case lists:keytake(Key, 2, Monitors0) of
+ false ->
+ Monitors0;
+ {value, {{OldMonitorRef, _}, _}, Monitors1} ->
+ true = erlang:demonitor(OldMonitorRef, [flush]),
+ Monitors1
+ end,
+ %% Then we unconditionally insert in the ets table.
+ %% If residual data is there, it will be overwritten.
+ true = ets:insert(?TAB, {Key, Pid}),
+ %% Finally we start monitoring this new process.
+ MonitorRef = erlang:monitor(process, Pid),
+ State#state{monitors=[{{MonitorRef, Pid}, Key}|Monitors]}.
diff --git a/server/_build/default/lib/ranch/src/ranch_ssl.erl b/server/_build/default/lib/ranch/src/ranch_ssl.erl
new file mode 100644
index 0000000..f769868
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_ssl.erl
@@ -0,0 +1,243 @@
+%% Copyright (c) 2011-2018, 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(ranch_ssl).
+-behaviour(ranch_transport).
+
+-export([name/0]).
+-export([secure/0]).
+-export([messages/0]).
+-export([listen/1]).
+-export([disallowed_listen_options/0]).
+-export([accept/2]).
+-export([accept_ack/2]).
+-export([handshake/3]).
+-export([connect/3]).
+-export([connect/4]).
+-export([recv/3]).
+-export([recv_proxy_header/2]).
+-export([send/2]).
+-export([sendfile/2]).
+-export([sendfile/4]).
+-export([sendfile/5]).
+-export([setopts/2]).
+-export([getopts/2]).
+-export([getstat/1]).
+-export([getstat/2]).
+-export([controlling_process/2]).
+-export([peername/1]).
+-export([sockname/1]).
+-export([shutdown/2]).
+-export([close/1]).
+
+-type ssl_opt() :: {alpn_preferred_protocols, [binary()]}
+ | {beast_mitigation, one_n_minus_one | zero_n | disabled}
+ | {cacertfile, string()}
+ | {cacerts, [public_key:der_encoded()]}
+ | {cert, public_key:der_encoded()}
+ | {certfile, string()}
+ | {ciphers, [ssl_cipher:erl_cipher_suite()]}
+ | {client_renegotiation, boolean()}
+ | {crl_cache, {module(), {internal | any(), list()}}}
+ | {crl_check, boolean() | peer | best_effort}
+ | {depth, 0..255}
+ | {dh, public_key:der_encoded()}
+ | {dhfile, string()}
+ | {fail_if_no_peer_cert, boolean()}
+ | {hibernate_after, integer() | undefined}
+ | {honor_cipher_order, boolean()}
+ | {key, {'RSAPrivateKey' | 'DSAPrivateKey' | 'PrivateKeyInfo', public_key:der_encoded()}}
+ | {keyfile, string()}
+ | {log_alert, boolean()}
+ | {next_protocols_advertised, [binary()]}
+ | {padding_check, boolean()}
+ | {partial_chain, fun(([public_key:der_encoded()]) -> {trusted_ca, public_key:der_encoded()} | unknown_ca)}
+ | {password, string()}
+ | {psk_identity, string()}
+ | {reuse_session, fun()}
+ | {reuse_sessions, boolean()}
+ | {secure_renegotiate, boolean()}
+ | {signature_algs, [{atom(), atom()}]}
+ | {sni_fun, fun()}
+ | {sni_hosts, [{string(), ssl_opt()}]}
+ | {user_lookup_fun, {fun(), any()}}
+ | {v2_hello_compatible, boolean()}
+ | {verify, verify_none | verify_peer}
+ | {verify_fun, {fun(), any()}}
+ | {versions, [atom()]}.
+-export_type([ssl_opt/0]).
+
+-type opt() :: ranch_tcp:opt() | ssl_opt().
+-export_type([opt/0]).
+
+-type opts() :: [opt()].
+-export_type([opts/0]).
+
+name() -> ssl.
+
+-spec secure() -> boolean().
+secure() ->
+ true.
+
+messages() -> {ssl, ssl_closed, ssl_error}.
+
+-spec listen(opts()) -> {ok, ssl:sslsocket()} | {error, atom()}.
+listen(Opts) ->
+ case lists:keymember(cert, 1, Opts)
+ orelse lists:keymember(certfile, 1, Opts)
+ orelse lists:keymember(sni_fun, 1, Opts)
+ orelse lists:keymember(sni_hosts, 1, Opts) of
+ true ->
+ do_listen(Opts);
+ false ->
+ {error, no_cert}
+ end.
+
+do_listen(Opts0) ->
+ Opts1 = ranch:set_option_default(Opts0, backlog, 1024),
+ Opts2 = ranch:set_option_default(Opts1, nodelay, true),
+ Opts3 = ranch:set_option_default(Opts2, send_timeout, 30000),
+ Opts = ranch:set_option_default(Opts3, send_timeout_close, true),
+ %% We set the port to 0 because it is given in the Opts directly.
+ %% The port in the options takes precedence over the one in the
+ %% first argument.
+ ssl:listen(0, ranch:filter_options(Opts, disallowed_listen_options(),
+ [binary, {active, false}, {packet, raw}, {reuseaddr, true}])).
+
+%% 'binary' and 'list' are disallowed but they are handled
+%% specifically as they do not have 2-tuple equivalents.
+disallowed_listen_options() ->
+ [alpn_advertised_protocols, client_preferred_next_protocols,
+ fallback, server_name_indication, srp_identity
+ |ranch_tcp:disallowed_listen_options()].
+
+-spec accept(ssl:sslsocket(), timeout())
+ -> {ok, ssl:sslsocket()} | {error, closed | timeout | atom()}.
+accept(LSocket, Timeout) ->
+ ssl:transport_accept(LSocket, Timeout).
+
+-spec accept_ack(ssl:sslsocket(), timeout()) -> ok.
+accept_ack(CSocket, Timeout) ->
+ {ok, _} = handshake(CSocket, [], Timeout),
+ ok.
+
+-spec handshake(inet:socket() | ssl:sslsocket(), opts(), timeout())
+ -> {ok, ssl:sslsocket()} | {error, any()}.
+handshake(CSocket, Opts, Timeout) ->
+ case ssl:handshake(CSocket, Opts, Timeout) of
+ {ok, NewSocket} ->
+ {ok, NewSocket};
+ Error = {error, _} ->
+ Error
+ end.
+
+%% @todo Probably filter Opts?
+-spec connect(inet:ip_address() | inet:hostname(),
+ inet:port_number(), any())
+ -> {ok, inet:socket()} | {error, atom()}.
+connect(Host, Port, Opts) when is_integer(Port) ->
+ ssl:connect(Host, Port,
+ Opts ++ [binary, {active, false}, {packet, raw}]).
+
+%% @todo Probably filter Opts?
+-spec connect(inet:ip_address() | inet:hostname(),
+ inet:port_number(), any(), timeout())
+ -> {ok, inet:socket()} | {error, atom()}.
+connect(Host, Port, Opts, Timeout) when is_integer(Port) ->
+ ssl:connect(Host, Port,
+ Opts ++ [binary, {active, false}, {packet, raw}],
+ Timeout).
+
+-spec recv(ssl:sslsocket(), non_neg_integer(), timeout())
+ -> {ok, any()} | {error, closed | atom()}.
+recv(Socket, Length, Timeout) ->
+ ssl:recv(Socket, Length, Timeout).
+
+-spec recv_proxy_header(ssl:sslsocket(), timeout())
+ -> {ok, ranch_proxy_header:proxy_info()}
+ | {error, closed | atom()}
+ | {error, protocol_error, atom()}.
+recv_proxy_header(SSLSocket, Timeout) ->
+ %% There's currently no documented way to perform a TCP recv
+ %% on an sslsocket(), even before the TLS handshake. However
+ %% nothing prevents us from retrieving the TCP socket and using
+ %% it. Since it's an undocumented interface this may however
+ %% make forward-compatibility more difficult.
+ {sslsocket, {gen_tcp, TCPSocket, _, _}, _} = SSLSocket,
+ ranch_tcp:recv_proxy_header(TCPSocket, Timeout).
+
+-spec send(ssl:sslsocket(), iodata()) -> ok | {error, atom()}.
+send(Socket, Packet) ->
+ ssl:send(Socket, Packet).
+
+-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd())
+ -> {ok, non_neg_integer()} | {error, atom()}.
+sendfile(Socket, Filename) ->
+ sendfile(Socket, Filename, 0, 0, []).
+
+-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(),
+ non_neg_integer(), non_neg_integer())
+ -> {ok, non_neg_integer()} | {error, atom()}.
+sendfile(Socket, File, Offset, Bytes) ->
+ sendfile(Socket, File, Offset, Bytes, []).
+
+%% Unlike with TCP, no syscall can be used here, so sending files
+%% through SSL will be much slower in comparison. Note that unlike
+%% file:sendfile/5 this function accepts either a file or a file name.
+-spec sendfile(ssl:sslsocket(), file:name_all() | file:fd(),
+ non_neg_integer(), non_neg_integer(), ranch_transport:sendfile_opts())
+ -> {ok, non_neg_integer()} | {error, atom()}.
+sendfile(Socket, File, Offset, Bytes, Opts) ->
+ ranch_transport:sendfile(?MODULE, Socket, File, Offset, Bytes, Opts).
+
+%% @todo Probably filter Opts?
+-spec setopts(ssl:sslsocket(), list()) -> ok | {error, atom()}.
+setopts(Socket, Opts) ->
+ ssl:setopts(Socket, Opts).
+
+-spec getopts(ssl:sslsocket(), [atom()]) -> {ok, list()} | {error, atom()}.
+getopts(Socket, Opts) ->
+ ssl:getopts(Socket, Opts).
+
+-spec getstat(ssl:sslsocket()) -> {ok, list()} | {error, atom()}.
+getstat(Socket) ->
+ ssl:getstat(Socket).
+
+-spec getstat(ssl:sslsocket(), [atom()]) -> {ok, list()} | {error, atom()}.
+getstat(Socket, OptionNames) ->
+ ssl:getstat(Socket, OptionNames).
+
+-spec controlling_process(ssl:sslsocket(), pid())
+ -> ok | {error, closed | not_owner | atom()}.
+controlling_process(Socket, Pid) ->
+ ssl:controlling_process(Socket, Pid).
+
+-spec peername(ssl:sslsocket())
+ -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+peername(Socket) ->
+ ssl:peername(Socket).
+
+-spec sockname(ssl:sslsocket())
+ -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+sockname(Socket) ->
+ ssl:sockname(Socket).
+
+-spec shutdown(ssl:sslsocket(), read | write | read_write)
+ -> ok | {error, atom()}.
+shutdown(Socket, How) ->
+ ssl:shutdown(Socket, How).
+
+-spec close(ssl:sslsocket()) -> ok.
+close(Socket) ->
+ ssl:close(Socket).
diff --git a/server/_build/default/lib/ranch/src/ranch_sup.erl b/server/_build/default/lib/ranch/src/ranch_sup.erl
new file mode 100644
index 0000000..3cb0cd6
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_sup.erl
@@ -0,0 +1,40 @@
+%% Copyright (c) 2011-2018, 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(ranch_sup).
+-behaviour(supervisor).
+
+-export([start_link/0]).
+-export([init/1]).
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, []).
+
+init([]) ->
+ Intensity = case application:get_env(ranch_sup_intensity) of
+ {ok, Value1} -> Value1;
+ undefined -> 1
+ end,
+ Period = case application:get_env(ranch_sup_period) of
+ {ok, Value2} -> Value2;
+ undefined -> 5
+ end,
+ ranch_server = ets:new(ranch_server, [
+ ordered_set, public, named_table]),
+ Procs = [
+ {ranch_server, {ranch_server, start_link, []},
+ permanent, 5000, worker, [ranch_server]}
+ ],
+ {ok, {{one_for_one, Intensity, Period}, Procs}}.
diff --git a/server/_build/default/lib/ranch/src/ranch_tcp.erl b/server/_build/default/lib/ranch/src/ranch_tcp.erl
new file mode 100644
index 0000000..b7ece5b
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_tcp.erl
@@ -0,0 +1,245 @@
+%% Copyright (c) 2011-2018, 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(ranch_tcp).
+-behaviour(ranch_transport).
+
+-export([name/0]).
+-export([secure/0]).
+-export([messages/0]).
+-export([listen/1]).
+-export([disallowed_listen_options/0]).
+-export([accept/2]).
+-export([accept_ack/2]).
+-export([handshake/3]).
+-export([connect/3]).
+-export([connect/4]).
+-export([recv/3]).
+-export([recv_proxy_header/2]).
+-export([send/2]).
+-export([sendfile/2]).
+-export([sendfile/4]).
+-export([sendfile/5]).
+-export([setopts/2]).
+-export([getopts/2]).
+-export([getstat/1]).
+-export([getstat/2]).
+-export([controlling_process/2]).
+-export([peername/1]).
+-export([sockname/1]).
+-export([shutdown/2]).
+-export([close/1]).
+
+-type opt() :: {backlog, non_neg_integer()}
+ | {buffer, non_neg_integer()}
+ | {delay_send, boolean()}
+ | {dontroute, boolean()}
+ | {exit_on_close, boolean()}
+ | {fd, non_neg_integer()}
+ | {high_msgq_watermark, non_neg_integer()}
+ | {high_watermark, non_neg_integer()}
+ | inet
+ | inet6
+ | {ip, inet:ip_address()}
+ | {ipv6_v6only, boolean()}
+ | {keepalive, boolean()}
+ | {linger, {boolean(), non_neg_integer()}}
+ | {low_msgq_watermark, non_neg_integer()}
+ | {low_watermark, non_neg_integer()}
+ | {nodelay, boolean()}
+ | {port, inet:port_number()}
+ | {priority, integer()}
+ | {raw, non_neg_integer(), non_neg_integer(), binary()}
+ | {recbuf, non_neg_integer()}
+ | {send_timeout, timeout()}
+ | {send_timeout_close, boolean()}
+ | {sndbuf, non_neg_integer()}
+ | {tos, integer()}.
+-export_type([opt/0]).
+
+-type opts() :: [opt()].
+-export_type([opts/0]).
+
+name() -> tcp.
+
+-spec secure() -> boolean().
+secure() ->
+ false.
+
+messages() -> {tcp, tcp_closed, tcp_error}.
+
+-spec listen(opts()) -> {ok, inet:socket()} | {error, atom()}.
+listen(Opts) ->
+ Opts2 = ranch:set_option_default(Opts, backlog, 1024),
+ Opts3 = ranch:set_option_default(Opts2, nodelay, true),
+ Opts4 = ranch:set_option_default(Opts3, send_timeout, 30000),
+ Opts5 = ranch:set_option_default(Opts4, send_timeout_close, true),
+ %% We set the port to 0 because it is given in the Opts directly.
+ %% The port in the options takes precedence over the one in the
+ %% first argument.
+ gen_tcp:listen(0, ranch:filter_options(Opts5, disallowed_listen_options(),
+ [binary, {active, false}, {packet, raw}, {reuseaddr, true}])).
+
+%% 'binary' and 'list' are disallowed but they are handled
+%% specifically as they do not have 2-tuple equivalents.
+disallowed_listen_options() ->
+ [active, header, mode, packet, packet_size, line_delimiter, reuseaddr].
+
+-spec accept(inet:socket(), timeout())
+ -> {ok, inet:socket()} | {error, closed | timeout | atom()}.
+accept(LSocket, Timeout) ->
+ gen_tcp:accept(LSocket, Timeout).
+
+-spec accept_ack(inet:socket(), timeout()) -> ok.
+accept_ack(CSocket, Timeout) ->
+ {ok, _} = handshake(CSocket, [], Timeout),
+ ok.
+
+-spec handshake(inet:socket(), opts(), timeout()) -> {ok, inet:socket()}.
+handshake(CSocket, _, _) ->
+ {ok, CSocket}.
+
+%% @todo Probably filter Opts?
+-spec connect(inet:ip_address() | inet:hostname(),
+ inet:port_number(), any())
+ -> {ok, inet:socket()} | {error, atom()}.
+connect(Host, Port, Opts) when is_integer(Port) ->
+ gen_tcp:connect(Host, Port,
+ Opts ++ [binary, {active, false}, {packet, raw}]).
+
+%% @todo Probably filter Opts?
+-spec connect(inet:ip_address() | inet:hostname(),
+ inet:port_number(), any(), timeout())
+ -> {ok, inet:socket()} | {error, atom()}.
+connect(Host, Port, Opts, Timeout) when is_integer(Port) ->
+ gen_tcp:connect(Host, Port,
+ Opts ++ [binary, {active, false}, {packet, raw}],
+ Timeout).
+
+-spec recv(inet:socket(), non_neg_integer(), timeout())
+ -> {ok, any()} | {error, closed | atom()}.
+recv(Socket, Length, Timeout) ->
+ gen_tcp:recv(Socket, Length, Timeout).
+
+-spec recv_proxy_header(inet:socket(), timeout())
+ -> {ok, ranch_proxy_header:proxy_info()}
+ | {error, closed | atom()}
+ | {error, protocol_error, atom()}.
+recv_proxy_header(Socket, Timeout) ->
+ case recv(Socket, 0, Timeout) of
+ {ok, Data} ->
+ case ranch_proxy_header:parse(Data) of
+ {ok, ProxyInfo, <<>>} ->
+ {ok, ProxyInfo};
+ {ok, ProxyInfo, Rest} ->
+ case gen_tcp:unrecv(Socket, Rest) of
+ ok ->
+ {ok, ProxyInfo};
+ Error ->
+ Error
+ end;
+ {error, HumanReadable} ->
+ {error, protocol_error, HumanReadable}
+ end;
+ Error ->
+ Error
+ end.
+
+-spec send(inet:socket(), iodata()) -> ok | {error, atom()}.
+send(Socket, Packet) ->
+ gen_tcp:send(Socket, Packet).
+
+-spec sendfile(inet:socket(), file:name_all() | file:fd())
+ -> {ok, non_neg_integer()} | {error, atom()}.
+sendfile(Socket, Filename) ->
+ sendfile(Socket, Filename, 0, 0, []).
+
+-spec sendfile(inet:socket(), file:name_all() | file:fd(), non_neg_integer(),
+ non_neg_integer())
+ -> {ok, non_neg_integer()} | {error, atom()}.
+sendfile(Socket, File, Offset, Bytes) ->
+ sendfile(Socket, File, Offset, Bytes, []).
+
+-spec sendfile(inet:socket(), file:name_all() | file:fd(), non_neg_integer(),
+ non_neg_integer(), [{chunk_size, non_neg_integer()}])
+ -> {ok, non_neg_integer()} | {error, atom()}.
+sendfile(Socket, Filename, Offset, Bytes, Opts)
+ when is_list(Filename) orelse is_atom(Filename)
+ orelse is_binary(Filename) ->
+ case file:open(Filename, [read, raw, binary]) of
+ {ok, RawFile} ->
+ try sendfile(Socket, RawFile, Offset, Bytes, Opts) of
+ Result -> Result
+ after
+ ok = file:close(RawFile)
+ end;
+ {error, _} = Error ->
+ Error
+ end;
+sendfile(Socket, RawFile, Offset, Bytes, Opts) ->
+ Opts2 = case Opts of
+ [] -> [{chunk_size, 16#1FFF}];
+ _ -> Opts
+ end,
+ try file:sendfile(RawFile, Socket, Offset, Bytes, Opts2) of
+ Result -> Result
+ catch
+ error:{badmatch, {error, enotconn}} ->
+ %% file:sendfile/5 might fail by throwing a
+ %% {badmatch, {error, enotconn}}. This is because its
+ %% implementation fails with a badmatch in
+ %% prim_file:sendfile/10 if the socket is not connected.
+ {error, closed}
+ end.
+
+%% @todo Probably filter Opts?
+-spec setopts(inet:socket(), list()) -> ok | {error, atom()}.
+setopts(Socket, Opts) ->
+ inet:setopts(Socket, Opts).
+
+-spec getopts(inet:socket(), [atom()]) -> {ok, list()} | {error, atom()}.
+getopts(Socket, Opts) ->
+ inet:getopts(Socket, Opts).
+
+-spec getstat(inet:socket()) -> {ok, list()} | {error, atom()}.
+getstat(Socket) ->
+ inet:getstat(Socket).
+
+-spec getstat(inet:socket(), [atom()]) -> {ok, list()} | {error, atom()}.
+getstat(Socket, OptionNames) ->
+ inet:getstat(Socket, OptionNames).
+
+-spec controlling_process(inet:socket(), pid())
+ -> ok | {error, closed | not_owner | atom()}.
+controlling_process(Socket, Pid) ->
+ gen_tcp:controlling_process(Socket, Pid).
+
+-spec peername(inet:socket())
+ -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+peername(Socket) ->
+ inet:peername(Socket).
+
+-spec sockname(inet:socket())
+ -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+sockname(Socket) ->
+ inet:sockname(Socket).
+
+-spec shutdown(inet:socket(), read | write | read_write)
+ -> ok | {error, atom()}.
+shutdown(Socket, How) ->
+ gen_tcp:shutdown(Socket, How).
+
+-spec close(inet:socket()) -> ok.
+close(Socket) ->
+ gen_tcp:close(Socket).
diff --git a/server/_build/default/lib/ranch/src/ranch_transport.erl b/server/_build/default/lib/ranch/src/ranch_transport.erl
new file mode 100644
index 0000000..486c6d6
--- /dev/null
+++ b/server/_build/default/lib/ranch/src/ranch_transport.erl
@@ -0,0 +1,151 @@
+%% Copyright (c) 2012-2018, 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(ranch_transport).
+
+-export([sendfile/6]).
+
+-type socket() :: any().
+-export_type([socket/0]).
+
+-type opts() :: any().
+-type stats() :: any().
+-type sendfile_opts() :: [{chunk_size, non_neg_integer()}].
+-export_type([sendfile_opts/0]).
+
+-callback name() -> atom().
+-callback secure() -> boolean().
+-callback messages() -> {OK::atom(), Closed::atom(), Error::atom()}.
+-callback listen(opts()) -> {ok, socket()} | {error, atom()}.
+-callback accept(socket(), timeout())
+ -> {ok, socket()} | {error, closed | timeout | atom()}.
+-callback handshake(socket(), opts(), timeout()) -> {ok, socket()} | {error, any()}.
+-callback connect(string(), inet:port_number(), opts())
+ -> {ok, socket()} | {error, atom()}.
+-callback connect(string(), inet:port_number(), opts(), timeout())
+ -> {ok, socket()} | {error, atom()}.
+-callback recv(socket(), non_neg_integer(), timeout())
+ -> {ok, any()} | {error, closed | timeout | atom()}.
+-callback recv_proxy_header(socket(), timeout())
+ -> {ok, ranch_proxy_header:proxy_info()}
+ | {error, closed | atom()}
+ | {error, protocol_error, atom()}.
+-callback send(socket(), iodata()) -> ok | {error, atom()}.
+-callback sendfile(socket(), file:name_all() | file:fd())
+ -> {ok, non_neg_integer()} | {error, atom()}.
+-callback sendfile(socket(), file:name_all() | file:fd(), non_neg_integer(),
+ non_neg_integer()) -> {ok, non_neg_integer()} | {error, atom()}.
+-callback sendfile(socket(), file:name_all() | file:fd(), non_neg_integer(),
+ non_neg_integer(), sendfile_opts())
+ -> {ok, non_neg_integer()} | {error, atom()}.
+-callback setopts(socket(), opts()) -> ok | {error, atom()}.
+-callback getopts(socket(), [atom()]) -> {ok, opts()} | {error, atom()}.
+-callback getstat(socket()) -> {ok, stats()} | {error, atom()}.
+-callback getstat(socket(), [atom()]) -> {ok, stats()} | {error, atom()}.
+-callback controlling_process(socket(), pid())
+ -> ok | {error, closed | not_owner | atom()}.
+-callback peername(socket())
+ -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+-callback sockname(socket())
+ -> {ok, {inet:ip_address(), inet:port_number()}} | {error, atom()}.
+-callback shutdown(socket(), read | write | read_write)
+ -> ok | {error, atom()}.
+-callback close(socket()) -> ok.
+
+%% A fallback for transports that don't have a native sendfile implementation.
+%% Note that the ordering of arguments is different from file:sendfile/5 and
+%% that this function accepts either a raw file or a file name.
+-spec sendfile(module(), socket(), file:name_all() | file:fd(),
+ non_neg_integer(), non_neg_integer(), sendfile_opts())
+ -> {ok, non_neg_integer()} | {error, atom()}.
+sendfile(Transport, Socket, Filename, Offset, Bytes, Opts)
+ when is_list(Filename) orelse is_atom(Filename)
+ orelse is_binary(Filename) ->
+ ChunkSize = chunk_size(Opts),
+ case file:open(Filename, [read, raw, binary]) of
+ {ok, RawFile} ->
+ _ = case Offset of
+ 0 ->
+ ok;
+ _ ->
+ {ok, _} = file:position(RawFile, {bof, Offset})
+ end,
+ try
+ sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize)
+ after
+ ok = file:close(RawFile)
+ end;
+ {error, _Reason} = Error ->
+ Error
+ end;
+sendfile(Transport, Socket, RawFile, Offset, Bytes, Opts) ->
+ ChunkSize = chunk_size(Opts),
+ Initial2 = case file:position(RawFile, {cur, 0}) of
+ {ok, Offset} ->
+ Offset;
+ {ok, Initial} ->
+ {ok, _} = file:position(RawFile, {bof, Offset}),
+ Initial
+ end,
+ case sendfile_loop(Transport, Socket, RawFile, Bytes, 0, ChunkSize) of
+ {ok, _Sent} = Result ->
+ {ok, _} = file:position(RawFile, {bof, Initial2}),
+ Result;
+ {error, _Reason} = Error ->
+ Error
+ end.
+
+-spec chunk_size(sendfile_opts()) -> pos_integer().
+chunk_size(Opts) ->
+ case lists:keyfind(chunk_size, 1, Opts) of
+ {chunk_size, ChunkSize}
+ when is_integer(ChunkSize) andalso ChunkSize > 0 ->
+ ChunkSize;
+ {chunk_size, 0} ->
+ 16#1FFF;
+ false ->
+ 16#1FFF
+ end.
+
+-spec sendfile_loop(module(), socket(), file:fd(), non_neg_integer(),
+ non_neg_integer(), pos_integer())
+ -> {ok, non_neg_integer()} | {error, any()}.
+sendfile_loop(_Transport, _Socket, _RawFile, Sent, Sent, _ChunkSize)
+ when Sent =/= 0 ->
+ %% All requested data has been read and sent, return number of bytes sent.
+ {ok, Sent};
+sendfile_loop(Transport, Socket, RawFile, Bytes, Sent, ChunkSize) ->
+ ReadSize = read_size(Bytes, Sent, ChunkSize),
+ case file:read(RawFile, ReadSize) of
+ {ok, IoData} ->
+ case Transport:send(Socket, IoData) of
+ ok ->
+ Sent2 = iolist_size(IoData) + Sent,
+ sendfile_loop(Transport, Socket, RawFile, Bytes, Sent2,
+ ChunkSize);
+ {error, _Reason} = Error ->
+ Error
+ end;
+ eof ->
+ {ok, Sent};
+ {error, _Reason} = Error ->
+ Error
+ end.
+
+-spec read_size(non_neg_integer(), non_neg_integer(), non_neg_integer()) ->
+ non_neg_integer().
+read_size(0, _Sent, ChunkSize) ->
+ ChunkSize;
+read_size(Bytes, Sent, ChunkSize) ->
+ min(Bytes - Sent, ChunkSize).