[
  {
    "path": ".gitattributes",
    "content": "# Don't include Erlang.mk in diffs.\nerlang.mk -diff\n\n# Don't change line endings in our test data on Windows.\ntest/ws_perf_SUITE_data/*.txt -text\n"
  },
  {
    "path": ".github/workflows/ci.yaml",
    "content": "## Use workflows from ninenines/ci.erlang.mk to test Cowboy.\n\nname: Check Cowboy\n\non:\n  push:\n    branches:\n      - master\n  pull_request:\n  schedule:\n      ## Every Monday at 2am.\n      - cron: 0 2 * * 1\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\njobs:\n  check:\n    name: Check\n    if: ${{ !cancelled() }}\n    uses: ninenines/ci.erlang.mk/.github/workflows/ci.yaml@master\n\n  dialyzer-no-quicer:\n    name: Check / Dialyzer (without COWBOY_QUICER)\n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Checkout repository\n      uses: actions/checkout@v4\n\n    - name: Install latest Erlang/OTP\n      uses: erlef/setup-beam@v1\n      with:\n        otp-version: '> 0'\n\n    - name: Run Dialyzer (without COWBOY_QUICER)\n      run: make dialyze COWBOY_QUICER=0\n\n  examples:\n    name: Check examples\n    runs-on: ubuntu-latest\n    steps:\n\n    - name: Checkout repository\n      uses: actions/checkout@v4\n\n    - name: Install latest Erlang/OTP\n      uses: erlef/setup-beam@v1\n      with:\n        otp-version: '> 0'\n\n    - name: Run ct-examples\n      run: make ct-examples\n\n    - name: Upload logs\n      uses: actions/upload-artifact@v4\n      if: always()\n      with:\n        name: Common Test logs (examples)\n        path: |\n          logs/\n          !logs/**/log_private\n"
  },
  {
    "path": ".gitignore",
    "content": ".cowboy.plt\n.erlang.mk\n_rel\ncowboy.d\ndeps\ndoc/guide.pdf\ndoc/html\ndoc/man3\ndoc/man7\nebin/*.beam\nebin/test\nexamples/*/ebin\nexamples/*/*.d\nlogs\nrelx\ntest/*.beam\n"
  },
  {
    "path": "CONTRIBUTING.asciidoc",
    "content": "= Contributing\n\nThis document is a guide on how to best contribute to this project.\n\n== Definitions\n\n*SHOULD* describes optional steps. *MUST* describes mandatory steps.\n\n*SHOULD NOT* and *MUST NOT* describes pitfalls to avoid.\n\n_Your local copy_ refers to the copy of the repository that you have\non your computer. _origin_ refers to your fork of the project. _upstream_\nrefers to the official repository for this project.\n\n== Discussions\n\nFor general discussion about this project, please open a ticket.\nFeedback is always welcome and may transform in tasks to improve\nthe project, so having the discussion start there is a plus.\n\nAlternatively you may try the https://discord.gg/x468ZsxG[Discord server]\nor, if you need the discussion to stay private, you can send an\nemail at contact@ninenines.eu.\n\n== Support\n\nFree support is generally not available. The rule is that free\nsupport is only given if doing so benefits most users. In practice\nthis means that free support will only be given if the issues are\ndue to a fault in the project itself or its documentation.\n\nPaid support is available for all price ranges. Please send an\nemail to contact@ninenines.eu for more information.\n\n== Bug reports\n\nYou *SHOULD* open a ticket for every bug you encounter, regardless\nof the version you use. A ticket not only helps the project ensure\nthat bugs are squashed, it also helps other users who later run\ninto this issue. You *SHOULD* give as much information as possible\nincluding what commit/branch, what OS/version and so on.\n\nYou *SHOULD NOT* open a ticket if another already exists for the\nsame issue. You *SHOULD* instead either add more information by\ncommenting on it, or simply comment to inform the maintainer that\nyou are also affected. The maintainer *SHOULD* reply to every\nnew ticket when they are opened. If the maintainer didn't say\nanything after a few days, you *SHOULD* write a new comment asking\nfor more information.\n\nYou *SHOULD* provide a reproducible test case, either in the\nticket or by sending a pull request and updating the test suite.\n\nWhen you have a fix ready, you *SHOULD* open a pull request,\neven if the code does not fit the requirements discussed below.\nProviding a fix, even a dirty one, can help other users and/or\nat least get the maintainer on the right tracks.\n\nYou *SHOULD* try to relax and be patient. Some tickets are merged\nor fixed quickly, others aren't. There's no real rules around that.\nYou can become a paying customer if you need something fast.\n\n== Security reports\n\nYou *SHOULD* open a ticket when you identify a DoS vulnerability\nin this project. You *SHOULD* include the resources needed to\nDoS the project; every project can be brought down if you have\nthe necessary resources.\n\nYou *SHOULD* send an email to contact@ninenines.eu when you\nidentify a security vulnerability. If the vulnerability originates\nfrom code inside Erlang/OTP itself, you *SHOULD* also consult\nwith OTP Team directly to get the problem fixed upstream.\n\n== Feature requests\n\nFeature requests are always welcome. To be accepted, however, they\nmust be well defined, make sense in the context of the project and\nbenefit most users.\n\nFeature requests not benefiting most users  may only be accepted\nwhen accompanied with a proper pull request.\n\nYou *MUST* open a ticket to explain what the new feature is, even\nif you are going to submit a pull request for it.\n\nAll these conditions are meant to ensure that the project stays\nlightweight and maintainable.\n\n== Documentation submissions\n\nYou *SHOULD* follow the code submission guidelines to submit\ndocumentation.\n\nThe documentation is available in the 'doc/src/' directory. There\nare three kinds of documentation: manual, guide and tutorials. The\nformat for the documentation is Asciidoc.\n\nYou *SHOULD* follow the same style as the surrounding documentation\nwhen editing existing files.\n\nYou *MUST* include the source when providing media.\n\n== Examples submissions\n\nYou *SHOULD* follow the code submission guidelines to submit examples.\n\nThe examples are available in the 'examples/' directory.\n\nYou *SHOULD* focus on exactly one thing per example.\n\n== Code submissions\n\nYou *SHOULD* open a pull request to submit code.\n\nYou *SHOULD* open a ticket to discuss backward incompatible changes\nbefore you submit code. This step ensures that you do not work on\na large change that will then be rejected.\n\nYou *SHOULD* send your code submission using a pull request on GitHub.\nIf you can't, please send an email to contact@ninenines.eu with your\npatch.\n\nThe following sections explain the normal GitHub workflow.\n\n=== Cloning\n\nYou *MUST* fork the project's repository on GitHub by clicking on the\n_Fork_ button.\n\nOn the right page of your fork's page is a field named _SSH clone URL_.\nIts contents will be identified as `$ORIGIN_URL` in the following snippet.\n\nOn the right side of the project's repository page is a similar field.\nIts contents will be identified as `$UPSTREAM_URL`.\n\nFinally, `$PROJECT` is the name of this project.\n\nTo setup your clone and be able to rebase when requested, run the\nfollowing commands:\n\n[source,bash]\n$ git clone $ORIGIN_URL\n$ cd $PROJECT\n$ git remote add upstream $UPSTREAM_URL\n\n=== Branching\n\nYou *SHOULD* base your branch on _master_, unless your patch applies\nto a stable release, in which case you need to base your branch on\nthe stable branch, for example _1.0.x_.\n\nThe first step is therefore to checkout the branch in question:\n\n[source,bash]\n$ git checkout 1.0.x\n\nThe next step is to update the branch to the current version from\n_upstream_. In the following snippet, replace _1.0.x_ by _master_\nif you are patching _master_.\n\n[source,bash]\n$ git fetch upstream\n$ git rebase upstream/1.0.x\n\nThis last command may fail and ask you to stash your changes. When\nthat happens, run the following sequence of commands:\n\n[source,bash]\n$ git stash\n$ git rebase upstream/1.0.x\n$ git stash pop\n\nThe final step is to create a new branch you can work in. The name\nof the new branch is up to you, there is no particular requirement.\nReplace `$BRANCH` with the branch name you came up with:\n\n[source,bash]\n$ git checkout -b $BRANCH\n\n_Your local copy_ is now ready.\n\n=== Source editing\n\nThere are very few rules with regard to source code editing.\n\nYou *MUST* use horizontal tabs for indentation. Use one tab\nper indentation level.\n\nYou *MUST NOT* align code. You can only add or remove one\nindentation level compared to the previous line.\n\nYou *SHOULD NOT* write lines more than about a hundred\ncharacters. There is no hard limit, just try to keep it\nas readable as possible.\n\nYou *SHOULD* write small functions when possible.\n\nYou *SHOULD* avoid a too big hierarchy of case clauses inside\na single function.\n\nYou *SHOULD* add tests to make sure your code works.\n\n=== Committing\n\nYou *SHOULD* run Dialyzer and the test suite while working on\nyour patch, and you *SHOULD* ensure that no additional tests\nfail when you finish.\n\nYou can use the following command to run Dialyzer:\n\n[source,bash]\n$ make dialyze\n\nYou have two options to run tests. You can either run tests\nacross all supported Erlang versions, or just on the version\nyou are currently using.\n\nTo test across all supported Erlang versions:\n\n[source,bash]\n$ make -k ci\n\nTo test using the current version:\n\n[source,bash]\n$ make tests\n\nYou can then open Common Test logs in 'logs/all_runs.html'.\n\nBy default Cowboy excludes a few test suites that take too\nlong to complete. For example all the examples are built and\ntested, and one Websocket test suite is very extensive. In\norder to run everything, do:\n\n[source,bash]\n$ make tests FULL=1\n\nOnce all tests pass (or at least, no new tests are failing),\nyou can commit your changes.\n\nFirst you need to add your changes:\n\n[source,bash]\n$ git add src/file_you_edited.erl\n\nIf you want an interactive session, allowing you to filter\nout changes that have nothing to do with this commit:\n\n[source,bash]\n$ git add -p\n\nYou *MUST* put all related changes inside a single commit. The\ngeneral rule is that all commits must pass tests. Fix one bug\nper commit. Add one feature per commit. Separate features in\nmultiple commits only if smaller parts of the feature make\nsense on their own.\n\nFinally once all changes are added you can commit. This\ncommand will open the editor of your choice where you can\nput a proper commit title and message.\n\n[source,bash]\n$ git commit\n\nDo not use the `-m` option as it makes it easy to break the\nfollowing rules:\n\nYou *MUST* write a proper commit title and message. The commit\ntitle is the first line and *MUST* be at most 72 characters.\nThe second line *MUST* be left blank. Everything after that is\nthe commit message. You *SHOULD* write a detailed commit\nmessage. The lines of the message *MUST* be at most 80 characters.\nYou *SHOULD* explain what the commit does, what references you\nused and any other information that helps understanding why\nthis commit exists. You *MUST NOT* include commands to close\nGitHub tickets automatically.\n\n=== Cleaning the commit history\n\nIf you create a new commit every time you make a change, however\ninsignificant, you *MUST* consolidate those commits before\nsending the pull request.\n\nThis is done through _rebasing_. The easiest way to do so is\nto use interactive rebasing, which allows you to choose which\ncommits to keep, squash, edit and so on. To rebase, you need\nto give the original commit before you made your changes. If\nyou only did two changes, you can use the shortcut form `HEAD^^`:\n\n[source,bash]\n$ git rebase -i HEAD^^\n\n=== Submitting the pull request\n\nYou *MUST* push your branch to your fork on GitHub. Replace\n`$BRANCH` with your branch name:\n\n[source,bash]\n$ git push origin $BRANCH\n\nYou can then submit the pull request using the GitHub interface.\nYou *SHOULD* provide an explanatory message and refer to any\nprevious ticket related to this patch. You *MUST NOT* include\ncommands to close other tickets automatically.\n\n=== Updating the pull request\n\nSometimes the maintainer will ask you to change a few things.\nOther times you will notice problems with your submission and\nwant to fix them on your own.\n\nIn either case you do not need to close the pull request. You\ncan just push your changes again and, if needed, force them.\nThis will update the pull request automatically.\n\n[source,bash]\n$ git push -f origin $BRANCH\n\n=== Merging\n\nThis is an open source project maintained by independent developers.\nPlease be patient when your changes aren't merged immediately.\n\nAll pull requests run through a Continuous Integration service\nto ensure nothing gets broken by the changes submitted.\n\nBug fixes will be merged immediately when all tests pass.\nThe maintainer may do style changes in the merge commit if\nthe submitter is not available. The maintainer *MUST* open\na new ticket if the solution could still be improved.\n\nNew features and backward incompatible changes will be merged\nwhen all tests pass and all other requirements are fulfilled.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2011-2025, Loïc Hoguin <essen@ninenines.eu>\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "# See LICENSE for licensing information.\n\nPROJECT = cowboy\nPROJECT_DESCRIPTION = Small, fast, modern HTTP server.\nPROJECT_VERSION = 2.14.2\nPROJECT_REGISTERED = cowboy_clock\n\n# Options.\n\nPLT_APPS = public_key ssl # ct_helper gun common_test inets\nCT_OPTS += -ct_hooks cowboy_ct_hook [] # -boot start_sasl\n#CT_OPTS += +JPperf true +S 1\n\n# Dependencies.\n\nLOCAL_DEPS = crypto\n\nDEPS = cowlib ranch\ndep_cowlib = git https://github.com/ninenines/cowlib 2.16.0\ndep_ranch = git https://github.com/ninenines/ranch 1.8.1\n\nifeq ($(COWBOY_QUICER),1)\nDEPS += quicer\ndep_quicer = git https://github.com/emqx/quic main\nendif\n\nDOC_DEPS = asciideck\n\nTEST_DEPS = $(if $(CI_ERLANG_MK),ci.erlang.mk) ct_helper gun\ndep_ct_helper = git https://github.com/extend/ct_helper master\ndep_gun = git https://github.com/ninenines/gun master\n\n# CI configuration.\n\ndep_ci.erlang.mk = git https://github.com/ninenines/ci.erlang.mk master\nDEP_EARLY_PLUGINS = ci.erlang.mk\n\nAUTO_CI_OTP ?= OTP-LATEST-24+\nAUTO_CI_WINDOWS ?= OTP-LATEST-24+\n\n# Hex configuration.\n\ndefine HEX_TARBALL_EXTRA_METADATA\n#{\n\tlicenses => [<<\"ISC\">>],\n\tlinks => #{\n\t\t<<\"User guide\">> => <<\"https://ninenines.eu/docs/en/cowboy/2.14/guide/\">>,\n\t\t<<\"Function reference\">> => <<\"https://ninenines.eu/docs/en/cowboy/2.14/manual/\">>,\n\t\t<<\"GitHub\">> => <<\"https://github.com/ninenines/cowboy\">>,\n\t\t<<\"Sponsor\">> => <<\"https://github.com/sponsors/essen\">>\n\t}\n}\nendef\n\nhex_req_ranch = >= 1.8.0 and < 3.0.0\nhex_req_cowlib = >= 2.16.0 and < 3.0.0\n\n# Standard targets.\n\ninclude erlang.mk\n\n# Don't run the examples/autobahn test suites by default.\n\nifndef FULL\nCT_SUITES := $(filter-out examples http_perf ws_autobahn ws_perf,$(CT_SUITES))\nendif\n\n# Don't run HTTP/3 test suites on Windows.\n\nifeq ($(PLATFORM),msys2)\nCT_SUITES := $(filter-out rfc9114 rfc9204 rfc9220,$(CT_SUITES))\nendif\n\n# Compile options.\n\nERLC_OPTS += +warn_missing_spec +warn_untyped_record # +bin_opt_info\nTEST_ERLC_OPTS += +'{parse_transform, eunit_autoexport}'\n\nifeq ($(COWBOY_QUICER),1)\nERLC_OPTS += -D COWBOY_QUICER=1\nTEST_ERLC_OPTS += -D COWBOY_QUICER=1\nendif\n\n# Generate rebar.config on build.\n\napp:: rebar.config\n\n# Fix quicer compilation for HTTP/3.\n\nautopatch-quicer::\n\t$(verbose) printf \"%s\\n\" \"all: ;\" > $(DEPS_DIR)/quicer/c_src/Makefile.erlang.mk\n\n# Dialyze the tests.\n\n#DIALYZER_OPTS += --src -r test\n\n# h2spec setup.\n\nGOPATH := $(ERLANG_MK_TMP)/gopath\nexport GOPATH\n\nH2SPEC := $(GOPATH)/src/github.com/summerwind/h2spec/h2spec\nexport H2SPEC\n\n# @todo It would be better to allow these dependencies to be specified\n# on a per-target basis instead of for all targets.\ntest-build:: $(H2SPEC)\n\n$(H2SPEC):\n\t$(gen_verbose) mkdir -p $(GOPATH)/src/github.com/summerwind\n\t$(verbose) git clone --depth 1 https://github.com/summerwind/h2spec $(dir $(H2SPEC)) || true\n\t$(verbose) $(MAKE) -C $(dir $(H2SPEC)) build MAKEFLAGS= || true\n\n# Prepare for the release.\n\nprepare_tag:\n\t$(verbose) $(warning Hex metadata: $(HEX_TARBALL_EXTRA_METADATA))\n\t$(verbose) echo\n\t$(verbose) echo -n \"Most recent tag:            \"\n\t$(verbose) git tag --sort taggerdate | tail -n1\n\t$(verbose) git verify-tag `git tag --sort taggerdate | tail -n1`\n\t$(verbose) echo -n \"MAKEFILE: \"\n\t$(verbose) grep -m1 PROJECT_VERSION Makefile\n\t$(verbose) echo -n \"APP:                 \"\n\t$(verbose) grep -m1 vsn ebin/$(PROJECT).app | sed 's/\t//g'\n\t$(verbose) echo -n \"GUIDE:  \"\n\t$(verbose) grep -h dep_$(PROJECT)_commit doc/src/guide/*.asciidoc || true\n\t$(verbose) echo\n\t$(verbose) echo \"Links in the README:\"\n\t$(verbose) grep http.*:// README.asciidoc\n\t$(verbose) echo\n\t$(verbose) echo \"Titles in most recent CHANGELOG:\"\n\t$(verbose) for f in `ls -rv doc/src/guide/migrating_from_*.asciidoc | head -n1`; do \\\n\t\techo $$f:; \\\n\t\tgrep == $$f; \\\n\tdone\n\t$(verbose) echo\n\t$(verbose) echo \"Dependencies:\"\n\t$(verbose) grep ^DEPS Makefile || echo \"DEPS =\"\n\t$(verbose) grep ^dep_ Makefile || true\n\t$(verbose) grep ^hex_req_ Makefile || true\n\t$(verbose) echo\n\t$(verbose) echo \"rebar.config:\"\n\t$(verbose) cat rebar.config || true\n"
  },
  {
    "path": "README.asciidoc",
    "content": "= Cowboy\n\nCowboy is a small, fast and modern HTTP server for Erlang/OTP.\n\n== Goals\n\nCowboy aims to provide a *complete* HTTP stack in a *small* code base.\nIt is optimized for *low latency* and *low memory usage*, in part\nbecause it uses *binary strings*.\n\nCowboy provides *routing* capabilities, selectively dispatching requests\nto handlers written in Erlang.\n\nBecause it uses Ranch for managing connections, Cowboy can easily be\n*embedded* in any other application.\n\nCowboy is *clean* and *well tested* Erlang code.\n\n== Online documentation\n\n* https://ninenines.eu/docs/en/cowboy/2.14/guide[User guide]\n* https://ninenines.eu/docs/en/cowboy/2.14/manual[Function reference]\n\n== Offline documentation\n\n* While still online, run `make docs`\n* User guide available in `doc/` in PDF and HTML formats\n* Function reference man pages available in `doc/man3/` and `doc/man7/`\n* Run `make install-docs` to install man pages on your system\n* Full documentation in Asciidoc available in `doc/src/`\n* Examples available in `examples/`\n\n== Getting help\n\n* https://discord.gg/x25nNq2fFE[Discord server]\n* https://github.com/ninenines/cowboy/issues[Issues tracker]\n* https://ninenines.eu/services[Commercial Support]\n* https://github.com/sponsors/essen[Sponsor me!]\n"
  },
  {
    "path": "doc/src/guide/book.asciidoc",
    "content": "// a2x: --dblatex-opts \"-P latex.output.revhistory=0 -P doc.publisher.show=0 -P index.numbered=0\"\n// a2x: --dblatex-opts \"-s cowboy\"\n// a2x: -d book --attribute tabsize=4\n\n= Cowboy User Guide\n\n// REST: where should i handle bindings? init, probably. qs? in media type functions\n// REST: explain how a module per media type is good; module may be shared between client/server\n\n= Rationale\n\ninclude::modern_web.asciidoc[The modern Web]\n\ninclude::erlang_web.asciidoc[Erlang and the Web]\n\n= Introduction\n\ninclude::introduction.asciidoc[Introduction]\n\ninclude::getting_started.asciidoc[Getting started]\n\ninclude::flow_diagram.asciidoc[Flow diagram]\n\n= Configuration\n\ninclude::listeners.asciidoc[Listeners]\n\ninclude::routing.asciidoc[Routing]\n\ninclude::constraints.asciidoc[Constraints]\n\n= Handlers\n\ninclude::handlers.asciidoc[Handlers]\n\ninclude::loop_handlers.asciidoc[Loop handlers]\n\ninclude::static_files.asciidoc[Static files]\n\n= Request and response\n\ninclude::req.asciidoc[Request details]\n\ninclude::req_body.asciidoc[Reading the request body]\n\ninclude::resp.asciidoc[Sending a response]\n\ninclude::cookies.asciidoc[Using cookies]\n\ninclude::multipart.asciidoc[Multipart]\n\n= REST\n\ninclude::rest_principles.asciidoc[REST principles]\n\ninclude::rest_handlers.asciidoc[Handling REST requests]\n\ninclude::rest_flowcharts.asciidoc[REST flowcharts]\n\ninclude::resource_design.asciidoc[Designing a resource handler]\n\n= Websocket\n\ninclude::ws_protocol.asciidoc[The Websocket protocol]\n\ninclude::ws_handlers.asciidoc[Websocket handlers]\n\n= Advanced\n\ninclude::streams.asciidoc[Streams]\n\ninclude::middlewares.asciidoc[Middlewares]\n\ninclude::performance.asciidoc[Performance]\n\n= Additional information\n\ninclude::migrating_from_2.14.asciidoc[Changes since Cowboy 2.14]\n\ninclude::migrating_from_2.13.asciidoc[Migrating from Cowboy 2.13 to 2.14]\n\ninclude::migrating_from_2.12.asciidoc[Migrating from Cowboy 2.12 to 2.13]\n\ninclude::migrating_from_2.11.asciidoc[Migrating from Cowboy 2.11 to 2.12]\n\ninclude::migrating_from_2.10.asciidoc[Migrating from Cowboy 2.10 to 2.11]\n\ninclude::migrating_from_2.9.asciidoc[Migrating from Cowboy 2.9 to 2.10]\n\ninclude::migrating_from_2.8.asciidoc[Migrating from Cowboy 2.8 to 2.9]\n\ninclude::migrating_from_2.7.asciidoc[Migrating from Cowboy 2.7 to 2.8]\n\ninclude::migrating_from_2.6.asciidoc[Migrating from Cowboy 2.6 to 2.7]\n\ninclude::migrating_from_2.5.asciidoc[Migrating from Cowboy 2.5 to 2.6]\n\ninclude::migrating_from_2.4.asciidoc[Migrating from Cowboy 2.4 to 2.5]\n\ninclude::migrating_from_2.3.asciidoc[Migrating from Cowboy 2.3 to 2.4]\n\ninclude::migrating_from_2.2.asciidoc[Migrating from Cowboy 2.2 to 2.3]\n\ninclude::migrating_from_2.1.asciidoc[Migrating from Cowboy 2.1 to 2.2]\n\ninclude::migrating_from_2.0.asciidoc[Migrating from Cowboy 2.0 to 2.1]\n\ninclude::migrating_from_1.0.asciidoc[Migrating from Cowboy 1.0 to 2.0]\n\ninclude::specs.asciidoc[HTTP and other specifications]\n"
  },
  {
    "path": "doc/src/guide/constraints.asciidoc",
    "content": "[[constraints]]\n== Constraints\n\nConstraints are validation and conversion functions applied\nto user input.\n\nThey are used in various places in Cowboy, including the\nrouter and the `cowboy_req` match functions.\n\n=== Syntax\n\nConstraints are provided as a list of fields. For each field\nin the list, specific constraints can be applied, as well as\na default value if the field is missing.\n\nA field can take the form of an atom `field`, a tuple with\nconstraints `{field, Constraints}` or a tuple with constraints\nand a default value `{field, Constraints, Default}`.\nThe `field` form indicates the field is mandatory.\n\nNote that when used with the router, only the second form\nmakes sense, as it does not use the default and the field\nis always defined.\n\nConstraints for each field are provided as an ordered list\nof atoms or funs to apply. Built-in constraints are provided\nas atoms, while custom constraints are provided as funs.\n\nWhen multiple constraints are provided, they are applied in\nthe order given. If the value has been modified by a constraint\nthen the next one receives the new value.\n\nFor example, the following constraints will first validate\nand convert the field `my_value` to an integer, and then\ncheck that the integer is positive:\n\n[source,erlang]\n----\nPositiveFun = fun\n    (_, V) when V > 0 ->\n        {ok, V};\n    (_, _) ->\n        {error, not_positive}\nend,\n{my_value, [int, PositiveFun]}.\n----\n\nWe ignore the first fun argument in this snippet. We shouldn't.\nWe will simply learn what it is later in this chapter.\n\nWhen there's only one constraint, it can be provided directly\nwithout wrapping it into a list:\n\n[source,erlang]\n----\n{my_value, int}\n----\n\n=== Built-in constraints\n\nBuilt-in constraints are specified as an atom:\n\n[cols=\"<,<\",options=\"header\"]\n|===\n| Constraint | Description\n| int        | Converts binary value to integer.\n| nonempty   | Ensures the binary value is non-empty.\n|===\n\n=== Custom constraints\n\nCustom constraints are specified as a fun. This fun takes\ntwo arguments. The first argument indicates the operation\nto be performed, and the second is the value. What the\nvalue is and what must be returned depends on the operation.\n\nCowboy currently defines three operations. The operation\nused for validating and converting user input is the `forward`\noperation.\n\n[source,erlang]\n----\nint(forward, Value) ->\n    try\n        {ok, binary_to_integer(Value)}\n    catch _:_ ->\n        {error, not_an_integer}\n    end;\n----\n\nThe value must be returned even if it is not converted\nby the constraint.\n\nThe two other operations are currently experimental. They are\nmeant to help implement HATEOAS type services, but proper\nsupport for HATEOAS is not expected to be available before\nCowboy 3.0 because of Cowboy's current router's limitations.\n\nThe `reverse` operation does the opposite: it\ntakes a converted value and changes it back to what the\nuser input would have been.\n\n[source,erlang]\n----\nint(reverse, Value) ->\n\ttry\n\t\t{ok, integer_to_binary(Value)}\n\tcatch _:_ ->\n\t\t{error, not_an_integer}\n\tend;\n----\n\nFinally, the `format_error` operation takes an error\nreturned by any other operation and returns a formatted\nhuman-readable error message.\n\n[source,erlang]\n----\nint(format_error, {not_an_integer, Value}) ->\n\tio_lib:format(\"The value ~p is not an integer.\", [Value]).\n----\n\nNotice that for this case you get both the error and\nthe value that was given to the constraint that produced\nthis error.\n\nCowboy will not catch exceptions coming from constraint\nfunctions. They should be written to not emit any exceptions.\n"
  },
  {
    "path": "doc/src/guide/cookies.asciidoc",
    "content": "[[cookies]]\n== Using cookies\n\nCookies are a mechanism allowing applications to maintain\nstate on top of the stateless HTTP protocol.\n\nCookies are a name/value store where the names and values are\nstored in plain text. They expire either after a delay\nor when the browser closes. They can be configured on a\nspecific domain name or path, and restricted to secure\nresources (sent or downloaded over HTTPS), or restricted\nto the server (disallowing access from client-side scripts).\n\nCookie names are de facto case sensitive.\n\nCookies are stored client-side and sent with every subsequent\nrequest that matches the domain and path for which they were\nstored, until they expire. This can create a non-negligible\ncost.\n\nCookies should not be considered secure. They are stored on\nthe user's computer in plain text, and can be read by any\nprogram. They can also be read by proxies when using clear\nconnections. Always validate the value before using it,\nand never store any sensitive information inside it.\n\nCookies set by the server are only available in requests\nfollowing the client reception of the response containing\nthem.\n\nCookies may be sent repeatedly. This is often useful to\nupdate the expiration time and avoid losing a cookie.\n\n=== Setting cookies\n\nBy default cookies are defined for the duration of the session:\n\n[source,erlang]\n----\nSessionID = generate_session_id(),\nReq = cowboy_req:set_resp_cookie(<<\"sessionid\">>, SessionID, Req0).\n----\n\nThey can also be set for a duration in seconds:\n\n[source,erlang]\n----\nSessionID = generate_session_id(),\nReq = cowboy_req:set_resp_cookie(<<\"sessionid\">>, SessionID, Req0,\n    #{max_age => 3600}).\n----\n\nTo delete cookies, set `max_age` to 0:\n\n[source,erlang]\n----\nSessionID = generate_session_id(),\nReq = cowboy_req:set_resp_cookie(<<\"sessionid\">>, SessionID, Req0,\n    #{max_age => 0}).\n----\n\nTo restrict cookies to a specific domain and path, the options\nof the same name can be used:\n\n[source,erlang]\n----\nReq = cowboy_req:set_resp_cookie(<<\"inaccount\">>, <<\"1\">>, Req0,\n    #{domain => \"my.example.org\", path => \"/account\"}).\n----\n\nCookies will be sent with requests to this domain and all\nits subdomains, and to resources on this path or deeper\nin the path hierarchy.\n\nTo restrict cookies to secure channels (typically resources\navailable over HTTPS):\n\n[source,erlang]\n----\nSessionID = generate_session_id(),\nReq = cowboy_req:set_resp_cookie(<<\"sessionid\">>, SessionID, Req0,\n    #{secure => true}).\n----\n\nTo prevent client-side scripts from accessing a cookie:\n\n[source,erlang]\n----\nSessionID = generate_session_id(),\nReq = cowboy_req:set_resp_cookie(<<\"sessionid\">>, SessionID, Req0,\n    #{http_only => true}).\n----\n\nCookies may also be set client-side, for example using\nJavascript.\n\n=== Reading cookies\n\nThe client only ever sends back the cookie name and value.\nAll other options that can be set are never sent back.\n\nCowboy provides two functions for reading cookies. Both\ninvolve parsing the cookie header(s) and so should not\nbe called repeatedly.\n\nYou can get all cookies as a key/value list:\n\n[source,erlang]\nCookies = cowboy_req:parse_cookies(Req),\n{_, Lang} = lists:keyfind(<<\"lang\">>, 1, Cookies).\n\nOr you can perform a match against cookies and retrieve\nonly the ones you need, while at the same time doing\nany required post processing using xref:constraints[constraints].\nThis function returns a map:\n\n[source,erlang]\n#{id := ID, lang := Lang} = cowboy_req:match_cookies([id, lang], Req).\n\nYou can use constraints to validate the values while matching\nthem. The following snippet will crash if the `id` cookie is\nnot an integer number or if the `lang` cookie is empty. Additionally\nthe `id` cookie value will be converted to an integer term:\n\n[source,erlang]\nCookiesMap = cowboy_req:match_cookies([{id, int}, {lang, nonempty}], Req).\n\nNote that if two cookies share the same name, then the map value\nwill be a list of the two cookie values.\n\nA default value can be provided. The default will be used\nif the `lang` cookie is not found. It will not be used if\nthe cookie is found but has an empty value:\n\n[source,erlang]\n#{lang := Lang} = cowboy_req:match_cookies([{lang, [], <<\"en-US\">>}], Req).\n\nIf no default is provided and the value is missing, an\nexception is thrown.\n"
  },
  {
    "path": "doc/src/guide/cowboy.sty",
    "content": "\\NeedsTeXFormat{LaTeX2e}\n\\ProvidesPackage{asciidoc-dblatex}[2012/10/24 AsciiDoc DocBook Style]\n\n%% Just use the original package and pass the options.\n\\RequirePackageWithOptions{docbook}\n\n%% Define an alias for make snippets to be compatible with source-highlighter.\n\\lstalias{makefile}{make}\n"
  },
  {
    "path": "doc/src/guide/erlang_web.asciidoc",
    "content": "[[erlang_web]]\n== Erlang and the Web\n\nErlang is the ideal platform for writing Web applications.\nIts features are a perfect match for the requirements of\nmodern Web applications.\n\n=== The Web is concurrent\n\nWhen you access a website there is little concurrency\ninvolved. A few connections are opened and requests\nare sent through these connections. Then the web page\nis displayed on your screen. Your browser will only\nopen up to 4 or 8 connections to the server, depending\non your settings. This isn't much.\n\nBut think about it. You are not the only one accessing\nthe server at the same time. There can be hundreds, if\nnot thousands, if not millions of connections to the\nsame server at the same time.\n\nEven today a lot of systems used in production haven't\nsolved the C10K problem (ten thousand concurrent connections).\nAnd the ones who did are trying hard to get to the next\nstep, C100K, and are pretty far from it.\n\nErlang meanwhile has no problem handling millions of\nconnections. At the time of writing there are application\nservers written in Erlang that can handle more than two\nmillion connections on a single server in a real production\napplication, with spare memory and CPU!\n\nThe Web is concurrent, and Erlang is a language designed\nfor concurrency, so it is a perfect match.\n\nOf course, various platforms need to scale beyond a few\nmillion connections. This is where Erlang's built-in\ndistribution mechanisms come in. If one server isn't\nenough, add more! Erlang allows you to use the same code\nfor talking to local processes or to processes in other\nparts of your cluster, which means you can scale very\nquickly if the need arises.\n\nThe Web has large userbases, and the Erlang platform was\ndesigned to work in a distributed setting, so it is a\nperfect match.\n\nOr is it? Surely you can find solutions to handle that many\nconcurrent connections with your favorite language... But all\nthese solutions will break down in the next few years. Why?\nFirstly because servers don't get any more powerful, they\ninstead get a lot more cores and memory. This is only useful\nif your application can use them properly, and Erlang is\nlight-years ahead of anything else in this respect. Secondly,\ntoday your computer and your phone are online, tomorrow your\nwatch, goggles, bike, car, fridge and tons of other devices\nwill also connect to various applications on the Internet.\n\nOnly Erlang is prepared to deal with what's coming.\n\n=== The Web is soft real time\n\nWhat does soft real time mean, you ask? It means we want the\noperations done as quickly as possible, and in the case of\nweb applications, it means we want the data propagated fast.\n\nIn comparison, hard real time has a similar meaning, but also\nhas a hard time constraint, for example an operation needs to\nbe done in under N milliseconds otherwise the system fails\nentirely.\n\nUsers aren't that needy yet, they just want to get access\nto their content in a reasonable delay, and they want the\nactions they make to register at most a few seconds after\nthey submitted them, otherwise they'll start worrying about\nwhether it successfully went through.\n\nThe Web is soft real time because taking longer to perform an\noperation would be seen as bad quality of service.\n\nErlang is a soft real time system. It will always run\nprocesses fairly, a little at a time, switching to another\nprocess after a while and preventing a single process to\nsteal resources from all others. This means that Erlang\ncan guarantee stable low latency of operations.\n\nErlang provides the guarantees that the soft real time Web\nrequires.\n\n=== The Web is asynchronous\n\nLong ago, the Web was synchronous because HTTP was synchronous.\nYou fired a request, and then waited for a response. Not anymore.\nIt all began when XmlHttpRequest started being used. It allowed\nthe client to perform asynchronous calls to the server.\n\nThen Websocket appeared and allowed both the server and the client\nto send data to the other endpoint completely asynchronously. The\ndata is contained within frames and no response is necessary.\n\nErlang processes work the same. They send each other data contained\nwithin messages and then continue running without needing a response.\nThey tend to spend most of their time inactive, waiting for a new\nmessage, and the Erlang VM happily activate them when one is received.\n\nIt is therefore quite easy to imagine Erlang being good at receiving\nWebsocket frames, which may come in at unpredictable times, pass the\ndata to the responsible processes which are always ready waiting for\nnew messages, and perform the operations required by only activating\nthe required parts of the system.\n\nThe more recent Web technologies, like Websocket of course, but also\nHTTP/2.0, are all fully asynchronous protocols. The concept\nof requests and responses is retained of course, but anything could\nbe sent in between, by both the client or the browser, and the\nresponses could also be received in a completely different order.\n\nErlang is by nature asynchronous and really good at it thanks to the\ngreat engineering that has been done in the VM over the years. It's\nonly natural that it's so good at dealing with the asynchronous Web.\n\n=== The Web is omnipresent\n\nThe Web has taken a very important part of our lives. We're\nconnected at all times, when we're on our phone, using our computer,\npassing time using a tablet while in the bathroom... And this\nisn't going to slow down, every single device at home or on us\nwill be connected.\n\nAll these devices are always connected. And with the number of\nalternatives to give you access to the content you seek, users\ntend to not stick around when problems arise. Users today want\ntheir applications to be always available and if it's having\ntoo many issues they just move on.\n\nDespite this, when developers choose a product to use for building\nweb applications, their only concern seems to be \"Is it fast?\",\nand they look around for synthetic benchmarks showing which one\nis the fastest at sending \"Hello world\" with only a handful\nconcurrent connections. Web benchmarks haven't been representative\nof reality in a long time, and are drifting further away as\ntime goes on.\n\nWhat developers should really ask themselves is \"Can I service\nall my users with no interruption?\" and they'd find that they have\ntwo choices. They can either hope for the best, or they can use\nErlang.\n\nErlang is built for fault tolerance. When writing code in any other\nlanguage, you have to check all the return values and act accordingly\nto avoid any unforeseen issues. If you're lucky, you won't miss\nanything important. When writing Erlang code, you can just check\nthe success condition and ignore all errors. If an error happens,\nthe Erlang process crashes and is then restarted by a special\nprocess called a supervisor.\n\nErlang developers thus have no need to fear unhandled\nerrors, and can focus on handling only the errors that should\ngive some feedback to the user and let the system take care of\nthe rest. This also has the advantage of allowing them to write\na lot less code, and let them sleep at night.\n\nErlang's fault tolerance oriented design is the first piece of\nwhat makes it the best choice for the omnipresent, always available\nWeb.\n\nThe second piece is Erlang's built-in distribution. Distribution\nis a key part of building a fault tolerant system, because it\nallows you to handle bigger failures, like a whole server going\ndown, or even a data center entirely.\n\nFault tolerance and distribution are important today, and will be\nvital in the future of the Web. Erlang is ready.\n\n=== Learn Erlang\n\nIf you are new to Erlang, you may want to grab a book or\ntwo to get started. Those are my recommendations as the\nauthor of Cowboy.\n\n==== The Erlanger Playbook\n\nThe Erlanger Playbook is an ebook I am currently writing,\nwhich covers a number of different topics from code to\ndocumentation to testing Erlang applications. It also has\nan Erlang section where it covers directly the building\nblocks and patterns, rather than details like the syntax.\n\nYou can most likely read it as a complete beginner, but\nyou will need a companion book to make the most of it.\nBuy it from the https://ninenines.eu[Nine Nines website].\n\n==== Programming Erlang\n\nThis book is from one of the creator of Erlang, Joe\nArmstrong. It provides a very good explanation of what\nErlang is and why it is so. It serves as a very good\nintroduction to the language and platform.\n\nThe book is http://pragprog.com/book/jaerlang2/programming-erlang[Programming Erlang],\nand it also features a chapter on Cowboy.\n\n==== Learn You Some Erlang for Great Good!\n\nhttp://learnyousomeerlang.com[LYSE] is a much more complete\nbook covering many aspects of Erlang, while also providing\nstories and humor. Be warned: it's pretty verbose. It comes\nwith a free online version and a more refined paper and\nebook version.\n"
  },
  {
    "path": "doc/src/guide/flow_diagram.asciidoc",
    "content": "[[flow_diagram]]\n== Flow diagram\n\nCowboy is a lightweight HTTP server with support for HTTP/1.1,\nHTTP/2 and Websocket.\n\nIt is built on top of Ranch. Please see the Ranch guide for more\ninformation about how the network connections are handled.\n\n=== Overview\n\nimage::http_req_resp.png[HTTP request/response flowchart]\n\nAs you can see on the diagram, the client\nbegins by connecting to the server. This step is handled\nby a Ranch acceptor, which is a process dedicated to\naccepting new connections.\n\nAfter Ranch accepts a new connection, whether it is an\nHTTP/1.1 or HTTP/2 connection, Cowboy starts receiving\nrequests and handling them.\n\nIn HTTP/1.1 all requests come sequentially. In HTTP/2\nthe requests may arrive and be processed concurrently.\n\nWhen a request comes in, Cowboy creates a stream, which\nis a set of request/response and all the events associated\nwith them. The protocol code in Cowboy defers the handling\nof these streams to stream handler modules. When you\nconfigure Cowboy you may define one or more module that\nwill receive all events associated with a stream, including\nthe request, response, bodies, Erlang messages and more.\n\nBy default, Cowboy comes configured with a stream handler\ncalled `cowboy_stream_h`. This stream handler will create\na new process for every request coming in, and then\ncommunicate with this process to read the body or send\na response back. The request process executes middlewares.\nBy default, the request process executes the router and then\nthe handlers. Like stream handlers, middlewares may also be\ncustomized.\n\nA response may be sent at almost any point in this\ndiagram. If the response must be sent before the stream\nis initialized (because an error occurred early, for\nexample) then stream handlers receive a special event\nindicating this error.\n\n=== Protocol-specific headers\n\nCowboy takes care of protocol-specific headers and prevents\nyou from sending them manually. For HTTP/1.1 this includes\nthe `transfer-encoding` and `connection` headers. For HTTP/2\nthis includes the colon headers like `:status`.\n\nCowboy will also remove protocol-specific headers from\nrequests before passing them to stream handlers. Cowboy\ntries to hide the implementation details of all protocols\nas well as possible.\n\n=== Number of processes per connection\n\nBy default, Cowboy will use one process per connection,\nplus one process per set of request/response (called a\nstream, internally).\n\nThe reason it creates a new process for every request is due\nto the requirements of HTTP/2 where requests are executed\nconcurrently and independently from the connection. The\nframes from the different requests end up interleaved on\nthe single TCP connection.\n\nThe request processes are never reused. There is therefore\nno need to perform any cleanup after the response has been\nsent. The process will terminate and Erlang/OTP will reclaim\nall memory at once.\n\nCowboy ultimately does not require more than one process\nper connection. It is possible to interact with the connection\ndirectly from a stream handler, a low level interface to Cowboy.\nThey are executed from within the connection process, and can\nhandle the incoming requests and send responses. This is however\nnot recommended in normal circumstances, as a stream handler\ntaking too long to execute could have a negative impact on\nconcurrent requests or the state of the connection itself.\n\n=== Date header\n\nBecause querying for the current date and time can be expensive,\nCowboy generates one 'Date' header value every second, shares it\nto all other processes, which then simply copy it in the response.\nThis allows compliance with HTTP/1.1 with no actual performance loss.\n\n=== Binaries\n\nCowboy makes extensive use of binaries.\n\nBinaries are more efficient than lists for representing\nstrings because they take less memory space. Processing\nperformance can vary depending on the operation. Binaries\nare known for generally getting a great boost if the code\nis compiled natively. Please see the HiPE documentation\nfor more details.\n\nBinaries may end up being shared between processes. This\ncan lead to some large memory usage when one process keeps\nthe binary data around forever without freeing it. If you\nsee some weird memory usage in your application, this might\nbe the cause.\n"
  },
  {
    "path": "doc/src/guide/getting_started.asciidoc",
    "content": "[[getting_started]]\n== Getting started\n\nErlang is more than a language, it is also an operating system\nfor your applications. Erlang developers rarely write standalone\nmodules, they write libraries or applications, and then bundle\nthose into what is called a release. A release contains the\nErlang VM plus all applications required to run the node, so\nit can be pushed to production directly.\n\nThis chapter walks you through all the steps of setting up\nCowboy, writing your first application and generating your first\nrelease. At the end of this chapter you should know everything\nyou need to push your first Cowboy application to production.\n\n=== Prerequisites\n\nWe are going to use the https://github.com/ninenines/erlang.mk[Erlang.mk]\nbuild system. If you are using Windows, please check the\nhttp://erlang.mk/guide/installation.html[Installation instructions]\nto get your environment setup before you continue.\n\n=== Bootstrap\n\nFirst, let's create the directory for our application.\n\n[source,bash]\n$ mkdir hello_erlang\n$ cd hello_erlang\n\nThen we need to download Erlang.mk. Either use the following\ncommand or download it manually.\n\n[source,bash]\n$ wget https://erlang.mk/erlang.mk\n\nWe can now bootstrap our application. Since we are going to generate\na release, we will also bootstrap it at the same time.\n\n[source,bash]\n$ make -f erlang.mk bootstrap bootstrap-rel\n\nThis creates a Makefile, a base application, and the release files\nnecessary for creating the release. We can already build and start\nthis release.\n\n[source,bash]\n----\n$ make run\n...\n(hello_erlang@127.0.0.1)1>\n----\n\nEntering the command `i().` will show the running processes, including\none called `hello_erlang_sup`. This is the supervisor for our\napplication.\n\nThe release currently does nothing. In the rest of this chapter we\nwill add Cowboy as a dependency and write a simple \"Hello world!\"\nhandler.\n\n=== Cowboy setup\n\nWe will modify the 'Makefile' to tell the build system it needs to\nfetch and compile Cowboy, and that we will use releases:\n\n[source,makefile]\n----\nPROJECT = hello_erlang\n\nDEPS = cowboy\ndep_cowboy_commit = 2.14.2\n\nREL_DEPS = relx\n\nDEP_PLUGINS = cowboy\n\ninclude erlang.mk\n----\n\nThe `DEP_PLUGINS` line tells the build system to load the plugins\nCowboy provides. These include predefined templates that we will\nuse soon.\n\nThe `REL_DEPS` line tells the build system to fetch and build\n`relx`, the library that will create the release.\n\nIf you do `make run` now, Cowboy will be included in the release\nand started automatically. This is not enough however, as Cowboy\ndoesn't do anything by default. We still need to tell Cowboy to\nlisten for connections.\n\n=== Listening for connections\n\nFirst we define the routes that Cowboy will use to map requests\nto handler modules, and then we start the listener. This is best\ndone at application startup.\n\nOpen the 'src/hello_erlang_app.erl' file and add the necessary\ncode to the `start/2` function to make it look like this:\n\n[source,erlang]\n----\nstart(_Type, _Args) ->\n    Dispatch = cowboy_router:compile([\n        {'_', [{\"/\", hello_handler, []}]}\n    ]),\n    {ok, _} = cowboy:start_clear(my_http_listener,\n        [{port, 8080}],\n        #{env => #{dispatch => Dispatch}}\n    ),\n    hello_erlang_sup:start_link().\n----\n\nRoutes are explained in details in the xref:routing[Routing]\nchapter. For this tutorial we map the path `/` to the handler\nmodule `hello_handler`. This module doesn't exist yet.\n\nBuild and start the release, then open http://localhost:8080\nin your browser. You will get a 500 error because the module is missing.\nAny other URL, like http://localhost:8080/test, will result in a\n404 error.\n\n=== Handling requests\n\nCowboy features different kinds of handlers, including REST\nand Websocket handlers. For this tutorial we will use a plain\nHTTP handler.\n\nGenerate a handler from a template:\n\n[source,bash]\n$ make new t=cowboy.http n=hello_handler\n\nThen, open the 'src/hello_handler.erl' file and modify\nthe `init/2` function like this to send a reply.\n\n[source,erlang]\n----\ninit(Req0, State) ->\n    Req = cowboy_req:reply(200,\n        #{<<\"content-type\">> => <<\"text/plain\">>},\n        <<\"Hello Erlang!\">>,\n        Req0),\n    {ok, Req, State}.\n----\n\nWhat the above code does is send a 200 OK reply, with the\nContent-type header set to `text/plain` and the response\nbody set to `Hello Erlang!`.\n\nIf you run the release and open http://localhost:8080\nin your browser, you should get a nice `Hello Erlang!` displayed!\n"
  },
  {
    "path": "doc/src/guide/handlers.asciidoc",
    "content": "[[handlers]]\n== Handlers\n\nHandlers are Erlang modules that handle HTTP requests.\n\n=== Plain HTTP handlers\n\nThe most basic handler in Cowboy implements the mandatory\n`init/2` callback, manipulates the request, optionally\nsends a response and then returns.\n\nThis callback receives the xref:req[Req object] and the initial\nstate defined in the xref:routing[router configuration].\n\nA handler that does nothing would look like this:\n\n[source,erlang]\n----\ninit(Req, State) ->\n    {ok, Req, State}.\n----\n\nDespite sending no reply, a `204 No Content` response will be\nsent to the client, as Cowboy makes sure that a response is\nsent for every request.\n\nWe need to use the Req object to reply.\n\n[source,erlang]\n----\ninit(Req0, State) ->\n    Req = cowboy_req:reply(200, #{\n        <<\"content-type\">> => <<\"text/plain\">>\n    }, <<\"Hello World!\">>, Req0),\n    {ok, Req, State}.\n----\n\nCowboy will immediately send a response when `cowboy:reply/4`\nis called.\n\nWe then return a 3-tuple. `ok` means that the handler ran\nsuccessfully. We also give the modified Req back to Cowboy.\n\nThe last value of the tuple is a state that will be used\nin every subsequent callbacks to this handler. Plain HTTP\nhandlers only have one additional callback, the optional\nand rarely used `terminate/3`.\n\n=== Other handlers\n\nThe `init/2` callback can also be used to inform Cowboy\nthat this is a different kind of handler and that Cowboy\nshould switch to it. To do this you simply need to return\nthe module name of the handler type you want to switch to.\n\nCowboy comes with three handler types you can switch to:\nxref:rest_handlers[cowboy_rest], xref:ws_handlers[cowboy_websocket]\nand xref:loop_handlers[cowboy_loop]. In addition to those you\ncan define your own handler types.\n\nSwitching is simple. Instead of returning `ok`, you simply\nreturn the name of the handler type you want to use. The\nfollowing snippet switches to a Websocket handler:\n\n[source,erlang]\n----\ninit(Req, State) ->\n    {cowboy_websocket, Req, State}.\n----\n\n=== Cleaning up\n\nAll handler types provide the optional `terminate/3` callback.\n\n[source,erlang]\n----\nterminate(_Reason, _Req, _State) ->\n    ok.\n----\n\nThis callback is strictly reserved for any required cleanup.\nYou cannot send a response from this function. There is no\nother return value.\n\nThis callback is optional because it is rarely necessary.\nCleanup should be done in separate processes directly (by\nmonitoring the handler process to detect when it exits).\n\nCowboy does not reuse processes for different requests. The\nprocess will terminate soon after this call returns.\n"
  },
  {
    "path": "doc/src/guide/introduction.asciidoc",
    "content": "[[introduction]]\n== Introduction\n\nCowboy is a small, fast and modern HTTP server for Erlang/OTP.\n\nCowboy aims to provide a complete xref:modern_web[modern Web stack].\nThis includes HTTP/1.1, HTTP/2, Websocket, Server-Sent Events and\nWebmachine-based REST.\n\nCowboy comes with functions for introspection and tracing, enabling\ndevelopers to know precisely what is happening at any time. Its modular\ndesign also easily enable developers to add instrumentation.\n\nCowboy is a high quality project. It has a small code base, is very\nefficient (both in latency and memory use) and can easily be embedded\nin another application.\n\nCowboy is clean Erlang code. It includes hundreds of tests and its code\nis fully compliant with the Dialyzer. It is also well documented and\nfeatures a Function Reference, a User Guide and numerous Tutorials.\n\n=== Prerequisites\n\nBeginner Erlang knowledge is recommended for reading this guide.\n\nKnowledge of the HTTP protocol is recommended but not required, as it\nwill be detailed throughout the guide.\n\n=== Supported platforms\n\nCowboy is tested and supported on Linux, FreeBSD, Windows and OSX.\n\nCowboy has been reported to work on other platforms, but we make no\nguarantee that the experience will be safe and smooth. You are advised\nto perform the necessary testing and security audits prior to deploying\non other platforms.\n\nCowboy is developed for Erlang/OTP 24.0 and newer.\n\n=== License\n\nCowboy uses the ISC License.\n\n----\nCopyright (c) 2011-2025, Loïc Hoguin <essen@ninenines.eu>\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted, provided that the above\ncopyright notice and this permission notice appear in all copies.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\nWITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\nMERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\nANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\nWHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\nACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\nOR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n----\n\n=== Versioning\n\nCowboy uses http://semver.org/[Semantic Versioning 2.0.0].\n\n=== Conventions\n\nIn the HTTP protocol, the method name is case sensitive. All standard\nmethod names are uppercase.\n\nHeader names are case insensitive. When using HTTP/1.1, Cowboy converts\nall the request header names to lowercase. HTTP/2 requires clients to\nsend them as lowercase. Any other header name is expected to be provided\nlowercased, including when querying information about the request or\nwhen sending responses.\n\nThe same applies to any other case insensitive value.\n"
  },
  {
    "path": "doc/src/guide/listeners.asciidoc",
    "content": "[[listeners]]\n== Listeners\n\nA listener is a set of processes that listens on a port for\nnew connections. Incoming connections get handled by Cowboy.\nDepending on the connection handshake, one or another protocol\nmay be used.\n\nThis chapter is specific to Cowboy. Please refer to the\nhttps://ninenines.eu/docs/en/ranch/1.8/guide/listeners/[Ranch User Guide]\nfor more information about listeners.\n\nCowboy provides two types of listeners: one listening for\nclear TCP connections, and one listening for secure TLS\nconnections. Both of them support the HTTP/1.1 and HTTP/2\nprotocols.\n\n=== Clear TCP listener\n\nThe clear TCP listener will accept connections on the\ngiven port. A typical HTTP server would listen on port 80.\nPort 80 requires special permissions on most platforms\nhowever so a common alternative is port 8080.\n\nThe following snippet starts listening for connections\non port 8080:\n\n[source,erlang]\n----\nstart(_Type, _Args) ->\n    Dispatch = cowboy_router:compile([\n        {'_', [{\"/\", hello_handler, []}]}\n    ]),\n    {ok, _} = cowboy:start_clear(my_http_listener,\n        [{port, 8080}],\n        #{env => #{dispatch => Dispatch}}\n    ),\n    hello_erlang_sup:start_link().\n----\n\nThe xref:getting_started[Getting Started] chapter uses a\nclear TCP listener.\n\nClients connecting to Cowboy on the clear listener port are\nexpected to use either HTTP/1.1 or HTTP/2.\n\nCowboy supports both methods of initiating a clear\nHTTP/2 connection: through the Upgrade mechanism\n(https://tools.ietf.org/html/rfc7540#section-3.2[RFC 7540 3.2])\nor by sending the preface directly\n(https://tools.ietf.org/html/rfc7540#section-3.4[RFC 7540 3.4]).\n\nCompatibility with HTTP/1.0 is provided by Cowboy's HTTP/1.1\nimplementation.\n\n=== Secure TLS listener\n\nThe secure TLS listener will accept connections on the\ngiven port. A typical HTTPS server would listen on port 443.\nPort 443 requires special permissions on most platforms\nhowever so a common alternative is port 8443.\n\n// @todo Make a complete list of restrictions.\n\nThe function provided by Cowboy will ensure that the TLS\noptions given are following the HTTP/2 RFC with regards\nto security. For example some TLS extensions or ciphers\nmay be disabled. This also applies to HTTP/1.1 connections\non this listener. If this is not desirable, Ranch can be\nused directly to set up a custom listener.\n\n[source,erlang]\n----\nstart(_Type, _Args) ->\n    Dispatch = cowboy_router:compile([\n        {'_', [{\"/\", hello_handler, []}]}\n    ]),\n    {ok, _} = cowboy:start_tls(my_https_listener,\n        [\n            {port, 8443},\n            {certfile, \"/path/to/certfile\"},\n            {keyfile, \"/path/to/keyfile\"}\n        ],\n        #{env => #{dispatch => Dispatch}}\n    ),\n    hello_erlang_sup:start_link().\n----\n\nClients connecting to Cowboy on the secure listener are\nexpected to use the ALPN TLS extension to indicate what\nprotocols they understand. Cowboy always prefers HTTP/2\nover HTTP/1.1 when both are supported. When neither are\nsupported by the client, or when the ALPN extension was\nmissing, Cowboy expects HTTP/1.1 to be used.\n\nCowboy also advertises HTTP/2 support through the older\nNPN TLS extension for compatibility. Note however that\nthis support will likely not be enabled by default when\nCowboy 2.0 gets released.\n\nCompatibility with HTTP/1.0 is provided by Cowboy's HTTP/1.1\nimplementation.\n\n=== Stopping the listener\n\nWhen starting listeners along with the application it is\na good idea to also stop the listener when the application\nstops. This can be done by calling `cowboy:stop_listener/1`\nin the application's stop function:\n\n[source,erlang]\n----\nstop(_State) ->\n    ok = cowboy:stop_listener(my_http_listener).\n----\n\n=== Protocol configuration\n\nThe HTTP/1.1 and HTTP/2 protocols share the same semantics;\nonly their framing differs. The first is a text protocol and\nthe second a binary protocol.\n\nCowboy doesn't separate the configuration for HTTP/1.1 and\nHTTP/2. Everything goes into the same map. Many options are\nshared.\n\n// @todo Describe good to know options for both protocols?\n// Maybe do that in separate chapters?\n"
  },
  {
    "path": "doc/src/guide/loop_handlers.asciidoc",
    "content": "[[loop_handlers]]\n== Loop handlers\n\nLoop handlers are a special kind of HTTP handlers used when the\nresponse can not be sent right away. The handler enters instead\na receive loop waiting for the right message before it can send\na response.\n\nLoop handlers are used for requests where a response might not\nbe immediately available, but where you would like to keep the\nconnection open for a while in case the response arrives. The\nmost known example of such practice is known as long polling.\n\nLoop handlers can also be used for requests where a response is\npartially available and you need to stream the response body\nwhile the connection is open. The most known example of such\npractice is server-sent events, but it also applies to any\nresponse that takes a long time to send.\n\nWhile the same can be accomplished using plain HTTP handlers,\nit is recommended to use loop handlers because they are well-tested\nand allow using built-in features like hibernation and timeouts.\n\nLoop handlers essentially wait for one or more Erlang messages\nand feed these messages to the `info/3` callback. It also features\nthe `init/2` and `terminate/3` callbacks which work the same as\nfor plain HTTP handlers.\n\n=== Initialization\n\nThe `init/2` function must return a `cowboy_loop` tuple to enable\nloop handler behavior. This tuple may optionally contain\nthe atom `hibernate` to make the process enter hibernation\nuntil a message is received. Alternatively, the tuple may\noptionally contain a positive integer to create a `timeout`\nmessage when the process has not received messages for too\nlong.\n\nThis snippet enables the loop handler:\n\n[source,erlang]\n----\ninit(Req, State) ->\n    {cowboy_loop, Req, State}.\n----\n\nThis also makes the process hibernate:\n\n[source,erlang]\n----\ninit(Req, State) ->\n    {cowboy_loop, Req, State, hibernate}.\n----\n\nThis makes the process time out after 1000ms of idle time.\n\n[source,erlang]\n----\ninit(Req, State) ->\n    {cowboy_loop, Req, State, 1000}.\n----\n\n=== Receive loop\n\nOnce initialized, Cowboy will wait for messages to arrive\nin the process' mailbox. When a message arrives, Cowboy\ncalls the `info/3` function with the message, the Req object\nand the handler's state.\n\nThe following snippet sends a reply when it receives a\n`reply` message from another process, or waits for another\nmessage otherwise.\n\n[source,erlang]\n----\ninfo({reply, Body}, Req, State) ->\n    cowboy_req:reply(200, #{}, Body, Req),\n    {stop, Req, State};\ninfo(_Msg, Req, State) ->\n    {ok, Req, State, hibernate}.\n----\n\nDo note that the `reply` tuple here may be any message\nand is simply an example.\n\nThis callback may perform any necessary operation including\nsending all or parts of a reply, and will subsequently\nreturn a tuple indicating if more messages are to be expected.\n\nThe callback may also choose to do nothing at all and just\nskip the message received.\n\nIf a reply is sent, then the `stop` tuple should be returned.\nThis will instruct Cowboy to end the request.\n\nOtherwise an `ok` tuple should be returned.\n\n=== Streaming loop\n\nAnother common case well suited for loop handlers is\nstreaming data received in the form of Erlang messages.\nThis can be done by initiating a chunked reply in the\n`init/2` callback and then using `cowboy_req:chunk/2`\nevery time a message is received.\n\nThe following snippet does exactly that. As you can see\na chunk is sent every time an `event` message is received,\nand the loop is stopped by sending an `eof` message.\n\n[source,erlang]\n----\ninit(Req, State) ->\n    Req2 = cowboy_req:stream_reply(200, Req),\n    {cowboy_loop, Req2, State}.\n\ninfo(eof, Req, State) ->\n    {stop, Req, State};\ninfo({event, Data}, Req, State) ->\n    cowboy_req:stream_body(Data, nofin, Req),\n    {ok, Req, State};\ninfo(_Msg, Req, State) ->\n    {ok, Req, State}.\n----\n\n=== Cleaning up\n\nPlease refer to the xref:handlers[Handlers chapter]\nfor general instructions about cleaning up.\n\n=== Hibernate\n\nTo save memory, you may hibernate the process in between\nmessages received. This is done by returning the atom\n`hibernate` as part of the `loop` tuple callbacks normally\nreturn. Just add the atom at the end and Cowboy will hibernate\naccordingly.\n\n=== Idle timeout\n\nYou may activate timeout events by returning a positive integer\n`N` as part of the `loop` tuple callbacks return. The default\nvalue is `infinity`. The `info` callback will be called with the\natom `timeout` unless a message is received within `N` milliseconds:\n\n[source,erlang]\n----\ninfo(timeout, Req, State) ->\n    %% Do something...\n    {ok, Req, State, 1000}.\n----\n"
  },
  {
    "path": "doc/src/guide/middlewares.asciidoc",
    "content": "[[middlewares]]\n== Middlewares\n\nCowboy delegates the request processing to middleware components.\nBy default, two middlewares are defined, for the routing and handling\nof the request, as is detailed in most of this guide.\n\nMiddlewares give you complete control over how requests are to be\nprocessed. You can add your own middlewares to the mix or completely\nchange the chain of middlewares as needed.\n\nCowboy will execute all middlewares in the given order, unless one\nof them decides to stop processing.\n\n=== Usage\n\nMiddlewares only need to implement a single callback: `execute/2`.\nIt is defined in the `cowboy_middleware` behavior.\n\nThis callback has two arguments. The first is the `Req` object.\nThe second is the environment.\n\nMiddlewares can return one of three different values:\n\n* `{ok, Req, Env}` to continue the request processing\n* `{suspend, Module, Function, Args}` to hibernate\n* `{stop, Req}` to stop processing and move on to the next request\n\nOf note is that when hibernating, processing will resume on the given\nMFA, discarding all previous stacktrace. Make sure you keep the `Req`\nand `Env` in the arguments of this MFA for later use.\n\nIf an error happens during middleware processing, Cowboy will not try\nto send an error back to the socket, the process will just crash. It\nis up to the middleware to make sure that a reply is sent if something\ngoes wrong.\n\n=== Configuration\n\nThe middleware environment is defined as the `env` protocol option.\nIn the previous chapters we saw it briefly when we needed to pass\nthe routing information. It is a list of tuples with the first\nelement being an atom and the second any Erlang term.\n\nTwo values in the environment are reserved:\n\n* `listener` contains the name of the listener\n* `result` contains the result of the processing\n\nThe `listener` value is always defined. The `result` value can be\nset by any middleware. If set to anything other than `ok`, Cowboy\nwill not process any subsequent requests on this connection.\n\nThe middlewares that come with Cowboy may define or require other\nenvironment values to perform.\n\nYou can update the environment by calling the `cowboy:set_env/3`\nconvenience function, adding or replacing a value in the environment.\n\n=== Routing middleware\n\nThe routing middleware requires the `dispatch` value. If routing\nsucceeds, it will put the handler name and options in the `handler`\nand `handler_opts` values of the environment, respectively.\n\n=== Handler middleware\n\nThe handler middleware requires the `handler` and `handler_opts`\nvalues. It puts the result of the request handling into `result`.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_1.0.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 1.0 to 2.0\n\nA lot has changed between Cowboy 1.0 and 2.0. The `cowboy_req`\ninterface in particular has seen a massive revamp. Hooks are\ngone, their functionality can now be achieved via stream\nhandlers.\n\nThe documentation has seen great work, in particular the\nmanual. Each module and each function now has its own dedicated\nmanual page with full details and examples.\n\n=== Compatibility\n\nCompatibility with Erlang/OTP R16, 17 and 18 has been dropped.\nErlang/OTP 19.0 or above is required. It is non-trivial to\nmake Cowboy 2.0 work with older Erlang/OTP versions.\n\nCowboy 2.0 is not compatible with Cowlib versions older than\n2.0. It should be compatible with Ranch 1.0 or above, however\nit has not been tested with Ranch versions older than 1.4.\n\nCowboy 2.0 is tested on Arch Linux, Ubuntu, FreeBSD, Windows\nand OSX. It is tested with every point release (latest patch\nrelease) and also with HiPE on the most recent release.\n\nCowboy 2.0 now comes with Erlang.mk templates.\n\n=== Features added\n\n* The HTTP/2 protocol is now supported.\n\n* Cowboy no longer uses only one process per connection.\n  It now uses one process per connection plus one process\n  per request by default. This is necessary for HTTP/2.\n  There might be a slight drop in performance for HTTP/1.1\n  connections due to this change.\n\n* Cowboy internals have largely been reworked in order to\n  support HTTP/2. This opened the way to stream handlers,\n  which are a chain of modules that are called whenever\n  something happens relating to a request/response.\n\n* The `cowboy_stream_h` stream handler has been added.\n  It provides most of Cowboy's default behavior.\n\n* The `cowboy_compress_h` stream handler has been added.\n  It compresses responses when possible. It's worth noting\n  that it compresses in more cases than Cowboy 1.0 ever did.\n\n* Because of the many changes in the internals of Cowboy,\n  many options have been added or modified. Of note is that\n  the Websocket options are now given per handler rather\n  than for the entire listener.\n\n* Websocket permessage-deflate compression is now supported\n  via the `compress` option.\n\n* Static file handlers will now correctly find files found\n  in '.ez' archives.\n\n* Constraints have been generalized and are now used not only\n  in the router but also in some `cowboy_req` functions. Their\n  interface has also been modified to allow for reverse\n  operations and formatting of errors.\n\n=== Features removed\n\n* SPDY support has been removed. Use HTTP/2 instead.\n\n* Hooks have been removed. Use xref:streams[stream handlers] instead.\n\n* The undocumented `waiting_stream` hack has been removed.\n  It allowed disabling chunked transfer-encoding for HTTP/1.1.\n  It has no equivalent in Cowboy 2.0. Open a ticket if necessary.\n\n* Sub protocols still exist, but their interface has largely changed\n  and they are no longer documented for the time being.\n\n=== Changed behaviors\n\n* The handler behaviors have been renamed and are now `cowboy_handler`,\n  `cowboy_loop`, `cowboy_rest` and `cowboy_websocket`.\n\n* Plain HTTP, loop, REST and Websocket handlers have had their\n  init and terminate callbacks unified. They now all use the\n  `init/2` and `terminate/3` callbacks. The latter is now optional.\n  The terminate reason has now been documented for all handlers.\n\n* The tuple returned to switch to a different handler type has\n  changed. It now takes the form `{Module, Req, State}` or\n  `{Module, Req, State, Opts}`, where `Opts` is a map of options\n  to configure the handler. The timeout and hibernate options\n  must now be specified using this map, where applicable.\n\n* All behaviors that used to accept `halt` or `shutdown` tuples\n  as a return value no longer do so. The return value is now\n  a `stop` tuple, consistent across Cowboy.\n\n* Middlewares can no longer return an `error` tuple. They have\n  to send the response and return a `stop` tuple instead.\n\n* The `known_content_type` REST handler callback has been removed\n  as it was found unnecessary.\n\n* Websocket handlers have both the normal `init/2` and\n  an optional `websocket_init/1` function. The reason for\n  that exception is that the `websocket_*` callbacks execute\n  in a separate process from the `init/2` callback, and it\n  was therefore not obvious how timers or monitors should\n  be setup properly. They are effectively initializing the\n  handler before and after the HTTP/1.1 upgrade.\n\n* Websocket handlers can now send frames directly from\n  `websocket_init/1`. The frames will be sent immediately\n  after the handshake.\n\n* Websocket handler callbacks no longer receive the Req\n  argument. The `init/2` callback still receives it and\n  can be used to extract relevant information. The `terminate/3`\n  callback, if implemented, may still receive the Req\n  (see next bullet point).\n\n* Websocket handlers have a new `req_filter` option that\n  can be used to customize how much information should be\n  discarded from the Req object after the handshake. Note\n  that the Req object is only available in `terminate/3`\n  past that point.\n\n* Websocket handlers have their timeout default changed\n  from infinity to 60 seconds.\n\n=== New functions\n\n* The `cowboy_req:scheme/1` function has been added.\n\n* The `cowboy_req:uri/1,2` function has been added, replacing the\n  less powerful functions `cowboy_req:url/1` and `cowboy_req:host_url/1`.\n\n* The functions `cowboy_req:match_qs/2` and `cowboy_req:match_cookies/2`\n  allow matching query string and cookies against constraints.\n\n* The function `cowboy_req:set_resp_cookie/3` has been added to\n  complement `cowboy_req:set_resp_cookie/4`.\n\n* The functions `cowboy_req:resp_header/2,3` and `cowboy_req:resp_headers/1`\n  have been added. They can be used to retrieve response headers\n  that were previously set.\n\n* The function `cowboy_req:set_resp_headers/2` has been added. It\n  allows setting many response headers at once.\n\n* The functions `cowboy_req:push/3,4` can be used to push resources\n  for protocols that support it (by default only HTTP/2).\n\n=== Changed functions\n\n* The `cowboy:start_http/4` function was renamed to `cowboy:start_clear/3`.\n\n* The `cowboy:start_https/4` function was renamed to `cowboy:start_tls/3`.\n\n* Most, if not all, functions in the `cowboy_req` module have been modified.\n  Please consult the changelog of each individual functions. The changes\n  are mainly about simplifying and clarifying the interface. The Req is no\n  longer returned when not necessary, maps are used wherever possible,\n  and some functions have been renamed.\n\n* The position of the `Opts` argument for `cowboy_req:set_resp_cookie/4`\n  has changed to improve consistency. It is now the last argument.\n\n=== Removed functions\n\n* The functions `cowboy_req:url/1` and `cowboy_req:host_url/1` have been\n  removed in favor of the new function `cowboy_req:uri/1,2`.\n\n* The functions `cowboy_req:meta/2,3` and `cowboy_req:set_meta/3` have\n  been removed. The Req object is now a public map, therefore they became\n  unnecessary.\n\n* The functions `cowboy_req:set_resp_body_fun/2,3` have been removed.\n  For sending files, the function `cowboy_req:set_resp_body/2` can now\n  take a sendfile tuple.\n\n* Remove many undocumented functions from `cowboy_req`, including the\n  functions `cowboy_req:get/2` and `cowboy_req:set/3`.\n\n=== Other changes\n\n* The correct percent-decoding algorithm is now used for path elements\n  during routing. It will no longer decode `+` characters.\n\n* The router will now properly handle path segments `.` and `..`.\n\n* Routing behavior has changed for URIs containing latin1 characters.\n  They are no longer allowed. URIs are expected to be in UTF-8 once\n  they are percent-decoded.\n\n* Clients that send multiple headers of the same name\n  will have the values of those headers concatenated into a\n  comma-separated list. This is of special importance in the\n  case of the content-type header, as previously only the\n  first value was used in the `content_types_accepted/2` step\n  in REST handlers.\n\n* Etag comparison in REST handlers has been fixed. Some requests may\n  now fail when they succeeded in the past.\n\n* The `If-*-Since` headers are now ignored in REST handlers if\n  the corresponding `If*-Match` header exist. The former is\n  largely a backward compatible header and this shouldn't create\n  any issue. The new behavior follows the current RFCs more closely.\n\n* The static file handler has been improved to handle more special\n  characters on systems that accept them.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.0.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.0 to 2.1\n\nCowboy 2.1 focused on adding features that were temporarily\nremoved in Cowboy 2.0. A number of bugs found in the 2.0\nrelease were also fixed.\n\n=== Features added\n\n* It is now possible to obtain the client TLS certificate\n  and the local IP/port for the connection from the Req object.\n\n* Informational responses (1XX responses) can now be sent.\n  They must be sent before initiating the final response.\n\n* The `expect: 100-continue` header is now handled\n  automatically. The 100 response will be sent on the\n  first `cowboy_req:read_body/2,3,4` call. This only applies\n  when using the default `cowboy_stream_h` stream handler.\n\n=== Experimental features added\n\nExperimental features are previews of features that will be\nadded in a future release. They are not documented and their\ninterface may change at any time. You are welcome to try them\nand provide feedback.\n\n* The `cowboy_metrics_h` stream handler can be used to\n  extract metrics out of Cowboy. It must be used first in\n  the list of stream handlers, and will record all events\n  related to requests, responses and spawned processes.\n  When the stream terminates it will pass this information\n  to a user-defined callback.\n\n* The `cowboy_tracer_h` stream handler can be used to setup\n  automatic tracing of specific requests. You can conditionally\n  enable tracing based on a function, header, path or any other\n  element from the request and the trace will apply to the\n  entire connection and any processes created by it. This is\n  meant to be used for debugging both in tests and production.\n\n=== Changed behaviors\n\n* The `cowboy_rest` handler now implements a mechanism for\n  switching to a different type of handler from any callback\n  where `stop` is also allowed. Switch by returning\n  `{switch_handler, Module}` or `{switch_handler, Module, Opts}`.\n  This is especially useful for switching to `cowboy_loop`\n  for streaming the request or response body.\n\n* REST callbacks that do not allow `stop` as a return value\n  are now explicitly listed in the documentation.\n\n=== New functions\n\n* The function `cowboy_req:sock/1` returns the IP/port\n  of the local socket.\n\n* The function `cowboy_req:cert/1` returns the client\n  TLS certificate or `undefined` if it isn't available.\n\n* The function `cowboy_req:inform/2,3` sends an\n  informational response.\n\n=== Bugs fixed\n\n* Ensure HTTP/2 connections are not closed prematurely\n  when the user code does not read the request body.\n\n* Ensure HTTP/1.1 streams are not terminated too early.\n  Their behavior is now consistent with the HTTP/2 code\n  where the stream handler is only terminated when the\n  `stop` command is returned.\n\n* Sending zero-sized data from stream handlers or from\n  `cowboy_req:stream_body/3` could lead to issues with\n  HTTP/1.1. This has been fixed.\n\n* The final chunk sent by Cowboy when it terminates a\n  chunked body after the handler process exits was not\n  passed through stream handlers, which could lead to\n  issues when `cowboy_compress_h` was being used. This\n  is now corrected.\n\n* The stream handler state was discarded in some cases\n  where Cowboy had to send a response or response data\n  automatically when ending a stream. This has now\n  been corrected.\n\n* The stream handler callback `terminate/3` will now be\n  called when switching to another protocol using the\n  command `switch_protocol`. This doesn't apply when\n  doing upgrades to HTTP/2 as those occur before the\n  stream is initialized.\n\n* Cowlib has been updated to 2.0.1 to fix an issue with\n  Websocket compression when using Erlang/OTP 20.1. Note\n  that at the time of writing all 20.1 versions (from\n  20.1 to 20.1.4) have issues when compression is enabled.\n  It is expected to work properly from 20.1.5 onward. In\n  the meantime it is recommended to run the plain 20.1\n  release and disable Websocket compression, or use a\n  release before 20.1.\n\n* Cowboy will no longer crash when the `cowboy_clock`\n  process is not running. This can happen when Cowboy\n  is being restarted during upgrades, for example.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.1.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.1 to 2.2\n\nCowboy 2.2 focused on adding features required for writing\ngRPC servers and on completing test suites for the core\nHTTP RFCs, fixing many bugs along the way.\n\n=== Features added\n\n* Add support for sending trailers at the end of response bodies.\n  Trailers are additional header fields that may be sent after the\n  body to add more information to the response. Their usage is\n  required in gRPC servers. They are optional and may be discarded\n  in other scenarios (for example if the request goes through an\n  HTTP/1.0 proxy, as HTTP/1.0 does not support trailers).\n\n* The `max_skip_body_length` option was added to `cowboy_http`.\n  It controls how much of a request body Cowboy is willing to skip\n  when the handler did not touch it. If the remaining body size is\n  too large Cowboy instead closes the connection. It defaults to 1MB.\n\n* The CONNECT and TRACE methods are now rejected as they are\n  currently not implemented and must be handled differently than\n  other methods. They will be implemented in a future release.\n\n=== New functions\n\n* The function `stream_trailers/2` has been added. It terminates\n  a stream and adds trailer fields at the end of the response. A\n  corresponding stream handler command `{trailers, Trailers}`\n  has also been added.\n\n=== Bugs fixed\n\n* Test suites for the core HTTP RFCs RFC7230, RFC7231 and RFC7540\n  have been completed. Many of the bugs listed here were fixed as\n  a result of this work.\n\n* Many HTTP/2 edge cases when clients are misbehaving have been\n  corrected. This includes many cases where the request is malformed\n  (for example when a pseudo-header is present twice).\n\n* When the HTTP/2 SETTINGS_INITIAL_WINDOW_SIZE value changes,\n  Cowboy now properly updates the flow control windows.\n\n* HTTP/2 could mistakenly log stray messages that actually were\n  expected. This is no longer the case.\n\n* We no longer send a GOAWAY frame when the HTTP/2 preface is invalid.\n\n* Some values in the Req object of pushed requests were in the\n  wrong type. They are now the expected binary instead of iolist.\n\n* A response body was sometimes sent in response to HEAD requests\n  when using HTTP/2. The body is now ignored.\n\n* The `max_headers` option for `cowboy_http` was not always respected\n  depending on the contents of the buffer. The limit is now strict.\n\n* When an early error occurred on the HTTP/1.1 request line, the\n  partial Req given to stream handlers was missing the `ref` and\n  `peer` information. This has been corrected.\n\n* Absolute URIs with a userinfo component, or without an authority\n  component, are now properly rejected for HTTP/1.0 and HTTP/1.1.\n\n* Whitespace is no longer allowed in header lines before the colon.\n\n* 408 responses to HTTP/1.1 requests now properly include a\n  connection: close header indicating that we are going to\n  close the connection. This header will also be sent for\n  other early errors resulting in the closing of the connection.\n\n* When both the transfer-encoding and content-length headers are\n  sent in an HTTP/1.1 request, the transfer-encoding now takes\n  precedence over the content-length header and the latter is\n  removed from the Req object.\n\n* A 400 response is now returned when the transfer-encoding\n  header is invalid or contains any transfer-coding other\n  than chunked.\n\n* Invalid chunk sizes are now rejected immediately.\n\n* Chunk extensions are now limited to 129 characters. They are\n  not used in practice and are still ignored by Cowboy. The limit\n  is not configurable.\n\n* The final chunk was mistakenly sent in responses to HEAD\n  requests. This is now corrected.\n\n* `OPTIONS *` requests were broken in Cowboy 2.0. They are now\n  working again. Both the routing and `cowboy_req:uri/1,2` have\n  been corrected.\n\n* 204 responses no longer include a content-length header.\n\n* A packet could be lost when switching to Websocket or any\n  other protocol via the `switch_protocol` command. This is\n  now fixed.\n\n* A 426 response will now be sent when a handler requires\n  the client to upgrade to Websocket and the request did not\n  include the required headers.\n\n* Both experimental stream handlers `cowboy_metrics_h` and\n  `cowboy_tracer_h` received a number of fixes and improvements.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.10.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.10 to 2.11\n\nCowboy 2.11 contains a variety of new features and bug\nfixes. Nearly all previously experimental features are\nnow marked as stable, including Websocket over HTTP/2.\nIncluded is a fix for an HTTP/2 protocol CVE.\n\nCowboy 2.11 requires Erlang/OTP 24.0 or greater.\n\nCowboy is now using GitHub Actions for CI. The main reason\nfor the move is to reduce costs by no longer having to\nself-host CI runners. The downside is that GitHub runners\nare less reliable and timing dependent tests are now more\nlikely to fail.\n\n=== Features added\n\n* A new HTTP/2 option `max_cancel_stream_rate` has been added\n  to control the rate of stream cancellation the server will\n  accept. By default Cowboy will accept 500 cancelled streams\n  every 10 seconds.\n\n* A new stream handler `cowboy_decompress_h` has been added.\n  It allows automatically decompressing incoming gzipped\n  request bodies. It includes options to protect against\n  zip bombs.\n\n* Websocket over HTTP/2 is no longer considered experimental.\n  Note that the `enable_connect_protocol` option must be set\n  to `true` in order to use Websocket over HTTP/2 for the\n  time being.\n\n* Automatic mode for reading request bodies has been\n  documented. In automatic mode, Cowboy waits indefinitely\n  for data and sends a `request_body` message when data\n  comes in. It mirrors `{active, once}` socket modes.\n  This is ideal for loop handlers and is also used\n  internally for HTTP/2 Websocket.\n\n* Ranged requests support is no longer considered\n  experimental. It was added in 2.6 to both `cowboy_static`\n  and `cowboy_rest`. Ranged responses can be produced\n  either automatically (for the `bytes` unit) or manually.\n  REST flowcharts have been updated with the new callbacks\n  and steps related to handling ranged requests.\n\n* A new HTTP/1.1 and HTTP/2 option `reset_idle_timeout_on_send`\n  has been added. When enabled, the `idle_timeout` will be\n  reset every time Cowboy sends data to the socket.\n\n* Loop handlers may now return a timeout value in the place\n  of `hibernate`. Timeouts behave the same as in `gen_server`.\n\n* The `generate_etag` callback of REST handlers now accepts\n  `undefined` as a return value to allow conditionally\n  generating etags.\n\n* The `cowboy_compress_h` options `compress_threshold` and\n  `compress_buffering` are no longer considered experimental.\n  They were de facto stable since 2.6 as they already were\n  documented.\n\n* Functions `cowboy:get_env/2,3` have been added.\n\n* Better error messages have been added when trying to send\n  a 204 or 304 response with a body; when attempting to\n  send two responses to a single request; when trying to\n  push a response after the final response; when trying\n  to send a `set-cookie` header without using\n  `cowboy_req:set_resp_cookie/3,4`.\n\n=== Features removed\n\n* Cowboy will no longer include the NPN extension when\n  starting a TLS listener. This extension has long been\n  deprecated and replaced with the ALPN extension. Cowboy\n  will continue using the ALPN extension for protocol\n  negotiation.\n\n=== Bugs fixed\n\n* A fix was made to address the HTTP/2 CVE CVE-2023-44487\n  via the new HTTP/2 option `max_cancel_stream_rate`.\n\n* HTTP/1.1 requests that contain both a content-length and\n  a transfer-encoding header will now be rejected to avoid\n  security risks. Previous behavior was to ignore the\n  content-length header as recommended by the HTTP RFC.\n\n* HTTP/1.1 connections would sometimes use the wrong timeout\n  value to determine whether the connection should be closed.\n  This resulted in connections staying up longer than\n  intended. This should no longer be the case.\n\n* Cowboy now reacts to socket errors immediately for HTTP/1.1\n  and HTTP/2 when possible. Cowboy will notice when connections\n  have been closed properly earlier than before. This also\n  means that the socket option `send_timeout_close` will work\n  as expected.\n\n* Shutting down HTTP/1.1 pipelined requests could lead to\n  the current request being terminated before the response\n  has been sent. This has been addressed.\n\n* When using HTTP/1.1 an invalid Connection header will now\n  be rejected with a 400 status code instead of crashing.\n\n* The documentation now recommends increasing the HTTP/2\n  option `max_frame_size_received`. Cowboy currently uses\n  the protocol default but will increase its default in a\n  future release. Until then users are recommended to set\n  the option to ensure larger requests are accepted and\n  processed with acceptable performance.\n\n* Cowboy could sometimes send HTTP/2 WINDOW_UPDATE frames\n  twice in a row. Now they should be consolidated.\n\n* Cowboy would sometimes send HTTP/2 WINDOW_UPDATE frames\n  for streams that have stopped internally. This should\n  no longer be the case.\n\n* The `cowboy_compress_h` stream handler will no longer\n  attempt to compress responses that have an `etag` header\n  to avoid caching issues.\n\n* The `cowboy_compress_h` will now always add `accept-encoding`\n  to the `vary` header as it indicates that responses may\n  be compressed.\n\n* Cowboy will now remove the `trap_exit` process flag when\n  HTTP/1.1 connections upgrade to Websocket.\n\n* Exit gracefully instead of crashing when the socket gets\n  closed when reading the PROXY header.\n\n* Missing `cowboy_stream` manual pages have been added.\n\n* A number of fixes were made to documentation and examples.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.11.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.11 to 2.12\n\nCowboy 2.12 contains a small security improvement for\nthe HTTP/2 protocol.\n\nCowboy 2.12 requires Erlang/OTP 24.0 or greater.\n\n=== Features added\n\n* A new HTTP/2 option `max_fragmented_header_block_size` has\n  been added to limit the size of header blocks that are\n  sent over multiple HEADERS and CONTINUATION frames.\n\n* Update Cowlib to 2.13.0.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.12.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.12 to 2.13\n\nCowboy 2.13 focuses on improving the performance of\nWebsocket, as well as the HTTP protocols. It also\ncontains a variety of new features and bug fixes.\nIn addition, Cowboy 2.13 is the first Cowboy version\nthat contains the experimental HTTP/3 support.\n\nCowboy 2.13 requires Erlang/OTP 24.0 or greater.\n\n=== Features added\n\n* The option `dynamic_buffer` has been added. When\n  enabled, Cowboy will dynamically change the\n  `buffer` socket option based on how much data\n  it receives. It will start at 1024 bytes and\n  go up to 131072 bytes by default. This applies\n  to HTTP/1.1, HTTP/2 and Websocket. The performance\n  gains are very important depending on the scenario.\n\n* HTTP/1.1 and HTTP/2 now accept the `hibernate`\n  option. When set the connection process will\n  automatically hibernate to reduce memory usage\n  at a small performance cost.\n\n* The `protocols` and `alpn_default_protocol` protocol\n  options have been added to control exactly which\n  HTTP protocols are allowed over clear and TLS listeners.\n\n* The Websocket `max_frame_size` option can now be\n  set dynamically via the `set_options` command.\n  This allows configuring a smaller max size and\n  increase it after authentication or other checks.\n\n* `cowboy_req:set_resp_headers` now accept lists of\n  headers. This can be used to simplify passing\n  headers coming from client applications such as\n  Gun. Note that the set-cookie header cannot be\n  provided using this function.\n\n* `cowboy_rest` now always sets the allow header.\n\n* Update Ranch to 1.8.1.\n\n* Update Cowlib to 2.14.0.\n\n* When using Hex.pm, version check requirements will\n  now be relaxed. Cowboy will accept any Ranch version\n  from 1.8.0 to 2.2.0 as well as future 2.x versions.\n  Similarly, any Cowlib 2.x version from 2.14.0 will\n  be accepted.\n\n=== Experimental features added\n\n* Experimental support for HTTP/3 has been added,\n  including Websocket over HTTP/3. HTTP/3 support\n  is disabled by default; to enable, the environment\n  variable COWBOY_QUICER must be set at compile-time.\n\n=== Features deprecated\n\n* The `inactivity_timeout` option is now deprecated\n  for all protocols. It is de facto ignored when\n  `hibernate` is enabled.\n\n=== Optimisation-related changes\n\n* The behavior of the `idle_timeout` timer has been\n  changed for HTTP/2 and Websocket. Cowboy used to\n  reset the timer on every data packet received from\n  the socket. Now Cowboy will check periodically\n  whether new data was received in the interval.\n\n* URI and query string hex encoding and decoding has\n  been optimised.\n\n* Websocket UTF-8 validation of text frames has been\n  optimised.\n\n* Websocket unmasking has been optimised.\n\n=== Bugs fixed\n\n* HTTP/1.1 upgrade to HTTP/2 is now disabled over TLS,\n  as HTTP/2 over TLS must be negotiated via ALPN.\n\n* `cowboy_req:filter_cookies` could miss valid cookies.\n  It has been corrected.\n\n* HTTP/1.1 could get to a state where it would stop\n  receiving data from the socket, or buffer the data\n  without processing it, and the connection eventually\n  time out. This has been fixed.\n\n* Websocket did not compress zero-length frames properly.\n  This resulted in decompression errors in the client.\n  This has been corrected.\n\n* Websocket compression will now be disabled when only\n  the server sets `client_max_window_bits`, as otherwise\n  decompression errors will occur.\n\n* Websocket will now apply `max_frame_size` both to\n  compressed frames as well as the uncompressed payload.\n  Cowboy will stop decompressing when the limit is\n  reached.\n\n* Cowboy now properly handles exits of request processes\n  that occurred externally (e.g. via `exit/2`).\n\n* Invalid return values from `content_types_provided`\n  could result in an atom sent to the socket, leading\n  to a cryptic error message. The invalid value will\n  now result in a better error message.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.13.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.13 to 2.14\n\nCowboy 2.14 adds experimental support for HTTP/3\nWebTransport based on the most recent draft. It\nalso has a new data delivery mechanism for HTTP/2\nand HTTP/3 Websocket, providing better performance.\n\nCowboy 2.14 requires Erlang/OTP 24.0 or greater.\n\n=== Features added\n\n* The `relay` data delivery mechanism has been\n  added to HTTP/2 and HTTP/3 protocols. Using\n  this mechanism lets the Websocket protocol\n  bypass stream handlers to forward data from\n  the connection process to the Websocket\n  session process, as well as better manage\n  HTTP/2's flow control. This results in a\n  noticeable performance improvement. This\n  new mechanism can be used by all sub-protocols\n  built on top of HTTP/2 or HTTP/3 such as\n  Websocket or the upcoming HTTP/2 WebTransport.\n\n* The `last_modified` callback of REST handlers\n  now accepts `undefined` as a return value to\n  allow conditionally providing a timestamp.\n\n=== Experimental features added\n\n* Experimental support for HTTP/3 WebTransport\n  has been added, based on the most recent RFC\n  drafts. The implementation should also be\n  compatible with earlier drafts that are\n  currently in use by some browsers. Both\n  HTTP/3 and HTTP/3 WebTransport are disabled\n  by default; to enable, the environment\n  variable COWBOY_QUICER must be set at\n  compile-time, and a number of options must\n  be provided at run time, including\n  `enable_connect_protocol`, `h3_datagram`,\n  `wt_max_sessions` and for earlier drafts\n  `enable_webtransport`. The test suite is\n  the best place to get started at this time.\n\n=== Optimisation-related changes\n\n* The `dynamic_buffer` option introduced in\n  the previous release has been tweaked to\n  start at 512 bytes and have its value\n  changed less abruptly. This is based on\n  additional work done implementing the same\n  feature in RabbitMQ.\n\n* The static file handler will now use `raw`\n  mode to read file information to avoid a\n  bottleneck when querying the file server.\n\n=== Bugs fixed\n\n* It was possible for Websocket to fail to\n  enable active mode again after it had been\n  disabled. This has been fixed.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.14.asciidoc",
    "content": "[appendix]\n== Changes since Cowboy 2.14\n\nThe following patch versions were released since Cowboy 2.14:\n\n=== Cowboy 2.14.2\n\nCowboy compiled without `COWBOY_QUICER` set would\nhave a number of Dialyzer errors. Now in that\nscenario the HTTP/3 code is fully behind ifdefs\nand Dialyzer no longer complains.\n\nNow when `COWBOY_QUICER` isn't set:\n\n * `cowboy:start_quic/3` is no longer defined.\n * `cowboy_http3` compiles to an empty module.\n * `cowboy_quicer` compiles to an empty module.\n\n=== Cowboy 2.14.1\n\nHTTP/2 Websocket did not call `terminate/3` on abrupt\nsocket close (without a close frame being sent first).\nThis is now fixed. Do note however that the Websocket\nsession process must trap exits to call `terminate/3`.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.2.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.2 to 2.3\n\nCowboy 2.3 focused on making the Cowboy processes behave\nproperly according to OTP principles. This version is a\nvery good milestone toward that goal and most of everything\nshould now work. Release upgrades and a few details will\nbe improved in future versions.\n\n=== Features added\n\n* Add support for all functions from the module `sys`. Note\n  that Cowboy currently does not implement the `sys` debugging\n  mechanisms as tracing is recommended instead.\n\n* Add a `max_frame_size` option for Websocket handlers\n  to close the connection when the client attempts to\n  send a frame that's too large. It currently defaults\n  to `infinity` to avoid breaking existing code but will\n  be changed in a future version.\n\n* Update Cowlib to 2.2.1.\n\n* Add support for the 308 status code and a test suite\n  for RFC7538 where it is defined.\n\n=== Bugs fixed\n\n* Ensure timeout options accept the value `infinity` as\n  documented.\n\n* Properly reject HTTP/2 requests with an invalid content-length\n  header instead of simply crashing.\n\n* When switching from HTTP/1.1 to Websocket or user protocols\n  all the messages in the mailbox were flushed. Only messages\n  specific to `cowboy_http` should now be flushed.\n\n* Parsing of the x-forwarded-for header has been corrected.\n  It now supports IPv6 addresses both with and without port.\n\n* Websocket subprotocol tokens are now parsed in a case\n  insensitive manner, according to the spec.\n\n* Cookies without values are now allowed. For example `Cookie: foo`.\n\n* Colons are now allowed within path segments in routes provided\n  to `cowboy_router:compile/1` as long as they are not the first\n  character of the path segment.\n\n* The `cowboy_req:delete_resp_header/2` function will no longer\n  crash when no response header was set before calling it.\n\n* A miscount of the output HTTP/2 flow control window has been\n  fixed. It prevented sending the response body fully to some\n  clients. The issue only affected response bodies sent as iolists.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.3.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.3 to 2.4\n\nCowboy 2.4 focused on improving the HTTP/2 implementation.\nAll existing tests from RFC7540 and the h2spec test suite\nnow all pass. Numerous options have been added to control\nSETTINGS and related behavior. In addition experimental\nsupport for Websocket over HTTP/2 was added.\n\n=== Features added\n\n* Add experimental support for Websocket over HTTP/2.\n  You can use the `enable_connect_protocol` option to\n  enable. It implements the following draft:\n  https://tools.ietf.org/html/draft-ietf-httpbis-h2-websockets-01\n\n* Add options `max_decode_table_size` and\n  `max_encode_table_size` to restrict the size of the\n  HPACK compression dictionary.\n\n* Add option `max_concurrent_streams` to restrict the\n  number of HTTP/2 streams that can be opened concurrently.\n\n* Add options `initial_connection_window_size` and\n  `initial_stream_window_size` to restrict the size of\n  the HTTP/2 request body buffers for the whole connection\n  and per stream, respectively.\n\n* Add options `max_frame_size_received` and\n  `max_frame_size_sent` to restrict the size of\n  HTTP/2 frames.\n\n* Add option `settings_timeout` to reject clients that\n  did not send a SETTINGS ack. Note that this currently\n  may only occur at the beginning of the connection.\n\n* Update Ranch to 1.5.0\n\n* Update Cowlib to 2.3.0\n\n=== Bugs fixed\n\n* Fix the END_STREAM flag for informational responses\n  when using HTTP/2.\n\n* Receive and ignore HTTP/2 request trailers if any\n  for HTTP/2 requests. Request trailer information will\n  be propagated to the user code in a future release.\n\n* Reject WINDOW_UPDATE frames that are sent after the\n  client sent an RST_STREAM. Note that Cowboy will not\n  keep state information about terminated streams\n  forever and so the behavior might differ depending\n  on when the stream was reset.\n\n* Reject streams that depend on themselves. Note that\n  Cowboy currently does not implement HTTP/2's priority\n  mechanisms so this issue was harmless.\n\n* Reject HTTP/2 requests where the body size is different\n  than the content-length value. Note that due to how Cowboy\n  works some requests might go through regardless, for\n  example when the user code does not read the request body.\n\n* Fix all existing test failures from RFC7540. This was\n  mostly incorrect test cases or intermittent failures.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.4.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.4 to 2.5\n\nCowboy 2.5 focused on making the test suites pass. A\nvariety of new features, fixes and improvements have\nalso been worked on.\n\n=== Features added\n\n* Add option `linger_timeout` to control how long\n  Cowboy will wait before closing the socket when\n  shutting down the connection. This helps avoid\n  the TCP reset problem HTTP/1.1 suffers from. The\n  default is now 1000 ms.\n\n* It is now possible to stream a response body\n  without using chunked transfer-encoding when the\n  protocol is HTTP/1.1. To enable this behavior,\n  simply pass the content-length header with the\n  expected size when initiating the streamed response.\n\n* Update Ranch to 1.6.2\n\n* Update Cowlib to 2.6.0\n\n=== Experimental features added\n\n* Websocket handlers now feature a commands-based interface.\n  The return value from the callbacks can now take the form\n  `{Commands, State}` where `Commands` can be frames to be\n  sent or commands yet to be introduced. New commands will\n  be available only through this new interface.\n\n* Add the `{active, boolean()}` Websocket handler command.\n  It allows disabling reading from the socket when `false`\n  is returned. `true` reenables reading from the socket.\n\n* Add the protocol option `logger` that allows configuring\n  which logger module will be used. The logger module must\n  follow the interface of the new `logger` module in Erlang/OTP 21,\n  or be set to `error_logger` to keep the old behavior. A\n  similar transport option exists in Ranch 1.6; both options\n  are necessary to override Cowboy's default behavior completely.\n\n* Add the `{log, Level, Format, Args}` stream handler command.\n  Making it a command rather than a direct call will simplify\n  silencing particular log messages.\n\n=== New functions\n\n* The function `cowboy_req:stream_events/3` streams one or more\n  text/event-stream events, encoding them automatically.\n\n* The functions `cowboy_req:read_and_match_urlencoded_body/2,3`\n  can be used to read, parse and match application/x-www-form-urlencoded\n  request bodies, in a similar way to `cowboy_req:match_qs/2`.\n\n=== Bugs fixed\n\n* Fix Erlang/OTP 21 warnings.\n\n* Ensure that the port number is always defined in the\n  Req object. When it is not provided in the request,\n  the default port number for the protocol being used\n  will be set.\n\n* Ensure stream handlers can run after `cowboy_stream_h`.\n\n* Honor the SETTINGS_ENABLE_PUSH HTTP/2 setting: don't\n  send PUSH frames to clients that disabled it.\n\n* Fix HTTP/2 `settings_timeout` option when the value\n  is set to `infinity`.\n\n* HTTP/1.1 responses will no longer include a trailer header\n  when the request had no te header.\n\n* HTTP/1.1 204 responses no longer send the transfer-encoding\n  header when `cowboy_req:stream_reply/2,3` is used to send\n  a response.\n\n* Improve HTTP/1.1 keepalive handling to avoid processing\n  requests that follow the final request that will receive\n  a response.\n\n* Improve the validation of HTTP/1.1 absolute-form requests.\n\n* When the `switch_protocol` is used after a response was\n  sent, Cowboy will no longer attempt to send the 101 informational\n  response for the protocol upgrade. This caused a crash of the\n  connection previously.\n\n* Errors that occur when a callback returned by\n  `content_types_provided` does not exist have been improved.\n\n* Prevent annoying error logs when using sendfile in\n  Erlang/OTP 20 and lower.\n\n* Add missing frame types to `websocket_handle`.\n\n* A test suite has been added for RFC8297 to ensure that\n  103 informational responses can be sent.\n\n* Numerous test cases have been fixed, improved or removed in order\n  to make the test suites pass. Most of the failures were caused\n  by broken tests.\n\n* Some misguiding or incorrect statements in the documentation\n  have been removed or clarified.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.5.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.5 to 2.6\n\nCowboy 2.6 greatly refactored the HTTP/2 code, a large\npart of which was moved to Cowlib and is now used by\nboth the Cowboy server and the Gun client.\n\nA large number of tickets were also closed which\nresulted in many bugs fixed and many features and\noptions added, although some of them are still\nexperimental.\n\n=== Features added\n\n* Add support for the PROXY protocol header.\n  It can be enabled via the `proxy_header` option.\n  The proxy information can then be found under\n  the `proxy_info` key in the Req object.\n\n* Allow using sendfile tuples in `cowboy_req:stream_body/3`\n  and in the data command in stream handlers. The only\n  caveat is that when using `cowboy_compress_h` the\n  sendfile tuples may have to be converted to in-memory\n  data in order to compress them. This is the case for\n  gzip compression.\n\n* The stream handlers `cowboy_stream_h` and\n  `cowboy_compress_h` are now documented.\n\n* Add the `chunked` option to allow disabling chunked\n  transfer-encoding for HTTP/1.1 connections.\n\n* Add the `http10_keepalive` option to allow disabling\n  keep-alive for HTTP/1.0 connections.\n\n* Add the `idle_timeout` option for HTTP/2.\n\n* Add the `sendfile` option to both HTTP/1.1 and HTTP/2.\n  It allows disabling the sendfile syscall entirely for\n  all connections. It is recommended to disable sendfile\n  when using VirtualBox shared folders.\n\n* Add the `rate_limited/2` callback to REST handlers.\n\n* Add the `deflate_opts` option to Websocket handlers that\n  allows configuring deflate options for the\n  permessage-deflate extension.\n\n* Add the `charset` option to `cowboy_static`.\n\n* Add support for the SameSite cookie attribute.\n\n* Update Ranch to 1.7.0\n\n* Update Cowlib to 2.7.0\n\n=== Experimental features added\n\n* Add support for range requests (RFC7233) in REST handlers.\n  This adds two new callbacks: `ranges_accepted/2` and\n  `range_satisfiable/2` along with the user-specified\n  `ProvideRangeCallback/2`.\n\n* Add automatic handling of range requests to REST handlers\n  that return the callback `auto` from `ranges_accepted/2`.\n  Cowboy will call the configured `ProvideCallback` and\n  then split the output automatically for the ranged response.\n\n* Enable range requests support in `cowboy_static`.\n\n* Add the `{deflate, boolean()}` Websocket handler\n  command to disable permessage-deflate compression\n  temporarily.\n\n* Add the `compress_threshold` option which allows\n  configuring how much data must be present in a\n  response body to compress it. This only applies\n  to non-streamed bodies at this time.\n\n* Add the `compress_buffering` option which allows\n  controlling whether some buffering may be done\n  when streaming a response body. Change the default\n  behavior to not buffer to make sure it works by\n  default in all scenarios.\n\n* Add the `{set_options, map()}` command to stream\n  handlers and Websocket handlers. This can be used\n  to update options on a per-request basis. Allow\n  overriding the `idle_timeout` option for both\n  HTTP/1.1 and Websocket, the `cowboy_compress_h`\n  options for HTTP/1.1 and HTTP/2 and the `chunked`\n  option for HTTP/1.1.\n\n=== Bugs fixed\n\n* Do not send a content-length automatically with\n  304 responses. This status code allows a content-length\n  that corresponds to what would have been sent for a 200\n  response, but is never followed by a body.\n\n* HTTP/2 streams are now terminated once the body\n  has been sent fully, instead of immediately once\n  the stop command is returned (by default when the\n  request process exits). Metrics will therefore\n  more accurately represent when a stream ended.\n\n* Terminate connection processes gracefully when the\n  parent process exists or when sys:terminate/2,3\n  is called.\n\n* Automatically ignore the boundary parameter of multipart\n  media types when using REST handlers. This is a special\n  parameter that may change with all requests and cannot\n  be predicted.\n\n* Fix parsing of the accept header when it contains charset\n  parameters. They are case insensitive and will now be\n  lowercased, like for accept-charset and content-type.\n\n* Handle the charset parameter using `charsets_provided`\n  when it is present in the accept header when using\n  REST handlers.\n\n* Don't select charsets when the q-value is 0 in REST\n  handlers.\n\n* Handle accept-charset headers that include a wildcard\n  in REST handlers.\n\n* Only send a charset header when the content-type\n  negotiated is of type text in REST handlers.\n\n* Remove the default charset iso-8859-1 from REST\n  handlers when no other is provided. This has been\n  removed from the HTTP specifications for a long time.\n\n* Many cases where a content-type header was sent\n  unnecessarily in the REST handlers response have\n  been fixed.\n\n* Handle error_response commands in `cowboy_metrics_h`.\n\n* A number of types and function specifications were\n  fixed or improved. Dialyzer is now run against both\n  the code and tests to help uncover issues.\n\n* An undefined `cowboy_router` behavior has been\n  documented.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.6.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.6 to 2.7\n\nCowboy 2.7 improves the HTTP/2 code with optimizations\naround the sending of DATA and WINDOW_UPDATE frames;\ngraceful shutdown of the connection when the client is\ngoing away; and rate limiting mechanisms. New options\nand mechanisms have also been added to control the\namount of memory Cowboy ends up using with both HTTP/1.1\nand HTTP/2. Much, but not all, of this work was done\nto address HTTP/2 CVEs about potential denial of service.\n\nIn addition, many of the experimental features introduced\nin previous releases have been marked stable and are now\ndocumented.\n\nCowboy 2.7 requires Erlang/OTP 20.0 or greater.\n\n=== Features added\n\n* Cowboy is now compatible with both Ranch 1.7 and the\n  upcoming Ranch 2.0.\n\n* The number of HTTP/2 WINDOW_UPDATE frames Cowboy sends\n  has been greatly reduced. Cowboy now applies heuristics\n  to determine whether it is necessary to update the window,\n  based on the current window size and the amount of data\n  requested by streams (the `cowboy_req:read_body/2` length\n  for example). Six new options have been added to control\n  this behavior: `connection_window_margin_size`,\n  `connection_window_update_threshold`,\n  `max_connection_window_size`, `max_stream_window_size`,\n  `stream_window_margin_size` and\n  `stream_window_update_threshold`.\n\n* HTTP/2 connections will now be shut down gracefully\n  when receiving a GOAWAY frame. Cowboy will simply\n  wait for existing streams to finish before closing\n  the connection.\n\n* Functions that stream the response body now have\n  backpressure applied. They now wait for a message\n  to be sent back. The message will be held off when\n  using HTTP/2 and the buffer sizes exceed either\n  `max_connection_buffer_size` or `max_stream_buffer_size`.\n  For HTTP/1.1 the data is sent synchronously and we\n  rely instead on the TCP backpressure.\n\n* A new HTTP/2 option `stream_window_data_threshold`\n  can be used to control how little the DATA frames that\n  Cowboy sends can get. By default Cowboy will wait for\n  the window to be large enough to send either everything\n  queued or to reach the default maximum frame size of\n  16384 bytes.\n\n* A new HTTP/2 option `max_receive_frame_rate` can be\n  used to control how fast the server is willing to receive\n  frames. By default it will accept 1000 frames every 10\n  seconds.\n\n* A new HTTP/2 option `max_reset_stream_rate` can be\n  used to control the rate of errors the server is\n  willing to accept. By default it will accept 10\n  stream resets every 10 seconds.\n\n* Flow control for incoming data has been implemented\n  for HTTP/1.1. Cowboy will now wait for the user code\n  to ask for the request body before reading it from\n  the socket. The option `initial_stream_flow_size`\n  controls how much data Cowboy will read without\n  being asked.\n\n* The HTTP/1.1 and HTTP/2 option `logger` is now\n  documented.\n\n* The Websocket option `validate_utf8` has been\n  added. It can be used to disable the expensive UTF-8\n  validation for incoming text and close frames.\n\n* The experimental commands based Websocket interface\n  is now considered stable and has been documented.\n  The old interface is now deprecated.\n\n* A new Websocket handler command `shutdown_reason`\n  can be used to change the normal exit reason of\n  Websocket processes. By default `normal` is used;\n  with this command the exit reason can be changed\n  to `{shutdown, ShutdownReason}`.\n\n* The experimental stream handlers `cowboy_metrics_h`\n  and `cowboy_tracer_h` are now considered stable and\n  have been documented.\n\n* The stream handler commands `set_options` and `log`\n  are now considered stable and have been documented.\n\n* The router is now capable of retrieving dispatch\n  rules directly from the `persistent_term` storage\n  (available starting from Erlang/OTP 21.2).\n\n* Support for the status codes 208 and 508 has been\n  added.\n\n* Update Ranch to 1.7.1.\n\n* Update Cowlib to 2.8.0.\n\n=== Experimental features added\n\n* It is now possible to read the response body from any\n  process, as well as doing any other `cowboy_req`\n  operations. Since this is not recommended due to\n  race condition concerns this feature will always\n  remain experimental.\n\n=== New functions\n\n* The function `cowboy_req:filter_cookies/2` has been\n  added. It can be called before parsing/matching\n  cookies in order to filter out undesirables. The\n  main reason for doing this is to avoid most parse\n  errors that may occur when dealing with Web browsers\n  (which have a string-based Javascript interface to\n  cookies that is very permissive of invalid content)\n  and to be able to recover in other cases.\n\n* The function `cowboy_req:cast/2` has been added.\n  It can be used to send events to stream handlers.\n\n=== Bugs fixed\n\n* A number of fixes and additions were made to address the\n  HTTP/2 CVEs CVE-2019-9511 through CVE-2019-9518, except\n  for CVE-2019-9513 which required no intervention as the\n  relevant protocol feature is not implemented by Cowboy.\n\n* The HTTP/2 connection window could become larger than the\n  protocol allows, leading to errors. This has been corrected.\n\n* The presence of empty header names in HTTP/2 requests now\n  results in the request to be rejected.\n\n* Cowboy will now remove headers specific to HTTP/1.1\n  (the hop by hop headers such as connection or upgrade)\n  when building an HTTP/2 response.\n\n* A bug in the HTTP/2 code that resulted in the failure to\n  fully send iolist response bodies has been fixed. Cowboy\n  would just wait indefinitely in those cases.\n\n* It was possible for a final empty HTTP/2 DATA frame to get\n  stuck and never sent when the window reached 0 and the remote\n  end did not increase the window anymore. This has been\n  corrected.\n\n* Cowboy now uses the host header when the HTTP/2\n  :authority pseudo header is missing. A common scenario\n  where this occurs is when proxies translate incoming\n  HTTP/1.1 requests to HTTP/2.\n\n* HTTP/1.1 connections are now properly closed when the\n  user code sends less data than advertised in the response\n  headers.\n\n* Cowboy will now close HTTP/1.1 connections immediately when\n  a header line is missing a colon separator. Previously it\n  was waiting for more data.\n\n* It was possible for Cowboy to receive stray timeout messages\n  for HTTP/1.1 connections, resulting in crashes. The timeout\n  handling in HTTP/1.1 has been reworked and the issue should\n  no longer occur.\n\n* The type for the Req object has been updated to accept\n  custom fields as was already documented.\n\n* The authentication scheme returned when parsing the\n  authorization header is now case insensitive, which\n  means it will be returned as lowercase.\n\n* Cowboy no longer discards data that follows a Websocket\n  upgrade request. Note that the protocol does not allow\n  sending data before receiving a successful Websocket\n  upgrade response, so this fix is more out of principle\n  rather than to fix a real world issue.\n\n* The `cowboy_static` handler will now properly detect\n  the type of files that have an uppercase or mixed\n  extension component.\n\n* The `cowboy_static` handler is now consistent across all\n  supported platforms. It now explicitly rejects `path_info`\n  components that include a forward slash, backward slash\n  or NUL character.\n\n* The update to Ranch 1.7.1 fixes an issue with the PROXY\n  protocol that would cause checksum verification to fail.\n\n* The HTTP/1.1 error reason for `stream_error` mistakenly\n  contained an extra element. It has now been removed.\n\n* The `PartialReq` given to the `early_error` stream handler\n  callback now includes headers when the protocol is HTTP/2.\n\n* A bug where the stacktrace was incorrect in error messages\n  has been fixed. The problem occurred when an exception\n  occurred in the handler's terminate callback.\n\n* The REST flowchart for POST, PATCH and PUT has received\n  a number of fixes and had to be greatly reworked as a\n  result. When the method is PUT, we do not check for\n  the location header in the response. When the resource\n  doesn't exist and the method was PUT the flowchart was\n  largely incorrect. A 415 response may occur after the\n  `content_types_accepted` callback and was missing from\n  the flowchart.\n\n* The documentation for `content_types_accepted` now\n  includes the media type wildcard that was previously\n  missing.\n\n* The documentation for a type found in `cow_cookie`\n  was missing. A manual page for `cow_cookie` was added\n  and can be found in the Cowlib documentation.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.7.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.7 to 2.8\n\nCowboy 2.8 contains many optimizations for all\nprotocols. HTTP/1.1 has received the largest\nimprovements and Cowboy will now be able to\nhandle noticeably more requests. Thanks to\nthe folks at Stressgrid for helping identify that\nthe performance was lower than it should have been\nand for benchmarking my many changes and experiments.\n\nCowboy 2.8 also contains a small number of tweaks\nand bug fixes. Cowboy 2.8 is the first Cowboy release,\never, to be consistently green on all tested platforms.\nThis is mostly due to the reworking of some test cases,\nbut a few bugs were discovered and fixed in the process.\n\nCowboy 2.8 requires Erlang/OTP 22.0 or greater. It may\nalso work out of the box with Erlang/OTP 21.3 but this\nwas not tested and is not supported.\n\n=== Features added\n\n* Cowboy will now use `active,N` instead of `active,once`\n  to receive data from the socket. This greatly improves\n  the performance and allows Cowboy to process more\n  requests, especially for HTTP/1.1. The `active_n`\n  protocol option can be configured to change the\n  `active,N` value. The default is 100 for all protocols.\n\n* Add a `linger_timeout` option for HTTP/2. The default\n  is 1000, or one second. This helps ensure that the\n  final GOAWAY frame will be properly received by clients.\n\n* The function `cowboy_req:parse_header/2,3` will now\n  parse the headers `access-control-request-headers`,\n  `access-control-request-method`, `content-encoding`,\n  `content-language`, `max-forwards`, `origin`,\n  `proxy-authorization` and `trailer`.\n\n* A Performance chapter has been added to the guide.\n  More content will be added in future releases.\n\n* Update Cowlib to 2.9.1.\n\n=== Experimental features added\n\n* A `protocols` protocol option allows configuring which\n  protocol will be used for clear listeners. Setting it\n  to `[http2]` will disable HTTP/1.1 entirely. This feature\n  will be extended in a future release.\n\n=== Features modified\n\n* The default value for HTTP/1.1's `max_keepalive` option\n  has been increased. It now allows 1000 requests before\n  gracefully closing the connection.\n\n* The default value for HTTP/2's `max_received_frame_rate`\n  option has been increased. It now allows 10000 frames every\n  10 seconds.\n\n* Cowboy will now accept whitespace in cookie names. This\n  is in line with the recommended parsing algorithm for the\n  upcoming cookie RFC update, and corresponds to what browsers\n  are doing.\n\n=== Bugs fixed\n\n* The number of Transport:send/2 calls has been optimized\n  for HTTP/2. Reducing the number of calls has a noticeable\n  impact on the number of requests that can be processed.\n\n* Trying to use `cowboy_req:reply/4` with a status code of\n  204 or 304 and a non-empty response body will now result\n  in a crash. Using `cowboy_req:stream_reply/2,3` with 204\n  or 304 and then attempting to send a body will also result\n  in a crash. These status codes disallow response bodies\n  and trying to send one will break HTTP/1.1 framing.\n\n* A crash has been fixed related to HTTP/1.1 pipelining.\n  The bug was most likely introduced in Cowboy 2.6 when\n  flow control was added for HTTP/1.1 request bodies.\n\n* The HTTP/1.1 protocol code could get stuck because of flow\n  control. This has been corrected.\n\n* A crash has been fixed for HTTP/1.1. It occurred when\n  a flow control update was requested (such as reading\n  the request body) after the body was fully read.\n\n* The timeout was incorrectly reset sometimes when a stream\n  (a pair of request/response) terminated. This has been\n  corrected.\n\n* Handling of hibernation for Websocket has been improved.\n  Websocket over HTTP/2 now supports hibernating. Stray\n  messages no longer cancel hibernation.\n\n* The `cowboy_compress_h` stream handler will now ignore\n  malformed accept-encoding headers instead of crashing.\n\n* The manual pages for `cowboy:start_clear(3)` and\n  `cowboy:start_tls(3)` now mentions that some protocol\n  options may be documented in the releevant stream\n  handler.\n\n* The manual page for `cowboy_req:parse_header(3)` was\n  corrected. When an unsupported header is given the\n  function crashes, it does not return an `undefined` tuple.\n\n* The routing algorithm description in the user guide has\n  been improved.\n\n* The test suites are now consistently green on all tested\n  platforms. Most of the test failures were caused by flaky\n  tests. Avoiding the use of timeouts fixed most of them.\n  A small number of tests had to be reworked.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.8.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.8 to 2.9\n\nCowboy 2.9 implements graceful shutdown of connection\nprocesses for both HTTP/1.1 and HTTP/2 connections.\n\nCowboy 2.9 is the first release to support the much\nawaited Erlang/OTP 24 out of the box. While users that\nwere using Ranch 2.0 already were ready for OTP 24,\nthe Ranch version used by Cowboy out of the box was\nnot compatible and had to be updated.\n\nCowboy 2.9 also contains a small number of tweaks\nand bug fixes.\n\nCowboy 2.9 requires Erlang/OTP 22.0 or greater.\n\n=== Features added\n\n* Cowboy will now gracefully shutdown HTTP/1.1 and HTTP/2\n  connections when the supervisor asks the connection\n  process to exit, or when `sys:terminate/2,3` is used.\n  Two new configuration options were added for HTTP/2\n  to determine the timeouts for the graceful shutdown\n  steps.\n\n* REST handler `AcceptCallback` can now return `{created, URI}`\n  or `{see_other, URI}` to determine what response status code\n  should be sent (typically to differentiate between a new\n  resource and an update). The return value `{true, URI}` is\n  now deprecated.\n\n* Update Ranch to 1.8.0.\n\n* Update Cowlib to 2.11.0.\n\n=== Bugs fixed\n\n* Fix concurrent body streaming getting stuck with HTTP/2.\n  The alarm could get into blocking state indefinitely\n  when two or more request processes were streaming bodies.\n\n* Fix HTTP/2 rate limiting using the wrong default values\n  in some cases.\n\n* Don't produce an error report when the request process\n  exited normally (`normal` or `shutdown` exit reasons).\n\n* Fix `cowboy_tracer_h` to support trace messages without\n  timestamps.\n"
  },
  {
    "path": "doc/src/guide/migrating_from_2.9.asciidoc",
    "content": "[appendix]\n== Migrating from Cowboy 2.9 to 2.10\n\nCowboy 2.10 is a maintenance release adding support\nfor Erlang/OTP 26. The main change is a Cowlib update\nto fix a compilation error that only occurs starting\nfrom OTP 26.\n\nCowboy 2.10 requires Erlang/OTP 22.0 or greater.\n\n=== Features added\n\n* Add support for `Default` value of SameSite\n  cookie attribute.\n\n* Add support for the `stale-*` cache-control directives\n  from RFC 5861.\n\n* Update Cowlib to 2.12.1.\n\n=== Bugs fixed\n\n* Fix a compilation error in Cowlib when using Erlang/OTP 26.\n\n* Fix data sent after RST_STREAM in HTTP/2 in rare cases.\n\n* Fix parsing of RST_STREAM frames to properly handle\n  frames that have a valid length but were not fully\n  received yet.\n\n* Remove the obsolete `Version` cookie attribute.\n\n* Handle more edge cases for cookie parsing based on updates\n  to the RFC 6265bis draft.\n\n* Make Basic auth parsing ignore unknown authentication\n  parameters and generally update the code to conform\n  to RFC 7617.\n\n* Fix URI template reserved expansion of %-encoded.\n\n* Update structured headers implementation to RFC 8941.\n"
  },
  {
    "path": "doc/src/guide/modern_web.asciidoc",
    "content": "[[modern_web]]\n== The modern Web\n\nCowboy is a server for the modern Web. This chapter explains\nwhat it means and details all the standards involved.\n\nCowboy supports all the standards listed in this document.\n\n=== HTTP/2\n\nHTTP/2 is the most efficient protocol for consuming Web\nservices. It enables clients to keep a connection open\nfor long periods of time; to send requests concurrently;\nto reduce the size of requests through HTTP headers\ncompression; and more. The protocol is binary, greatly\nreducing the resources needed to parse it.\n\nHTTP/2 also enables the server to push messages to the\nclient. This can be used for various purposes, including\nthe sending of related resources before the client requests\nthem, in an effort to reduce latency. This can also be used\nto enable bidirectional communication.\n\nCowboy provides transparent support for HTTP/2. Clients\nthat know it can use it; others fall back to HTTP/1.1\nautomatically.\n\nHTTP/2 is compatible with the HTTP/1.1 semantics.\n\nHTTP/2 is defined by RFC 7540 and RFC 7541.\n\n=== HTTP/1.1\n\nHTTP/1.1 is the previous version of the HTTP protocol.\nThe protocol itself is text-based and suffers from numerous\nissues and limitations. In particular it is not possible\nto execute requests concurrently (though pipelining is\nsometimes possible), and it's also sometimes difficult\nto detect that a client disconnected.\n\nHTTP/1.1 does provide very good semantics for interacting\nwith Web services. It defines the standard methods, headers\nand status codes used by HTTP/1.1 and HTTP/2 clients and\nservers.\n\nHTTP/1.1 also defines compatibility with an older version\nof the protocol, HTTP/1.0, which was never really standardized\nacross implementations.\n\nThe core of HTTP/1.1 is defined by RFC 7230, RFC 7231,\nRFC 7232, RFC 7233, RFC 7234 and RFC 7235. Numerous RFCs\nand other specifications exist defining additional HTTP\nmethods, status codes, headers or semantics.\n\n=== Websocket\n\nxref:ws_protocol[Websocket] is a protocol built on top of HTTP/1.1\nthat provides a two-ways communication channel between the client and\nthe server. Communication is asynchronous and can occur concurrently.\n\nIt consists of a Javascript object allowing setting up a\nWebsocket connection to the server, and a binary based\nprotocol for sending data to the server or the client.\n\nWebsocket connections can transfer either UTF-8 encoded text\ndata or binary data. The protocol also includes support for\nimplementing a ping/pong mechanism, allowing the server and\nthe client to have more confidence that the connection is still\nalive.\n\nA Websocket connection can be used to transfer any kind of data,\nsmall or big, text or binary. Because of this Websocket is\nsometimes used for communication between systems.\n\nWebsocket messages have no semantics on their own. Websocket\nis closer to TCP in that aspect, and requires you to design\nand implement your own protocol on top of it; or adapt an\nexisting protocol to Websocket.\n\nCowboy provides an interface known as xref:ws_handlers[Websocket handlers]\nthat gives complete control over a Websocket connection.\n\nThe Websocket protocol is defined by RFC 6455.\n\n=== Long-lived requests\n\nCowboy provides an interface that can be used to support\nlong-polling or to stream large amounts of data reliably,\nincluding using Server-Sent Events.\n\nLong-polling is a mechanism in which the client performs\na request which may not be immediately answered by the\nserver. It allows clients to request resources that may\nnot currently exist, but are expected to be created soon,\nand which will be returned as soon as they are.\n\nLong-polling is essentially a hack, but it is widely used\nto overcome limitations on older clients and servers.\n\nServer-Sent Events is a small protocol defined as a media\ntype, `text/event-stream`, along with a new HTTP header,\n`Last-Event-ID`. It is defined in the EventSource W3C\nspecification.\n\nCowboy provides an interface known as xref:loop_handlers[loop handlers]\nthat facilitates the implementation of long-polling or stream\nmechanisms. It works regardless of the underlying protocol.\n\n=== REST\n\nxref:rest_principles[REST, or REpresentational State Transfer],\nis a style of architecture for loosely connected distributed\nsystems. It can easily be implemented on top of HTTP.\n\nREST is essentially a set of constraints to be followed.\nMany of these constraints are purely architectural and\nsolved by simply using HTTP. Some constraints must be\nexplicitly followed by the developer.\n\nCowboy provides an interface known as xref:rest_handlers[REST handlers]\nthat simplifies the implementation of a REST API on top of\nthe HTTP protocol.\n"
  },
  {
    "path": "doc/src/guide/multipart.asciidoc",
    "content": "[[multipart]]\n== Multipart requests\n\nMultipart originates from MIME, an Internet standard that\nextends the format of emails.\n\nA multipart message is a list of parts. A part contains\nheaders and a body. The body of the parts may be\nof any media type, and contain text or binary data.\nIt is possible for parts to contain a multipart media\ntype.\n\nIn the context of HTTP, multipart is most often used\nwith the `multipart/form-data` media type. It is what\nbrowsers use to upload files through HTML forms.\n\nThe `multipart/byteranges` is also common. It is the\nmedia type used to send arbitrary bytes from a resource,\nenabling clients to resume downloads.\n\n=== Form-data\n\nIn the normal case, when a form is submitted, the\nbrowser will use the `application/x-www-form-urlencoded`\ncontent-type. This type is just a list of keys and\nvalues and is therefore not fit for uploading files.\n\nThat's where the `multipart/form-data` content-type\ncomes in. When the form is configured to use this\ncontent-type, the browser will create a multipart\nmessage where each part corresponds to a field on\nthe form. For files, it also adds some metadata in\nthe part headers, like the file name.\n\nA form with a text input, a file input and a select\nchoice box will result in a multipart message with\nthree parts, one for each field.\n\nThe browser does its best to determine the media type\nof the files it sends this way, but you should not\nrely on it for determining the contents of the file.\nProper investigation of the contents is recommended.\n\n=== Checking for multipart messages\n\nThe content-type header indicates the presence of\na multipart message:\n\n[source,erlang]\n----\n{<<\"multipart\">>, <<\"form-data\">>, _}\n    = cowboy_req:parse_header(<<\"content-type\">>, Req).\n----\n\n=== Reading a multipart message\n\nCowboy provides two sets of functions for reading\nrequest bodies as multipart messages.\n\nThe `cowboy_req:read_part/1,2` functions return the\nnext part's headers, if any.\n\nThe `cowboy_req:read_part_body/1,2` functions return\nthe current part's body. For large bodies you may\nneed to call the function multiple times.\n\nTo read a multipart message you need to iterate over\nall its parts:\n\n[source,erlang]\n----\nmultipart(Req0) ->\n    case cowboy_req:read_part(Req0) of\n        {ok, _Headers, Req1} ->\n            {ok, _Body, Req} = cowboy_req:read_part_body(Req1),\n            multipart(Req);\n        {done, Req} ->\n            Req\n    end.\n----\n\nWhen part bodies are too large, Cowboy will return\na `more` tuple, and allow you to loop until the part\nbody has been fully read.\n\nThe function `cow_multipart:form_data/1` can be used\nto quickly obtain information about a part from a\n`multipart/form-data` message. The function returns\na `data` or a `file` tuple depending on whether this\nis a normal field or a file being uploaded.\n\nThe following snippet will use this function and\nuse different strategies depending on whether the\npart is a file:\n\n[source,erlang]\n----\nmultipart(Req0) ->\n    case cowboy_req:read_part(Req0) of\n        {ok, Headers, Req1} ->\n            Req = case cow_multipart:form_data(Headers) of\n                {data, _FieldName} ->\n                    {ok, _Body, Req2} = cowboy_req:read_part_body(Req1),\n                    Req2;\n                {file, _FieldName, _Filename, _CType} ->\n                    stream_file(Req1)\n            end,\n            multipart(Req);\n        {done, Req} ->\n            Req\n    end.\n\nstream_file(Req0) ->\n    case cowboy_req:read_part_body(Req0) of\n        {ok, _LastBodyChunk, Req} ->\n            Req;\n        {more, _BodyChunk, Req} ->\n            stream_file(Req)\n    end.\n----\n\nBoth the part header and body reading functions can take\noptions that will be given to the request body reading\nfunctions. By default, `cowboy_req:read_part/1` reads\nup to 64KB for up to 5 seconds. `cowboy_req:read_part_body/1`\nhas the same defaults as `cowboy_req:read_body/1`.\n\nTo change the defaults for part headers:\n\n[source,erlang]\ncowboy_req:read_part(Req, #{length => 128000}).\n\nAnd for part bodies:\n\n[source,erlang]\ncowboy_req:read_part_body(Req, #{length => 1000000, period => 7000}).\n\n=== Skipping unwanted parts\n\nPart bodies do not have to be read. Cowboy will automatically\nskip it when you request the next part's body.\n\nThe following snippet reads all part headers and skips\nall bodies:\n\n[source,erlang]\n----\nmultipart(Req0) ->\n    case cowboy_req:read_part(Req0) of\n        {ok, _Headers, Req} ->\n            multipart(Req);\n        {done, Req} ->\n            Req\n    end.\n----\n\nSimilarly, if you start reading the body and it ends up\nbeing too big, you can simply continue with the next part.\nCowboy will automatically skip what remains.\n\nWhile Cowboy can skip part bodies automatically, the read\nrate is not configurable. Depending on your application\nyou may want to skip manually, in particular if you observe\npoor performance while skipping.\n\nYou do not have to read all parts either. You can stop\nreading as soon as you find the data you need.\n\n// @todo Cover the building of multipart messages.\n"
  },
  {
    "path": "doc/src/guide/performance.asciidoc",
    "content": "[[performance]]\n== Performance\n\nThis chapter describes the performance characteristics\nof Cowboy and offers suggestions to get the most\nperformance out of your application.\n\n=== One process per connection\n\nThe first version of Cowboy featured a single process\nper connection, whereas the current version of Cowboy\nfeatures one process per connection plus one process\nper request. This has a negative impact on performance,\nbut is necessary in order to provide a common interface\nfor both HTTP/1.1 and HTTP/2 (as well as future HTTP\nversions).\n\nIt is still possible to use a single process per\nconnection, and avoid the creation of additional\nprocesses for each request, by implementing a\nstream handler to process the requests. This can\nbe done for all requests, or just for a single\nendpoint depending on the application's needs.\n\nStream handlers provide an asynchronous interface\nand must not block, so the implementation will\nbe very different from normal Cowboy handlers,\nbut the performance gains are important enough\nto justify it in some cases.\n"
  },
  {
    "path": "doc/src/guide/req.asciidoc",
    "content": "[[req]]\n== The Req object\n\nThe Req object is a variable used for obtaining information\nabout a request, read its body or send a response.\n\nIt is not really an object in the object-oriented sense.\nIt is a simple map that can be directly accessed or\nused when calling functions from the `cowboy_req` module.\n\nThe Req object is the subject of a few different chapters.\nIn this chapter we will learn about the Req object and\nlook at how to retrieve information about the request.\n\n=== Direct access\n\nThe Req map contains a number of fields which are documented\nand can be accessed directly. They are the fields that have\na direct mapping to HTTP: the request `method`; the HTTP\n`version` used; the effective URI components `scheme`,\n`host`, `port`, `path` and `qs`; the request `headers`;\nthe connection `peer` address and port; and the TLS\ncertificate `cert` when applicable.\n\nNote that the `version` field can be used to determine\nwhether a connection is using HTTP/2.\n\nTo access a field, you can simply match in the function\nhead. The following example sends a simple \"Hello world!\"\nresponse when the `method` is GET, and a 405 error\notherwise.\n\n[source,erlang]\n----\ninit(Req0=#{method := <<\"GET\">>}, State) ->\n    Req = cowboy_req:reply(200, #{\n        <<\"content-type\">> => <<\"text/plain\">>\n    }, <<\"Hello world!\">>, Req0),\n    {ok, Req, State};\ninit(Req0, State) ->\n    Req = cowboy_req:reply(405, #{\n        <<\"allow\">> => <<\"GET\">>\n    }, Req0),\n    {ok, Req, State}.\n----\n\nAny other field is internal and should not be accessed.\nThey may change in future releases, including maintenance\nreleases, without notice.\n\nModifying the Req object is allowed, but extra caution\nmust be used when modifying existing fields. You can\nadd as many new fields as necessary, however. Just make\nsure to namespace the field names so that no conflict\ncan occur with future Cowboy updates or with third party\nprojects.\n\n=== Introduction to the cowboy_req interface\n\n// @todo Link to cowboy_req manual\n\nFunctions in the `cowboy_req` module provide access to\nthe request information but also various operations that\nare common when dealing with HTTP requests.\n\nAll the functions that begin with a verb indicate an action.\nOther functions simply return the corresponding value\n(sometimes that value does need to be built, but the\ncost of the operation is equivalent to retrieving a value).\n\nSome of the `cowboy_req` functions return an updated Req\nobject. They are the read, reply, set and delete functions.\nWhile ignoring the returned Req will not cause incorrect\nbehavior for some of them, it is highly recommended to\nalways keep and use the last returned Req object. The\nmanual for `cowboy_req` details these functions and what\nmodifications are done to the Req object.\n\nSome of the calls to `cowboy_req` have side effects. This\nis the case of the read and reply functions. Cowboy reads\nthe request body or replies immediately when the function\nis called.\n\nAll functions will crash if something goes wrong. There\nis usually no need to catch these errors, Cowboy will\nsend the appropriate 4xx or 5xx response depending on\nwhere the crash occurred.\n\n=== Request method\n\nThe request method can be retrieved directly:\n\n[source, erlang]\n#{method := Method} = Req.\n\nOr using a function:\n\n[source,erlang]\nMethod = cowboy_req:method(Req).\n\nThe method is a case sensitive binary string. Standard\nmethods include GET, HEAD, OPTIONS, PATCH, POST, PUT\nor DELETE.\n\n=== HTTP version\n\nThe HTTP version is informational. It does not indicate that\nthe client implements the protocol well or fully.\n\nThere is typically no need to change behavior based on the\nHTTP version: Cowboy already does it for you.\n\nIt can be useful in some cases, though. For example, one may\nwant to redirect HTTP/1.1 clients to use Websocket, while HTTP/2\nclients keep using HTTP/2.\n\nThe HTTP version can be retrieved directly:\n\n[source,erlang]\n#{version := Version} = Req.\n\nOr using a function:\n\n[source,erlang]\nVersion = cowboy_req:version(Req).\n\nCowboy defines the `'HTTP/1.0'`, `'HTTP/1.1'` and `'HTTP/2'`\nversions. Custom protocols can define their own values as\natoms.\n\n=== Effective request URI\n\nThe scheme, host, port, path and query string components\nof the effective request URI can all be retrieved directly:\n\n[source,erlang]\n----\n#{\n    scheme := Scheme,\n    host := Host,\n    port := Port,\n    path := Path,\n    qs := Qs\n} = Req.\n----\n\nOr using the related functions:\n\n[source,erlang]\nScheme = cowboy_req:scheme(Req),\nHost = cowboy_req:host(Req),\nPort = cowboy_req:port(Req),\nPath = cowboy_req:path(Req).\nQs = cowboy_req:qs(Req).\n\nThe scheme and host are lowercased case insensitive binary\nstrings. The port is an integer representing the port number.\nThe path and query string are case sensitive binary strings.\n\nCowboy defines only the `<<\"http\">>` and `<<\"https\">>` schemes.\nThey are chosen so that the scheme will only be `<<\"https\">>`\nfor requests on secure HTTP/1.1 or HTTP/2 connections.\n// @todo Is that tested well?\n\nThe effective request URI itself can be reconstructed with\nthe `cowboy_req:uri/1,2` function. By default, an absolute\nURI is returned:\n\n[source,erlang]\n%% scheme://host[:port]/path[?qs]\nURI = cowboy_req:uri(Req).\n\nOptions are available to either disable or replace some\nor all of the components. Various URIs or URI formats can\nbe generated this way, including the origin form:\n\n[source,erlang]\n%% /path[?qs]\nURI = cowboy_req:uri(Req, #{host => undefined}).\n\nThe protocol relative form:\n\n[source,erlang]\n%% //host[:port]/path[?qs]\nURI = cowboy_req:uri(Req, #{scheme => undefined}).\n\nThe absolute URI without a query string:\n\n[source,erlang]\nURI = cowboy_req:uri(Req, #{qs => undefined}).\n\nA different host:\n\n[source,erlang]\nURI = cowboy_req:uri(Req, #{host => <<\"example.org\">>}).\n\nAnd any other combination.\n\n=== Bindings\n\nBindings are the host and path components that you chose\nto extract when defining the routes of your application.\nThey are only available after the routing.\n\nCowboy provides functions to retrieve one or all bindings.\n\nTo retrieve a single value:\n\n[source,erlang]\nValue = cowboy_req:binding(userid, Req).\n\nWhen attempting to retrieve a value that was not bound,\n`undefined` will be returned. A different default value\ncan be provided:\n\n[source,erlang]\nValue = cowboy_req:binding(userid, Req, 42).\n\nTo retrieve everything that was bound:\n\n[source,erlang]\nBindings = cowboy_req:bindings(Req).\n\nThey are returned as a map, with keys being atoms.\n\nThe Cowboy router also allows you to capture many host\nor path segments at once using the `...` qualifier.\n\nTo retrieve the segments captured from the host name:\n\n[source,erlang]\nHostInfo = cowboy_req:host_info(Req).\n\nAnd the path segments:\n\n[source,erlang]\nPathInfo = cowboy_req:path_info(Req).\n\nCowboy will return `undefined` if `...` was not used\nin the route.\n\n=== Query parameters\n\nCowboy provides two functions to access query parameters.\nYou can use the first to get the entire list of parameters.\n\n[source,erlang]\nQsVals = cowboy_req:parse_qs(Req),\n{_, Lang} = lists:keyfind(<<\"lang\">>, 1, QsVals).\n\nCowboy will only parse the query string, and not do any\ntransformation. This function may therefore return duplicates,\nor parameter names without an associated value. The order of\nthe list returned is undefined.\n\nWhen a query string is `key=1&key=2`, the list returned will\ncontain two parameters of name `key`.\n\nThe same is true when trying to use the PHP-style suffix `[]`.\nWhen a query string is `key[]=1&key[]=2`, the list returned will\ncontain two parameters of name `key[]`. Cowboy does not require\nthe `[]` suffix to properly handle repeated key names.\n\nWhen a query string is simply `key`, Cowboy will return the\nlist `[{<<\"key\">>, true}]`, using `true` to indicate that the\nparameter `key` was defined, but with no value.\n\nThe second function Cowboy provides allows you to match out\nonly the parameters you are interested in, and at the same\ntime do any post processing you require using xref:constraints[constraints].\nThis function returns a map.\n\n[source,erlang]\n#{id := ID, lang := Lang} = cowboy_req:match_qs([id, lang], Req).\n\nConstraints can be applied automatically. The following\nsnippet will crash when the `id` parameter is not an integer,\nor when the `lang` parameter is empty. At the same time, the\nvalue for `id` will be converted to an integer term:\n\n[source,erlang]\nQsMap = cowboy_req:match_qs([{id, int}, {lang, nonempty}], Req).\n\nA default value may also be provided. The default will be used\nif the `lang` key is not found. It will not be used if\nthe key is found but has an empty value.\n\n[source,erlang]\n#{lang := Lang} = cowboy_req:match_qs([{lang, [], <<\"en-US\">>}], Req).\n\nIf no default is provided and the value is missing, the\nquery string is deemed invalid and the process will crash.\n\nWhen the query string is `key=1&key=2`, the value for `key`\nwill be the list `[<<\"1\">>, <<\"2\">>]`. Parameter names do not\nneed to include the PHP-style suffix. Constraints may be used\nto ensure that only one value was given. Constraints do not\nautomatically look inside the list, a custom constraint must\nbe written if that is necessary.\n\n=== Headers\n\nHeader values can be retrieved either as a binary string\nor parsed into a more meaningful representation.\n\nThe get the raw value:\n\n[source,erlang]\nHeaderVal = cowboy_req:header(<<\"content-type\">>, Req).\n\nCowboy expects all header names to be provided as lowercase\nbinary strings. This is true for both requests and responses,\nregardless of the underlying protocol.\n\nWhen the header is missing from the request, `undefined`\nwill be returned. A different default can be provided:\n\n[source,erlang]\nHeaderVal = cowboy_req:header(<<\"content-type\">>, Req, <<\"text/plain\">>).\n\nAll headers can be retrieved at once, either directly:\n\n[source,erlang]\n#{headers := AllHeaders} = Req.\n\nOr using a function:\n\n[source,erlang]\nAllHeaders = cowboy_req:headers(Req).\n\nCowboy provides equivalent functions to parse individual\nheaders. There is no function to parse all headers at once.\n\nTo parse a specific header:\n\n[source,erlang]\nParsedVal = cowboy_req:parse_header(<<\"content-type\">>, Req).\n\nAn exception will be thrown if it doesn't know how to parse the\ngiven header, or if the value is invalid. The list of known headers\nand default values can be found in the manual.\n\nWhen the header is missing, `undefined` is returned. You can\nchange the default value. Note that it should be the parsed value\ndirectly:\n\n[source,erlang]\n----\nParsedVal = cowboy_req:parse_header(<<\"content-type\">>, Req,\n    {<<\"text\">>, <<\"plain\">>, []}).\n----\n\n=== Peer\n\nThe peer address and port number for the connection can be\nretrieved either directly or using a function.\n\nTo retrieve the peer directly:\n\n[source,erlang]\n#{peer := {IP, Port}} = Req.\n\nAnd using a function:\n\n[source,erlang]\n{IP, Port} = cowboy_req:peer(Req).\n\nNote that the peer corresponds to the remote end of the\nconnection to the server, which may or may not be the\nclient itself. It may also be a proxy or a gateway.\n"
  },
  {
    "path": "doc/src/guide/req_body.asciidoc",
    "content": "[[req_body]]\n== Reading the request body\n\nThe request body can be read using the Req object.\n\nCowboy will not attempt to read the body until requested.\nYou need to call the body reading functions in order to\nretrieve it.\n\nCowboy will not cache the body, it is therefore only\npossible to read it once.\n\nYou are not required to read it, however. If a body is\npresent and was not read, Cowboy will either cancel or\nskip its download, depending on the protocol.\n\nCowboy provides functions for reading the body raw,\nand read and parse form urlencoded or xref:multipart[multipart bodies].\nThe latter is covered in its own chapter.\n\n=== Request body presence\n\nNot all requests come with a body. You can check for\nthe presence of a request body with this function:\n\n[source,erlang]\ncowboy_req:has_body(Req).\n\nIt returns `true` if there is a body; `false` otherwise.\n\nIn practice, this function is rarely used. When the\nmethod is `POST`, `PUT` or `PATCH`, the request body\nis often required by the application, which should\njust attempt to read it directly.\n\n=== Request body length\n\nYou can obtain the length of the body:\n\n[source,erlang]\nLength = cowboy_req:body_length(Req).\n\nNote that the length may not be known in advance. In\nthat case `undefined` will be returned. This can happen\nwith HTTP/1.1's chunked transfer-encoding, or HTTP/2\nwhen no content-length was provided.\n\nCowboy will update the body length in the Req object\nonce the body has been read completely. A length will\nalways be returned when attempting to call this function\nafter reading the body completely.\n\n=== Reading the body\n\nYou can read the entire body with one function call:\n\n[source,erlang]\n{ok, Data, Req} = cowboy_req:read_body(Req0).\n\nCowboy returns an `ok` tuple when the body has been\nread fully.\n\nBy default, Cowboy will attempt to read up to 8MB\nof data, for up to 15 seconds. The call will return\nonce Cowboy has read at least 8MB of data, or at\nthe end of the 15 seconds period.\n\nThese values can be customized. For example, to read\nonly up to 1MB for up to 5 seconds:\n\n[source,erlang]\n----\n{ok, Data, Req} = cowboy_req:read_body(Req0,\n    #{length => 1000000, period => 5000}).\n----\n\nThese two options can effectively be used to control\nthe rate of transmission of the request body.\n\nIt is also possible to asynchronously read the request\nbody using auto mode:\n\n[source,erlang]\n----\nRef = make_ref(),\ncowboy_req:cast({read_body, self(), Ref, auto, infinity}, Req).\n----\n\nCowboy will wait indefinitely for data and then send a\n`request_body` message as soon as it has data available,\nregardless of length.\n\n[source,erlang]\n----\nreceive\n    {request_body, Ref, nofin, Data} ->\n        do_something(Data);\n    {request_body, Ref, fin, _BodyLen, Data} ->\n        do_something(Data)\nend.\n----\n\nAsynchronous reading of data pairs well with loop handlers.\n\n=== Streaming the body\n\nWhen the body is too large, the first call will return\na `more` tuple instead of `ok`. You can call the\nfunction again to read more of the body, reading\nit one chunk at a time.\n\n[source,erlang]\n----\nread_body_to_console(Req0) ->\n    case cowboy_req:read_body(Req0) of\n        {ok, Data, Req} ->\n            io:format(\"~s\", [Data]),\n            Req;\n        {more, Data, Req} ->\n            io:format(\"~s\", [Data]),\n            read_body_to_console(Req)\n    end.\n----\n\nThe `length` and `period` options can also be used.\nThey need to be passed for every call.\n\n=== Reading a form urlencoded body\n\nCowboy provides a convenient function for reading and\nparsing bodies sent as application/x-www-form-urlencoded.\n\n[source,erlang]\n{ok, KeyValues, Req} = cowboy_req:read_urlencoded_body(Req0).\n\nThis function returns a list of key/values, exactly like\nthe function `cowboy_req:parse_qs/1`.\n\nThe defaults for this function are different. Cowboy will\nread for up to 64KB and up to 5 seconds. They can be modified:\n\n[source,erlang]\n----\n{ok, KeyValues, Req} = cowboy_req:read_urlencoded_body(Req0,\n    #{length => 4096, period => 3000}).\n----\n"
  },
  {
    "path": "doc/src/guide/resource_design.asciidoc",
    "content": "[[resource_design]]\n== Designing a resource handler\n\nThis chapter aims to provide you with a list of questions\nyou must answer in order to write a good resource handler.\nIt is meant to be usable as a step by step guide.\n\n=== The service\n\nCan the service become unavailable, and when it does, can\nwe detect it? For example, database connectivity problems\nmay be detected early. We may also have planned outages\nof all or parts of the system. Implement the\n`service_available` callback.\n\nWhat HTTP methods does the service implement? Do we need\nmore than the standard OPTIONS, HEAD, GET, PUT, POST,\nPATCH and DELETE? Are we not using one of those at all?\nImplement the `known_methods` callback.\n\n=== Type of resource handler\n\nAm I writing a handler for a collection of resources,\nor for a single resource?\n\nThe semantics for each of these are quite different.\nYou should not mix collection and single resource in\nthe same handler.\n\n=== Collection handler\n\nSkip this section if you are not doing a collection.\n\nIs the collection hardcoded or dynamic? For example,\nif you use the route `/users` for the collection of\nusers then the collection is hardcoded; if you use\n`/forums/:category` for the collection of threads\nthen it isn't. When the collection is hardcoded you\ncan safely assume the resource always exists.\n\nWhat methods should I implement?\n\nOPTIONS is used to get some information about the\ncollection. It is recommended to allow it even if you\ndo not implement it, as Cowboy has a default\nimplementation built-in.\n\nHEAD and GET are used to retrieve the collection.\nIf you allow GET, also allow HEAD as there's no extra\nwork required to make it work.\n\nPOST is used to create a new resource inside the\ncollection. Creating a resource by using POST on\nthe collection is useful when resources may be\ncreated before knowing their URI, usually because\nparts of it are generated dynamically. A common\ncase is some kind of auto incremented integer\nidentifier.\n\nThe next methods are more rarely allowed.\n\nPUT is used to create a new collection (when\nthe collection isn't hardcoded), or replace\nthe entire collection.\n\nDELETE is used to delete the entire collection.\n\nPATCH is used to modify the collection using\ninstructions given in the request body. A PATCH\noperation is atomic. The PATCH operation may\nbe used for such things as reordering; adding,\nmodifying or deleting parts of the collection.\n\n=== Single resource handler\n\nSkip this section if you are doing a collection.\n\nWhat methods should I implement?\n\nOPTIONS is used to get some information about the\nresource. It is recommended to allow it even if you\ndo not implement it, as Cowboy has a default\nimplementation built-in.\n\nHEAD and GET are used to retrieve the resource.\nIf you allow GET, also allow HEAD as there's no extra\nwork required to make it work.\n\nPOST is used to update the resource.\n\nPUT is used to create a new resource (when it doesn't\nalready exist) or replace the resource.\n\nDELETE is used to delete the resource.\n\nPATCH is used to modify the resource using\ninstructions given in the request body. A PATCH\noperation is atomic. The PATCH operation may\nbe used for adding, removing or modifying specific\nvalues in the resource.\n\n=== The resource\n\nFollowing the above discussion, implement the\n`allowed_methods` callback.\n\nDoes the resource always exist? If it may not, implement\nthe `resource_exists` callback.\n\nDo I need to authenticate the client before they can\naccess the resource? What authentication mechanisms\nshould I provide? This may include form-based, token-based\n(in the URL or a cookie), HTTP basic, HTTP digest,\nSSL certificate or any other form of authentication.\nImplement the `is_authorized` callback.\n\nDo I need fine-grained access control? How do I determine\nthat they are authorized access? Handle that in your\n`is_authorized` callback.\n\nCan access to a resource be forbidden regardless of access\nbeing authorized? A simple example of that is censorship\nof a resource. Implement the `forbidden` callback.\n\nCan access be rate-limited for authenticated users? Use the\n`rate_limited` callback.\n\nAre there any constraints on the length of the resource URI?\nFor example, the URI may be used as a key in storage and may\nhave a limit in length. Implement `uri_too_long`.\n\n=== Representations\n\nWhat media types do I provide? If text based, what charsets\nare provided? What languages do I provide?\n\nImplement the mandatory `content_types_provided`. Prefix\nthe callbacks with `to_` for clarity. For example, `to_html`\nor `to_text`. For resources that don't implement methods\nGET or HEAD, you must still accept at least one media type,\nbut you can leave the callback as `undefined` since it will\nnever be called.\n\nImplement the `languages_provided` or `charsets_provided`\ncallbacks if applicable.\n\nDoes the resource accept ranged requests? If it does,\nimplement the `ranges_provided` callback. Resources that\nonly accept `bytes` units can use the callback name\n`auto` and let Cowboy automatically do ranged responses.\nOther callbacks should have a name prefix of `ranged_`\nfor clarity. For example, `ranged_bytes` or `ranged_pages`.\nIf the resource needs to perform additional checks before\naccepting to do a ranged responses, implement the\n`range_satisfiable` callback.\n\nIs there any other header that may make the representation\nof the resource vary? Implement the `variances` callback.\n\nDepending on your choices for caching content, you may\nwant to implement one or more of the `generate_etag`,\n`last_modified` and `expires` callbacks.\n\nDo I want the user or user agent to actively choose a\nrepresentation available? Send a list of available\nrepresentations in the response body and implement\nthe `multiple_choices` callback.\n\n=== Redirections\n\nDo I need to keep track of what resources were deleted?\nFor example, you may have a mechanism where moving a\nresource leaves a redirect link to its new location.\nImplement the `previously_existed` callback.\n\nWas the resource moved, and is the move temporary? If\nit is explicitly temporary, for example due to maintenance,\nimplement the `moved_temporarily` callback. Otherwise,\nimplement the `moved_permanently` callback.\n\n=== The request\n\nDo you need to read the query string? Individual headers?\nImplement `malformed_request` and do all the parsing and\nvalidation in this function. Note that the body should not\nbe read at this point.\n\nMay there be a request body? Will I know its size?\nWhat's the maximum size of the request body I'm willing\nto accept? Implement `valid_entity_length`.\n\nFinally, take a look at the sections corresponding to the\nmethods you are implementing.\n\n=== OPTIONS method\n\nCowboy by default will send back a list of allowed methods.\nDo I need to add more information to the response? Implement\nthe `options` method.\n\n=== GET and HEAD methods\n\nIf you implement the methods GET and/or HEAD, you must\nimplement one `ProvideCallback` callback for each\ncontent-type returned by the `content_types_provided`\ncallback.\n\nWhen range requests are accepted, you must implement one\n`RangeCallback` for each range unit returned by\n`ranges_provided` (unless `auto` was used). This is\nin addition to the `ProvideCallback` callback.\n\n=== PUT, POST and PATCH methods\n\nIf you implement the methods PUT, POST and/or PATCH,\nyou must implement the `content_types_accepted` callback,\nand one `AcceptCallback` callback for each content-type\nit returns. Prefix the `AcceptCallback` callback names\nwith `from_` for clarity. For example, `from_html` or\n`from_json`.\n\nDo we want to allow the POST method to create individual\nresources directly through their URI (like PUT)? Implement\nthe `allow_missing_post` callback. It is recommended to\nexplicitly use PUT in these cases instead.\n\nMay there be conflicts when using PUT to create or replace\na resource? Do we want to make sure that two updates around\nthe same time are not cancelling one another? Implement the\n`is_conflict` callback.\n\n=== DELETE methods\n\nIf you implement the method DELETE, you must implement\nthe `delete_resource` callback.\n\nWhen `delete_resource` returns, is the resource completely\nremoved from the server, including from any caching service?\nIf not, and/or if the deletion is asynchronous and we have\nno way of knowing it has been completed yet, implement the\n`delete_completed` callback.\n"
  },
  {
    "path": "doc/src/guide/resp.asciidoc",
    "content": "[[resp]]\n== Sending a response\n\nThe response must be sent using the Req object.\n\nCowboy provides two different ways of sending responses:\neither directly or by streaming the body. Response headers\nand body may be set in advance. The response is sent as\nsoon as one of the reply or stream reply function is\ncalled.\n\nCowboy also provides a simplified interface for sending\nfiles. It can also send only specific parts of a file.\n\nWhile only one response is allowed for every request,\nHTTP/2 introduced a mechanism that allows the server\nto push additional resources related to the response.\nThis chapter also describes how this feature works in\nCowboy.\n\n=== Reply\n\nCowboy provides three functions for sending the entire reply,\ndepending on whether you need to set headers and body. In all\ncases, Cowboy will add any headers required by the protocol\n(for example the date header will always be sent).\n\nWhen you need to set only the status code,\nuse `cowboy_req:reply/2`:\n\n[source,erlang]\nReq = cowboy_req:reply(200, Req0).\n\nWhen you need to set response headers at the same time,\nuse `cowboy_req:reply/3`:\n\n[source,erlang]\n----\nReq = cowboy_req:reply(303, #{\n    <<\"location\">> => <<\"https://ninenines.eu\">>\n}, Req0).\n----\n\nNote that the header name must always be a lowercase\nbinary.\n\nWhen you also need to set the response body,\nuse `cowboy_req:reply/4`:\n\n[source,erlang]\n----\nReq = cowboy_req:reply(200, #{\n    <<\"content-type\">> => <<\"text/plain\">>\n}, \"Hello world!\", Req0).\n----\n\nYou should always set the content-type header when the\nresponse has a body. There is however no need to set\nthe content-length header; Cowboy does it automatically.\n\nThe response body and the header values must be either\na binary or an iolist. An iolist is a list containing\nbinaries, characters, strings or other iolists. This\nallows you to build a response from different parts\nwithout having to do any concatenation:\n\n[source,erlang]\n----\nTitle = \"Hello world!\",\nBody = <<\"Hats off!\">>,\nReq = cowboy_req:reply(200, #{\n    <<\"content-type\">> => <<\"text/html\">>\n}, [\"<html><head><title>\", Title, \"</title></head>\",\n    \"<body><p>\", Body, \"</p></body></html>\"], Req0).\n----\n\nThis method of building responses is more efficient than\nconcatenating. Behind the scenes, each element of the list\nis simply a pointer, and those pointers are used directly\nwhen writing to the socket.\n\n=== Stream reply\n\nCowboy provides two functions for initiating a response,\nand an additional function for streaming the response body.\nCowboy will add any required headers to the response.\n\n// @todo For HTTP/1.1 Cowboy should probably not use chunked transfer-encoding if the content-length is set.\n\nWhen you need to set only the status code,\nuse `cowboy_req:stream_reply/2`:\n\n[source,erlang]\n----\nReq = cowboy_req:stream_reply(200, Req0),\n\ncowboy_req:stream_body(\"Hello...\", nofin, Req),\ncowboy_req:stream_body(\"chunked...\", nofin, Req),\ncowboy_req:stream_body(\"world!!\", fin, Req).\n----\n\nThe second argument to `cowboy_req:stream_body/3` indicates\nwhether this data terminates the body. Use `fin` for the\nfinal flag, and `nofin` otherwise.\n\nThis snippet does not set a content-type header. This is\nnot recommended. All responses with a body should have\na content-type. The header can be set beforehand, or\nusing the `cowboy_req:stream_reply/3`:\n\n[source,erlang]\n----\nReq = cowboy_req:stream_reply(200, #{\n    <<\"content-type\">> => <<\"text/html\">>\n}, Req0),\n\ncowboy_req:stream_body(\"<html><head>Hello world!</head>\", nofin, Req),\ncowboy_req:stream_body(\"<body><p>Hats off!</p></body></html>\", fin, Req).\n----\n\nHTTP provides a few different ways to stream response bodies.\nCowboy will select the most appropriate one based on the HTTP\nversion and the request and response headers.\n\nWhile not required by any means, it is recommended that you\nset the content-length header in the response if you know it\nin advance. This will ensure that the best response method\nis selected and help clients understand when the response\nis fully received.\n\nCowboy also provides a function to send response trailers.\nResponse trailers are semantically equivalent to the headers\nyou send in the response, only they are sent at the end.\nThis is especially useful to attach information to the\nresponse that could not be generated until the response\nbody was fully generated.\n\nTrailer fields must be listed in the trailer header. Any\nfield not listed might be dropped by the client or an intermediary.\n\n[source,erlang]\n----\nReq = cowboy_req:stream_reply(200, #{\n    <<\"content-type\">> => <<\"text/html\">>,\n    <<\"trailer\">> => <<\"expires, content-md5\">>\n}, Req0),\n\ncowboy_req:stream_body(\"<html><head>Hello world!</head>\", nofin, Req),\ncowboy_req:stream_body(\"<body><p>Hats off!</p></body></html>\", nofin, Req),\n\ncowboy_req:stream_trailers(#{\n    <<\"expires\">> => <<\"Sun, 10 Dec 2017 19:13:47 GMT\">>,\n    <<\"content-md5\">> => <<\"c6081d20ff41a42ce17048ed1c0345e2\">>\n}, Req).\n----\n\nThe stream ends with trailers. It is no longer possible to\nsend data after sending trailers. You cannot send trailers\nafter setting the `fin` flag when streaming the body.\n\n=== Preset response headers\n\nCowboy provides functions to set response headers without\nimmediately sending them. They are stored in the Req object\nand sent as part of the response when a reply function is\ncalled.\n\nTo set response headers:\n\n[source,erlang]\nReq = cowboy_req:set_resp_header(<<\"allow\">>, \"GET\", Req0).\n\nHeader names must be a lowercase binary.\n\nDo not use this function for setting cookies. Refer to\nthe xref:cookies[Cookies] chapter for more information.\n\nTo check if a response header has already been set:\n\n[source,erlang]\ncowboy_req:has_resp_header(<<\"allow\">>, Req).\n\nIt returns `true` if the header was set, `false` otherwise.\n\nTo delete a response header that was set previously:\n\n[source,erlang]\nReq = cowboy_req:delete_resp_header(<<\"allow\">>, Req0).\n\n=== Overriding headers\n\nAs Cowboy provides different ways of setting response\nheaders and body, clashes may occur, so it's important\nto understand what happens when a header is set twice.\n\nHeaders come from five different origins:\n\n* Protocol-specific headers (for example HTTP/1.1's connection header)\n* Other required headers (for example the date header)\n* Preset headers\n* Headers given to the reply function\n* Set-cookie headers\n\nCowboy does not allow overriding protocol-specific headers.\n\nSet-cookie headers will always be appended at the end of\nthe list of headers before sending the response.\n\nHeaders given to the reply function will always override\npreset headers and required headers. If a header is found\nin two or three of these, then the one in the reply function\nis picked and the others are dropped.\n\nSimilarly, preset headers will always override required\nheaders.\n\nTo illustrate, look at the following snippet. Cowboy by\ndefault sends the server header with the value \"Cowboy\".\nWe can override it:\n\n[source,erlang]\n----\nReq = cowboy_req:reply(200, #{\n    <<\"server\">> => <<\"yaws\">>\n}, Req0).\n----\n\n=== Preset response body\n\nCowboy provides functions to set the response body without\nimmediately sending it. It is stored in the Req object and\nsent when the reply function is called.\n\nTo set the response body:\n\n[source,erlang]\nReq = cowboy_req:set_resp_body(\"Hello world!\", Req0).\n\n// @todo Yeah we probably should add that function that\n// also sets the content-type at the same time...\n\nTo check if a response body has already been set:\n\n[source,erlang]\ncowboy_req:has_resp_body(Req).\n\nIt returns `true` if the body was set and is non-empty,\n`false` otherwise.\n\n// @todo We probably should also have a function that\n// properly removes the response body, including any\n// content-* headers.\n\nThe preset response body is only sent if the reply function\nused is `cowboy_req:reply/2` or `cowboy_req:reply/3`.\n\n=== Sending files\n\nCowboy provides a shortcut for sending files. When\nusing `cowboy_req:reply/4`, or when presetting the\nresponse header, you can give a `sendfile` tuple to\nCowboy:\n\n[source,erlang]\n{sendfile, Offset, Length, Filename}\n\nDepending on the values for `Offset` or `Length`, the\nentire file may be sent, or just a part of it.\n\nThe length is required even for sending the entire file.\nCowboy sends it in the content-length header.\n\nTo send a file while replying:\n\n[source,erlang]\n----\nReq = cowboy_req:reply(200, #{\n    <<\"content-type\">> => \"image/png\"\n}, {sendfile, 0, 12345, \"path/to/logo.png\"}, Req0).\n----\n\n// @todo An example of presetting a file would be useful,\n// but let's wait for the function that can set the\n// content-type at the same time.\n\n// @todo What about streaming many files? For example\n// it should be possible to build a tar file on the fly\n// while still using sendfile. Another example could be\n// proper support for multipart byte ranges. Yet another\n// example would be automatic concatenation of CSS or JS\n// files.\n\n=== Informational responses\n\nCowboy allows you to send informational responses.\n\nInformational responses are responses that have a status\ncode between 100 and 199. Any number can be sent before\nthe proper response. Sending an informational response\ndoes not change the behavior of the proper response, and\nclients are expected to ignore any informational response\nthey do not understand.\n\nThe following snippet sends a 103 informational response\nwith some headers that are expected to be in the final\nresponse.\n\n[source,erlang]\n----\nReq = cowboy_req:inform(103, #{\n    <<\"link\">> => <<\"</style.css>; rel=preload; as=style, </script.js>; rel=preload; as=script\">>\n}, Req0).\n----\n\n=== Push\n\nThe HTTP/2 protocol introduced the ability to push resources\nrelated to the one sent in the response. Cowboy provides two\nfunctions for that purpose: `cowboy_req:push/3,4`.\n\nPush is only available for HTTP/2. Cowboy will automatically\nignore push requests if the protocol doesn't support it.\n\nThe push function must be called before any of the reply\nfunctions. Doing otherwise will result in a crash.\n\nTo push a resource, you need to provide the same information\nas a client performing a request would. This includes the\nHTTP method, the URI and any necessary request headers.\n\nCowboy by default only requires you to give the path to\nthe resource and the request headers. The rest of the URI\nis taken from the current request (excluding the query\nstring, set to empty) and the method is GET by default.\n\nThe following snippet pushes a CSS file that is linked to\nin the response:\n\n[source,erlang]\n----\ncowboy_req:push(\"/static/style.css\", #{\n    <<\"accept\">> => <<\"text/css\">>\n}, Req0),\nReq = cowboy_req:reply(200, #{\n    <<\"content-type\">> => <<\"text/html\">>\n}, [\"<html><head><title>My web page</title>\",\n    \"<link rel='stylesheet' type='text/css' href='/static/style.css'>\",\n    \"<body><p>Welcome to Erlang!</p></body></html>\"], Req0).\n----\n\nTo override the method, scheme, host, port or query string,\nsimply pass in a fourth argument. The following snippet\nuses a different host name:\n\n[source,erlang]\n----\ncowboy_req:push(\"/static/style.css\", #{\n    <<\"accept\">> => <<\"text/css\">>\n}, #{host => <<\"cdn.example.org\">>}, Req),\n----\n\nPushed resources don't have to be files. As long as the push\nrequest is cacheable, safe and does not include a body, the\nresource can be pushed.\n\nUnder the hood, Cowboy handles pushed requests the same as\nnormal requests: a different process is created which will\nultimately send a response to the client.\n"
  },
  {
    "path": "doc/src/guide/rest_flowcharts.asciidoc",
    "content": "[[rest_flowcharts]]\n== REST flowcharts\n\nThis chapter will explain the REST handler state machine through\na number of different diagrams.\n\nThere are four main paths that requests may follow. One for the\nmethod OPTIONS; one for the methods GET and HEAD; one for the\nmethods PUT, POST and PATCH; and one for the method DELETE.\n\nAll paths start with the \"Start\" diagram, and all paths excluding\nthe OPTIONS path go through the \"Content negotiation\" diagram\nand optionally the \"Conditional requests\" diagram if the resource\nexists.\n\nThe red squares refer to another diagram. The light green squares\nindicate a response. Other squares may be either a callback or a\nquestion answered by Cowboy itself. Green arrows tend to indicate\nthe default behavior if the callback is undefined. The star next\nto values indicate that the value is descriptive rather than exact.\n\n=== Start\n\nAll requests start from here.\n\nimage::rest_start.png[REST starting flowchart]\n\nA series of callbacks are called in succession to perform\na general checkup of the service, the request line and\nrequest headers.\n\nThe request body, if any, is not expected to have been\nreceived for any of these steps. It is only processed\nat the end of the \"PUT, POST and PATCH methods\" diagram,\nwhen all conditions have been met.\n\nThe `known_methods` and `allowed_methods` callbacks\nreturn a list of methods. Cowboy then checks if the request\nmethod is in the list, and stops otherwise.\n\nThe `is_authorized` callback may be used to check that\naccess to the resource is authorized. Authentication\nmay also be performed as needed. When authorization is\ndenied, the return value from the callback must include\na challenge applicable to the requested resource, which\nwill be sent back to the client in the www-authenticate\nheader.\n\nThis diagram is immediately followed by either the\n\"OPTIONS method\" diagram when the request method is\nOPTIONS, or the \"Content negotiation\" diagram otherwise.\n\n=== OPTIONS method\n\nThis diagram only applies to OPTIONS requests.\n\nimage::rest_options.png[REST OPTIONS method flowchart]\n\nThe `options` callback may be used to add information\nabout the resource, such as media types or languages\nprovided; allowed methods; any extra information. A\nresponse body may also be set, although clients should\nnot be expected to read it.\n\nIf the `options` callback is not defined, Cowboy will\nsend a response containing the list of allowed methods\nby default.\n\n=== Content negotiation\n\nThis diagram applies to all request methods other than\nOPTIONS. It is executed right after the \"Start\" diagram\nis completed.\n\nimage::rest_conneg.png[REST content negotiation flowchart]\n\nThe purpose of these steps is to determine an appropriate\nrepresentation to be sent back to the client.\n\nThe request may contain any of the accept header; the\naccept-language header; or the accept-charset header.\nWhen present, Cowboy will parse the headers and then\ncall the corresponding callback to obtain the list\nof provided content-type, language or charset for this\nresource. It then automatically select the best match\nbased on the request.\n\nIf a callback is not defined, Cowboy will select the\ncontent-type, language or charset that the client\nprefers.\n\nThe `content_types_provided` also returns the name of\na callback for every content-type it accepts. This\ncallback will only be called at the end of the\n\"GET and HEAD methods\" diagram, when all conditions\nhave been met.\n\nOptionally, the `ranges_provided` also returns the\nname of a callback for every range unit it accepts. This\nwill be called at the end of the \"GET and HEAD methods\"\ndiagram in the case of ranged requests.\n\nThe selected content-type, language and charset are\nsaved as meta values in the Req object. You *should*\nuse the appropriate representation if you set a\nresponse body manually (alongside an error code,\nfor example).\n\nThis diagram is immediately followed by\nthe \"GET and HEAD methods\" diagram,\nthe \"PUT, POST and PATCH methods\" diagram,\nor the \"DELETE method\" diagram, depending on the\nmethod.\n\n=== GET and HEAD methods\n\nThis diagram only applies to GET and HEAD requests.\n\nFor a description of the `cond` step, please see\nthe \"Conditional requests\" diagram.\n\nimage::rest_get_head.png[REST GET/HEAD methods flowchart]\n\nWhen the resource exists, and the conditional steps\nsucceed, the resource can be retrieved.\n\nCowboy prepares the response by first retrieving\nmetadata about the representation, then by calling\nthe `ProvideCallback` callback. This is the callback\nyou defined for each content-types you returned from\n`content_types_provided`. This callback returns the body\nthat will be sent back to the client.\n\nFor ranged requests, but only when the `ranges_provided`\ncallback was defined earlier, Cowboy will add the selected\n`range` information to the Req object and call the\n`range_satisfiable` callback. After confirming that the\nrange can be provided, Cowboy calls the `RangeResource`\ncallback and produces a ranged response using the\nranged data from the callback.\n\nWhen the resource does not exist, Cowboy will figure out\nwhether the resource existed previously, and if so whether\nit was moved elsewhere in order to redirect the client to\nthe new URI.\n\nThe `moved_permanently` and `moved_temporarily` callbacks\nmust return the new location of the resource if it was in\nfact moved.\n\n=== PUT, POST and PATCH methods\n\nThis diagram only applies to PUT, POST and PATCH requests.\n\nFor a description of the `cond` step, please see\nthe \"Conditional requests\" diagram.\n\nimage::rest_put_post_patch.png[REST PUT/POST/PATCH methods flowchart]\n\nWhen the resource exists, first the conditional steps\nare executed. When that succeeds, and the method is PUT,\nCowboy will call the `is_conflict` callback. This function\ncan be used to prevent potential race conditions, by locking\nthe resource for example.\n\nThen all three methods reach the `content_types_accepted`\nstep that we will describe in a few paragraphs.\n\nWhen the resource does not exist, and the method is PUT,\nCowboy will check for conflicts and then move on to the\n`content_types_accepted` step. For other methods, Cowboy\nwill figure out whether the resource existed previously,\nand if so whether it was moved elsewhere. If the resource\nis truly non-existent, the method is POST and the call\nfor `allow_missing_post` returns `true`, then Cowboy will\nmove on to the `content_types_accepted` step. Otherwise\nthe request processing ends there.\n\nThe `moved_permanently` and `moved_temporarily` callbacks\nmust return the new location of the resource if it was in\nfact moved.\n\nThe `content_types_accepted` returns a list of\ncontent-types it accepts, but also the name of a callback\nfor each of them. Cowboy will select the appropriate\ncallback for processing the request body and call it.\n\nThis callback may return one of three different return\nvalues.\n\nIf an error occurred while processing the request body,\nit must return `false` and Cowboy will send an\nappropriate error response.\n\nIf the method is POST, then you may return `true` with\nan URI of where the resource has been created. This is\nespecially useful for writing handlers for collections.\n\nOtherwise, return `true` to indicate success. Cowboy\nwill select the appropriate response to be sent depending\non whether a resource has been created, rather than\nmodified, and on the availability of a location header\nor a body in the response.\n\n=== DELETE method\n\nThis diagram only applies to DELETE requests.\n\nFor a description of the `cond` step, please see\nthe \"Conditional requests\" diagram.\n\nimage::rest_delete.png[REST DELETE method flowchart]\n\nWhen the resource exists, and the conditional steps\nsucceed, the resource can be deleted.\n\nDeleting the resource is a two steps process. First\nthe callback `delete_resource` is executed. Use this\ncallback to delete the resource.\n\nBecause the resource may be cached, you must also\ndelete all cached representations of this resource\nin the system. This operation may take a while though,\nso you may return before it finished.\n\nCowboy will then call the `delete_completed` callback.\nIf you know that the resource has been completely\ndeleted from your system, including from caches, then\nyou can return `true`. If any doubts persist, return\n`false`. Cowboy will assume `true` by default.\n\nTo finish, Cowboy checks if you set a response body,\nand depending on that, sends the appropriate response.\n\nWhen the resource does not exist, Cowboy will figure out\nwhether the resource existed previously, and if so whether\nit was moved elsewhere in order to redirect the client to\nthe new URI.\n\nThe `moved_permanently` and `moved_temporarily` callbacks\nmust return the new location of the resource if it was in\nfact moved.\n\n=== Conditional requests\n\nThis diagram applies to all request methods other than\nOPTIONS. It is executed right after the `resource_exists`\ncallback, when the resource exists.\n\nimage::rest_cond.png[REST conditional requests flowchart]\n\nA request becomes conditional when it includes either of\nthe if-match header; the if-unmodified-since header; the\nif-none-match header; or the if-modified-since header.\n\nIf the condition fails, the request ends immediately\nwithout any retrieval or modification of the resource.\n\nThe `generate_etag` and `last_modified` are called as\nneeded. Cowboy will only call them once and then cache\nthe results for subsequent use.\n"
  },
  {
    "path": "doc/src/guide/rest_handlers.asciidoc",
    "content": "[[rest_handlers]]\n== REST handlers\n\nREST is implemented in Cowboy as a sub protocol. The request\nis handled as a state machine with many optional callbacks\ndescribing the resource and modifying the machine's behavior.\n\nThe REST handler is the recommended way to handle HTTP requests.\n\n=== Initialization\n\nFirst, the `init/2` callback is called. This callback is common\nto all handlers. To use REST for the current request, this function\nmust return a `cowboy_rest` tuple.\n\n[source,erlang]\n----\ninit(Req, State) ->\n    {cowboy_rest, Req, State}.\n----\n\nCowboy will then switch to the REST protocol and start executing\nthe state machine.\n\nAfter reaching the end of the flowchart, the `terminate/3` callback\nwill be called if it is defined.\n\n=== Methods\n\nThe REST component has code for handling the following HTTP methods:\nHEAD, GET, POST, PATCH, PUT, DELETE and OPTIONS.\n\nOther methods can be accepted, however they have no specific callback\ndefined for them at this time.\n\n=== Callbacks\n\nAll callbacks are optional. Some may become mandatory depending\non what other defined callbacks return. The various flowcharts\nin the next chapter should be a useful to determine which callbacks\nyou need.\n\nAll callbacks take two arguments, the Req object and the State,\nand return a three-element tuple of the form `{Value, Req, State}`.\n\nNearly all callbacks can also return `{stop, Req, State}` to\nstop execution of the request, and\n`{{switch_handler, Module}, Req, State}` or\n`{{switch_handler, Module, Opts}, Req, State}` to switch to\na different handler type. The exceptions are `expires`\n`generate_etag`, `last_modified` and `variances`.\n\nThe following table summarizes the callbacks and their default values.\nIf the callback isn't defined, then the default value will be used.\nPlease look at the flowcharts to find out the result of each return\nvalue.\n\nIn the following table, \"skip\" means the callback is entirely skipped\nif it is undefined, moving directly to the next step. Similarly,\n\"none\" means there is no default value for this callback.\n\n[cols=\"<,^\",options=\"header\"]\n|===\n| Callback name          | Default value\n| allowed_methods        | `[<<\"GET\">>, <<\"HEAD\">>, <<\"OPTIONS\">>]`\n| allow_missing_post     | `true`\n| charsets_provided      | skip\n| content_types_accepted | none\n// @todo Space required for the time being: https://github.com/spf13/hugo/issues/2398\n| content_types_provided | `[{{ <<\"text\">>, <<\"html\">>, '*'}, to_html}]`\n| delete_completed       | `true`\n| delete_resource        | `false`\n| expires                | `undefined`\n| forbidden              | `false`\n| generate_etag          | `undefined`\n| is_authorized          | `true`\n| is_conflict            | `false`\n| known_methods          | `[<<\"GET\">>, <<\"HEAD\">>, <<\"POST\">>, <<\"PUT\">>, <<\"PATCH\">>, <<\"DELETE\">>, <<\"OPTIONS\">>]`\n| languages_provided     | skip\n| last_modified          | `undefined`\n| malformed_request      | `false`\n| moved_permanently      | `false`\n| moved_temporarily      | `false`\n| multiple_choices       | `false`\n| options                | `ok`\n| previously_existed     | `false`\n| ranges_provided        | skip\n| range_satisfiable      | `true`\n| rate_limited           | `false`\n| resource_exists        | `true`\n| service_available      | `true`\n| uri_too_long           | `false`\n| valid_content_headers  | `true`\n| valid_entity_length    | `true`\n| variances              | `[]`\n|===\n\nAs you can see, Cowboy tries to move on with the request whenever\npossible by using well thought out default values.\n\nIn addition to these, there can be any number of user-defined\ncallbacks that are specified through `content_types_accepted/2`,\n`content_types_provided/2` or `ranges_provided/2`. They can take\nany name (except `auto` for range callbacks), however\nit is recommended to use a separate prefix for the callbacks of\neach function. For example, `from_html` and `to_html` indicate\nin the first case that we're accepting a resource given as HTML,\nand in the second case that we send one as HTML.\n\n=== Meta data\n\nCowboy will set informative values to the Req object at various\npoints of the execution. You can retrieve them by matching the\nReq object directly. The values are defined in the following table:\n\n[cols=\"<,<\",options=\"header\"]\n|===\n| Key        | Details\n| media_type | The content-type negotiated for the response entity\n| language   | The language negotiated for the response entity\n| charset    | The charset negotiated for the response entity\n| range      | The range selected for the ranged response\n|===\n\nThey can be used to send a proper body with the response to a\nrequest that used a method other than HEAD or GET.\n\n=== Response headers\n\nCowboy will set response headers automatically over the execution\nof the REST code. They are listed in the following table.\n\n[cols=\"<,<\",options=\"header\"]\n|===\n| Header name      | Details\n| accept-ranges    | Range units accepted by the resource\n| allow            | HTTP methods allowed by the resource\n| content-language | Language used in the response body\n| content-range    | Range of the content found in the response\n| content-type     | Media type and charset of the response body\n| etag             | Etag of the resource\n| expires          | Expiration date of the resource\n| last-modified    | Last modification date for the resource\n| location         | Relative or absolute URI to the requested resource\n| retry-after      | Delay or time the client should wait before accessing the resource\n| vary             | List of headers that may change the representation of the resource\n| www-authenticate | Authentication information to access the resource\n|===\n"
  },
  {
    "path": "doc/src/guide/rest_principles.asciidoc",
    "content": "[[rest_principles]]\n== REST principles\n\nThis chapter will attempt to define the concepts behind REST\nand explain what makes a service RESTful.\n\nREST is often confused with performing a distinct operation\ndepending on the HTTP method, while using more than the GET\nand POST methods. That's highly misguided at best.\n\nWe will first attempt to define REST and will look at what\nit means in the context of HTTP and the Web.\nFor a more in-depth explanation of REST, you can read\nhttp://www.ics.uci.edu/~fielding/pubs/dissertation/top.htm[Roy T. Fielding's dissertation]\nas it does a great job explaining where it comes from and\nwhat it achieves.\n\n=== REST architecture\n\nREST is a *client-server* architecture. The client and the server\nboth have a different set of concerns. The server stores and/or\nmanipulates information and makes it available to the user in\nan efficient manner. The client takes that information and\ndisplays it to the user and/or uses it to perform subsequent\nrequests for information. This separation of concerns allows both\nthe client and the server to evolve independently as it only\nrequires that the interface stays the same.\n\nREST is *stateless*. That means the communication between the\nclient and the server always contains all the information needed\nto perform the request. There is no session state in the server,\nit is kept entirely on the client's side. If access to a resource\nrequires authentication, then the client needs to authenticate\nitself with every request.\n\nREST is *cacheable*. The client, the server and any intermediary\ncomponents can all cache resources in order to improve performance.\n\nREST provides a *uniform interface* between components. This\nsimplifies the architecture, as all components follow the same\nrules to speak to one another. It also makes it easier to understand\nthe interactions between the different components of the system.\nA number of constraints are required to achieve this. They are\ncovered in the rest of the chapter.\n\nREST is a *layered system*. Individual components cannot see\nbeyond the immediate layer with which they are interacting. This\nmeans that a client connecting to an intermediate component, like\na proxy, has no knowledge of what lies beyond. This allows\ncomponents to be independent and thus easily replaceable or\nextendable.\n\nREST optionally provides *code on demand*. Code may be downloaded\nto extend client functionality. This is optional however because\nthe client may not be able to download or run this code, and so\na REST component cannot rely on it being executed.\n\n=== Resources and resource identifiers\n\nA resource is an abstract concept. In a REST system, any information\nthat can be named may be a resource. This includes documents, images,\na collection of resources and any other information. Any information\nthat can be the target of an hypertext link can be a resource.\n\nA resource is a conceptual mapping to a set of entities. The set of\nentities evolves over time; a resource doesn't. For example, a resource\ncan map to \"users who have logged in this past month\" and another\nto \"all users\". At some point in time they may map to the same set of\nentities, because all users logged in this past month. But they are\nstill different resources. Similarly, if nobody logged in recently,\nthen the first resource may map to the empty set. This resource exists\nregardless of the information it maps to.\n\nResources are identified by uniform resource identifiers, also known\nas URIs. Sometimes internationalized resource identifiers, or IRIs,\nmay also be used, but these can be directly translated into a URI.\n\nIn practice we will identify two kinds of resources. Individual\nresources map to a set of one element, for example \"user Joe\".\nCollection of resources map to a set of 0 to N elements,\nfor example \"all users\".\n\n=== Resource representations\n\nThe representation of a resource is a sequence of bytes associated\nwith metadata.\n\nThe metadata comes as a list of key-value pairs, where the name\ncorresponds to a standard that defines the value's structure and\nsemantics. With HTTP, the metadata comes in the form of request\nor response headers. The headers' structure and semantics are well\ndefined in the HTTP standard. Metadata includes representation\nmetadata, resource metadata and control data.\n\nThe representation metadata gives information about the\nrepresentation, such as its media type, the date of last\nmodification, or even a checksum.\n\nResource metadata could be link to related resources or\ninformation about additional representations of the resource.\n\nControl data allows parameterizing the request or response.\nFor example, we may only want the representation returned if\nit is more recent than the one we have in cache. Similarly,\nwe may want to instruct the client about how it should cache\nthe representation. This isn't restricted to caching. We may,\nfor example, want to store a new representation of a resource\nonly if it wasn't modified since we first retrieved it.\n\nThe data format of a representation is also known as the media\ntype. Some media types are intended for direct rendering to the\nuser, while others are intended for automated processing. The\nmedia type is a key component of the REST architecture.\n\n=== Self-descriptive messages\n\nMessages must be self-descriptive. That means that the data\nformat of a representation must always come with its media\ntype (and similarly requesting a resource involves choosing\nthe media type of the representation returned). If you are\nsending HTML, then you must say it is HTML by sending the\nmedia type with the representation. In HTTP this is done\nusing the content-type header.\n\nThe media type is often an IANA registered media type, like\n`text/html` or `image/png`, but does not need to be. Exactly\ntwo things are important for respecting this constraint: that\nthe media type is well specified, and that the sender and\nrecipient agree about what the media type refers to.\n\nThis means that you can create your own media types, like\n`application/x-mine`, and that as long as you write the\nspecifications for it and that both endpoints agree about\nit then the constraint is respected.\n\n=== Hypermedia as the engine of application state\n\nThe last constraint is generally where services that claim\nto be RESTful fail. Interactions with a server must be\nentirely driven by hypermedia. The client does not need\nany prior knowledge of the service in order to use it,\nother than an entry point and of course basic understanding\nof the media type of the representations, at the very least\nenough to find and identify hyperlinks and link relations.\n\nTo give a simple example, if your service only works with\nthe `application/json` media type then this constraint\ncannot be respected (as there are no concept of links in\nJSON) and thus your service isn't RESTful. This is the case\nfor the majority of self-proclaimed REST services.\n\nOn the other hand if you create a JSON based media type\nthat has a concept of links and link relations, then\nyour service might be RESTful.\n\nRespecting this constraint means that the entirety of the\nservice becomes self-discoverable, not only the resources\nin it, but also the operations you can perform on it. This\nmakes clients very thin as there is no need to implement\nanything specific to the service to operate on it.\n"
  },
  {
    "path": "doc/src/guide/routing.asciidoc",
    "content": "[[routing]]\n== Routing\n\nCowboy does nothing by default.\n\nTo make Cowboy useful, you need to map URIs to Erlang modules that will\nhandle the requests. This is called routing.\n\nCowboy routes requests using the following algorithm:\n\n* If no configured host matches the request URI, a 400 response\n  is returned.\n\n* Otherwise, the first configured host that matches the request\n  URI will be used. Only the paths configured for this host will\n  be considered.\n\n* If none of the configured paths found in the previous step\n  match the request URI, a 404 response is returned.\n\n* Otherwise, the handler and its initial state are added to the\n  environment and the request continues to be processed.\n\nNOTE: It is possible to run into a situation where two hosts match a\nrequest URI, but only the paths on the second host match the\nrequest URI. In this case the expected result is a 404 response\nbecause the only paths used during routing are the paths from\nthe first configured host that matches the request URI.\n\nRoutes need to be compiled before they can be used by Cowboy.\nThe result of the compilation is the dispatch rules.\n\n=== Syntax\n\nThe general structure for the routes is defined as follow.\n\n[source,erlang]\nRoutes = [Host1, Host2, ... HostN].\n\nEach host contains matching rules for the host along with optional\nconstraints, and a list of routes for the path component.\n\n[source,erlang]\nHost1 = {HostMatch, PathsList}.\nHost2 = {HostMatch, Constraints, PathsList}.\n\nThe list of routes for the path component is defined similar to the\nlist of hosts.\n\n[source,erlang]\nPathsList = [Path1, Path2, ... PathN].\n\nFinally, each path contains matching rules for the path along with\noptional constraints, and gives us the handler module to be used\nalong with its initial state.\n\n[source,erlang]\nPath1 = {PathMatch, Handler, InitialState}.\nPath2 = {PathMatch, Constraints, Handler, InitialState}.\n\nContinue reading to learn more about the match syntax and the optional\nconstraints.\n\n=== Match syntax\n\nThe match syntax is used to associate host names and paths with their\nrespective handlers.\n\nThe match syntax is the same for host and path with a few subtleties.\nIndeed, the segments separator is different, and the host is matched\nstarting from the last segment going to the first. All examples will\nfeature both host and path match rules and explain the differences\nwhen encountered.\n\nExcluding special values that we will explain at the end of this section,\nthe simplest match value is a host or a path. It can be given as either\na `string()` or a `binary()`.\n\n[source,erlang]\n----\nPathMatch1 = \"/\".\nPathMatch2 = \"/path/to/resource\".\n\nHostMatch1 = \"cowboy.example.org\".\n----\n\nAs you can see, all paths defined this way must start with a slash\ncharacter. Note that these two paths are identical as far as routing\nis concerned.\n\n[source,erlang]\nPathMatch2 = \"/path/to/resource\".\nPathMatch3 = \"/path/to/resource/\".\n\nHosts with and without a trailing dot are equivalent for routing.\nSimilarly, hosts with and without a leading dot are also equivalent.\n\n[source,erlang]\nHostMatch1 = \"cowboy.example.org\".\nHostMatch2 = \"cowboy.example.org.\".\nHostMatch3 = \".cowboy.example.org\".\n\nIt is possible to extract segments of the host and path and to store\nthe values in the `Req` object for later use. We call these kind of\nvalues bindings.\n\nThe syntax for bindings is very simple. A segment that begins with\nthe `:` character means that what follows until the end of the segment\nis the name of the binding in which the segment value will be stored.\n\n[source,erlang]\nPathMatch = \"/hats/:name/prices\".\nHostMatch = \":subdomain.example.org\".\n\nIf these two end up matching when routing, you will end up with two\nbindings defined, `subdomain` and `name`, each containing the\nsegment value where they were defined. For example, the URL\n`http://test.example.org/hats/wild_cowboy_legendary/prices` will\nresult in having the value `test` bound to the name `subdomain`\nand the value `wild_cowboy_legendary` bound to the name `name`.\nThey can later be retrieved using `cowboy_req:binding/{2,3}`. The\nbinding name must be given as an atom.\n\nThere is a special binding name you can use to mimic the underscore\nvariable in Erlang. Any match against the `_` binding will succeed\nbut the data will be discarded. This is especially useful for\nmatching against many domain names in one go.\n\n[source,erlang]\nHostMatch = \"ninenines.:_\".\n\nSimilarly, it is possible to have optional segments. Anything\nbetween brackets is optional.\n\n[source,erlang]\nPathMatch = \"/hats/[page/:number]\".\nHostMatch = \"[www.]ninenines.eu\".\n\nYou can also have imbricated optional segments.\n\n[source,erlang]\nPathMatch = \"/hats/[page/[:number]]\".\n\nWhile Cowboy does not reject multiple brackets in a route,\nthe behavior may be undefined if the route is under-specified.\nFor example, this route requires constraints to determine what\nis a chapter and what is a page, since they are both optional:\n\n[source,erlang]\nPathMatch = \"/book/[:chapter]/[:page]\".\n\nYou can retrieve the rest of the host or path using `[...]`.\nIn the case of hosts it will match anything before, in the case\nof paths anything after the previously matched segments. It is\na special case of optional segments, in that it can have\nzero, one or many segments. You can then find the segments using\n`cowboy_req:host_info/1` and `cowboy_req:path_info/1` respectively.\nThey will be represented as a list of segments.\n\n[source,erlang]\nPathMatch = \"/hats/[...]\".\nHostMatch = \"[...]ninenines.eu\".\n\nIf a binding appears twice in the routing rules, then the match\nwill succeed only if they share the same value. This copies the\nErlang pattern matching behavior.\n\n[source,erlang]\nPathMatch = \"/hats/:name/:name\".\n\nThis is also true when an optional segment is present. In this\ncase the two values must be identical only if the segment is\navailable.\n\n[source,erlang]\nPathMatch = \"/hats/:name/[:name]\".\n\nIf a binding is defined in both the host and path, then they must\nalso share the same value.\n\n[source,erlang]\nPathMatch = \"/:user/[...]\".\nHostMatch = \":user.github.com\".\n\nFinally, there are two special match values that can be used. The\nfirst is the atom `'_'` which will match any host or path.\n\n[source,erlang]\nPathMatch = '_'.\nHostMatch = '_'.\n\nThe second is the special host match `\"*\"` which will match the\nwildcard path, generally used alongside the `OPTIONS` method.\n\n[source,erlang]\nHostMatch = \"*\".\n\n=== Constraints\n\nAfter the matching has completed, the resulting bindings can be tested\nagainst a set of constraints. Constraints are only tested when the\nbinding is defined. They run in the order you defined them. The match\nwill succeed only if they all succeed. If the match fails, then Cowboy\ntries the next route in the list.\n\nThe format used for constraints is the same as match functions in\n`cowboy_req`: they are provided as a list of fields which may have\none or more constraints. While the router accepts the same format,\nit will skip fields with no constraints and will also ignore default\nvalues, if any.\n\nRead more about xref:constraints[constraints].\n\n=== Compilation\n\nThe routes must be compiled before Cowboy can use them. The compilation\nstep normalizes the routes to simplify the code and speed up the\nexecution, but the routes are still looked up one by one in the end.\nFaster compilation strategies could be to compile the routes directly\nto Erlang code, but would require heavier dependencies.\n\nTo compile routes, just call the appropriate function:\n\n[source,erlang]\n----\nDispatch = cowboy_router:compile([\n    %% {HostMatch, list({PathMatch, Handler, InitialState})}\n    {'_', [{'_', my_handler, #{}}]}\n]),\n%% Name, TransOpts, ProtoOpts\ncowboy:start_clear(my_http_listener,\n    [{port, 8080}],\n    #{env => #{dispatch => Dispatch}}\n).\n----\n\n=== Using persistent_term\n\nThe routes can be stored in `persistent_term` starting from\nErlang/OTP 21.2. This may give a performance improvement when\nthere are a large number of routes.\n\nTo use this functionality you need to compile the routes,\nstore them in `persistent_term` and then inform Cowboy:\n\n[source,erlang]\n----\nDispatch = cowboy_router:compile([\n    {'_', [{'_', my_handler, #{}}]}\n]),\npersistent_term:put(my_app_dispatch, Dispatch),\ncowboy:start_clear(my_http_listener,\n    [{port, 8080}],\n    #{env => #{dispatch => {persistent_term, my_app_dispatch}}}\n).\n----\n\n=== Live update\n\nYou can use the `cowboy:set_env/3` function for updating the dispatch\nlist used by routing. This will apply to all new connections accepted\nby the listener:\n\n[source,erlang]\nDispatch = cowboy_router:compile(Routes),\ncowboy:set_env(my_http_listener, dispatch, Dispatch).\n\nNote that you need to compile the routes again before updating.\n\nWhen using `persistent_term` there is no need to call this function,\nyou can simply put the new routes in the storage.\n"
  },
  {
    "path": "doc/src/guide/specs.asciidoc",
    "content": "[appendix]\n== HTTP and other specifications\n\nThis chapter intends to list all the specification documents\nfor or related to HTTP.\n\n=== HTTP\n\n==== IANA Registries\n\n* https://www.iana.org/assignments/http-methods/http-methods.xhtml[HTTP Method Registry]\n* https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml[HTTP Status Code Registry]\n* https://www.iana.org/assignments/message-headers/message-headers.xhtml[Message Headers]\n* https://www.iana.org/assignments/http-parameters/http-parameters.xhtml[HTTP Parameters]\n* https://www.iana.org/assignments/http-alt-svc-parameters/http-alt-svc-parameters.xhtml[HTTP Alt-Svc Parameter Registry]\n* https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml[HTTP Authentication Scheme Registry]\n* https://www.iana.org/assignments/http-cache-directives/http-cache-directives.xhtml[HTTP Cache Directive Registry]\n* https://www.iana.org/assignments/http-dig-alg/http-dig-alg.xhtml[HTTP Digest Algorithm Values]\n* https://www.iana.org/assignments/hoba-device-identifiers/hoba-device-identifiers.xhtml[HTTP Origin-Bound Authentication Device Identifier Types]\n* https://www.iana.org/assignments/http-upgrade-tokens/http-upgrade-tokens.xhtml[HTTP Upgrade Token Registry]\n* https://www.iana.org/assignments/http-warn-codes/http-warn-codes.xhtml[HTTP Warn Codes]\n* https://www.iana.org/assignments/http2-parameters/http2-parameters.xhtml[HTTP/2 Parameters]\n* https://www.ietf.org/assignments/websocket/websocket.xml[WebSocket Protocol Registries]\n\n==== Current\n\n* http://www.w3.org/TR/cors/[CORS]: Cross-Origin Resource Sharing\n* http://www.w3.org/TR/CSP2/[CSP2]: Content Security Policy Level 2\n* http://www.w3.org/TR/tracking-dnt/[DNT]: Tracking Preference Expression (DNT)\n* http://www.w3.org/TR/eventsource/[eventsource]: Server-Sent Events\n* https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4[Form content types]: Form content types\n* https://www.w3.org/TR/preload/[Preload]: Preload\n* https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt[PROXY]: The PROXY protocol\n* http://www.ics.uci.edu/~fielding/pubs/dissertation/rest_arch_style.htm[REST]: Fielding's Dissertation\n* https://tools.ietf.org/html/rfc1945[RFC 1945]: HTTP/1.0\n* https://tools.ietf.org/html/rfc1951[RFC 1951]: DEFLATE Compressed Data Format Specification version 1.3\n* https://tools.ietf.org/html/rfc1952[RFC 1952]: GZIP file format specification version 4.3\n* https://tools.ietf.org/html/rfc2046#section-5.1[RFC 2046]: Multipart media type (in MIME Part Two: Media Types)\n* https://tools.ietf.org/html/rfc2295[RFC 2295]: Transparent Content Negotiation in HTTP\n* https://tools.ietf.org/html/rfc2296[RFC 2296]: HTTP Remote Variant Selection Algorithm: RVSA/1.0\n* https://tools.ietf.org/html/rfc2817[RFC 2817]: Upgrading to TLS Within HTTP/1.1\n* https://tools.ietf.org/html/rfc2818[RFC 2818]: HTTP Over TLS\n* https://tools.ietf.org/html/rfc3230[RFC 3230]: Instance Digests in HTTP\n* https://tools.ietf.org/html/rfc4559[RFC 4559]: SPNEGO-based Kerberos and NTLM HTTP Authentication in Microsoft Windows\n* https://tools.ietf.org/html/rfc5789[RFC 5789]: PATCH Method for HTTP\n* https://tools.ietf.org/html/rfc5843[RFC 5843]: Additional Hash Algorithms for HTTP Instance Digests\n* https://tools.ietf.org/html/rfc5861[RFC 5861]: HTTP Cache-Control Extensions for Stale Content\n* https://tools.ietf.org/html/rfc6265[RFC 6265]: HTTP State Management Mechanism\n* https://tools.ietf.org/html/rfc6266[RFC 6266]: Use of the Content-Disposition Header Field\n* https://tools.ietf.org/html/rfc6454[RFC 6454]: The Web Origin Concept\n* https://tools.ietf.org/html/rfc6455[RFC 6455]: The WebSocket Protocol\n* https://tools.ietf.org/html/rfc6585[RFC 6585]: Additional HTTP Status Codes\n* https://tools.ietf.org/html/rfc6750[RFC 6750]: The OAuth 2.0 Authorization Framework: Bearer Token Usage\n* https://tools.ietf.org/html/rfc6797[RFC 6797]: HTTP Strict Transport Security (HSTS)\n* https://tools.ietf.org/html/rfc6903[RFC 6903]: Additional Link Relation Types\n* https://tools.ietf.org/html/rfc7034[RFC 7034]: HTTP Header Field X-Frame-Options\n* https://tools.ietf.org/html/rfc7089[RFC 7089]: Time-Based Access to Resource States: Memento\n* https://tools.ietf.org/html/rfc7230[RFC 7230]: HTTP/1.1 Message Syntax and Routing\n* https://tools.ietf.org/html/rfc7231[RFC 7231]: HTTP/1.1 Semantics and Content\n* https://tools.ietf.org/html/rfc7232[RFC 7232]: HTTP/1.1 Conditional Requests\n* https://tools.ietf.org/html/rfc7233[RFC 7233]: HTTP/1.1 Range Requests\n* https://tools.ietf.org/html/rfc7234[RFC 7234]: HTTP/1.1 Caching\n* https://tools.ietf.org/html/rfc7235[RFC 7235]: HTTP/1.1 Authentication\n* https://tools.ietf.org/html/rfc7239[RFC 7239]: Forwarded HTTP Extension\n* https://tools.ietf.org/html/rfc7240[RFC 7240]: Prefer Header for HTTP\n* https://tools.ietf.org/html/rfc7469[RFC 7469]: Public Key Pinning Extension for HTTP\n* https://tools.ietf.org/html/rfc7486[RFC 7486]: HTTP Origin-Bound Authentication (HOBA)\n* https://tools.ietf.org/html/rfc7538[RFC 7538]: HTTP Status Code 308 (Permanent Redirect)\n* https://tools.ietf.org/html/rfc7540[RFC 7540]: Hypertext Transfer Protocol Version 2 (HTTP/2)\n* https://tools.ietf.org/html/rfc7541[RFC 7541]: HPACK: Header Compression for HTTP/2\n* https://tools.ietf.org/html/rfc7578[RFC 7578]: Returning Values from Forms: multipart/form-data\n* https://tools.ietf.org/html/rfc7615[RFC 7615]: HTTP Authentication-Info and Proxy-Authentication-Info Response Header Fields\n* https://tools.ietf.org/html/rfc7616[RFC 7616]: HTTP Digest Access Authentication\n* https://tools.ietf.org/html/rfc7617[RFC 7617]: The 'Basic' HTTP Authentication Scheme\n* https://tools.ietf.org/html/rfc7639[RFC 7639]: The ALPN HTTP Header Field\n* https://tools.ietf.org/html/rfc7692[RFC 7692]: Compression Extensions for WebSocket\n* https://tools.ietf.org/html/rfc7694[RFC 7694]: HTTP Client-Initiated Content-Encoding\n* https://tools.ietf.org/html/rfc7725[RFC 7725]: An HTTP Status Code to Report Legal Obstacles\n* https://tools.ietf.org/html/rfc7804[RFC 7804]: Salted Challenge Response HTTP Authentication Mechanism\n* https://tools.ietf.org/html/rfc7838[RFC 7838]: HTTP Alternative Services\n* https://tools.ietf.org/html/rfc7932[RFC 7932]: Brotli Compressed Data Format\n* https://tools.ietf.org/html/rfc7936[RFC 7936]: Clarifying Registry Procedures for the WebSocket Subprotocol Name Registry\n* https://tools.ietf.org/html/rfc8053[RFC 8053]: HTTP Authentication Extensions for Interactive Clients\n* https://tools.ietf.org/html/rfc8164[RFC 8164]: Opportunistic Security for HTTP/2\n* https://tools.ietf.org/html/rfc8187[RFC 8187]: Indicating Character Encoding and Language for HTTP Header Field Parameters\n* https://tools.ietf.org/html/rfc8188[RFC 8188]: Encrypted Content-Encoding for HTTP\n* https://tools.ietf.org/html/rfc8246[RFC 8246]: HTTP Immutable Responses\n* https://tools.ietf.org/html/rfc8288[RFC 8288]: Web Linking\n* https://tools.ietf.org/html/rfc8297[RFC 8297]: An HTTP Status Code for Indicating Hints\n* https://tools.ietf.org/html/rfc8336[RFC 8336]: The ORIGIN HTTP/2 Frame\n* https://tools.ietf.org/html/rfc8441[RFC 8441]: Bootstrapping WebSockets with HTTP/2\n* https://tools.ietf.org/html/rfc8470[RFC 8470]: Using Early Data in HTTP\n* https://tools.ietf.org/html/rfc8473[RFC 8473]: Token Binding over HTTP\n* https://tools.ietf.org/html/rfc8586[RFC 8586]: Loop Detection in Content Delivery Networks (CDNs)\n* https://tools.ietf.org/html/rfc8594[RFC 8594]: The Sunset HTTP Header Field\n* https://tools.ietf.org/html/rfc8673[RFC 8673]: HTTP Random Access and Live Content\n* https://tools.ietf.org/html/rfc8674[RFC 8674]: The \"safe\" HTTP Preference\n* https://tools.ietf.org/html/rfc8740[RFC 8740]: Using TLS 1.3 with HTTP/2\n* https://tools.ietf.org/html/rfc8941[RFC 8941]: Structured Field Values for HTTP\n* https://tools.ietf.org/html/rfc8942[RFC 8942]: HTTP Client Hints\n* https://www.w3.org/TR/trace-context/[Trace Context]: Trace Context\n* https://www.w3.org/TR/webmention/[Webmention]: Webmention\n\n==== Upcoming\n\n* https://www.w3.org/TR/clear-site-data/[Clear Site Data]\n* https://www.w3.org/TR/csp-cookies/[Content Security Policy: Cookie Controls]\n* https://www.w3.org/TR/csp-embedded-enforcement/[Content Security Policy: Embedded Enforcement]\n* https://www.w3.org/TR/CSP3/[Content Security Policy Level 3]\n* https://www.w3.org/TR/csp-pinning/[Content Security Policy Pinning]\n* http://www.w3.org/TR/referrer-policy/[Referrer Policy]\n* http://www.w3.org/TR/UISecurity/[User Interface Security Directives for Content Security Policy]\n\n==== Informative\n\n* http://www.w3.org/TR/webarch/[Architecture of the World Wide Web]\n* https://tools.ietf.org/html/rfc2936[RFC 2936]: HTTP MIME Type Handler Detection\n* https://tools.ietf.org/html/rfc2964[RFC 2964]: Use of HTTP State Management\n* https://tools.ietf.org/html/rfc3143[RFC 3143]: Known HTTP Proxy/Caching Problems\n* https://tools.ietf.org/html/rfc6202[RFC 6202]: Known Issues and Best Practices for the Use of Long Polling and Streaming in Bidirectional HTTP\n* https://tools.ietf.org/html/rfc6838[RFC 6838]: Media Type Specifications and Registration Procedures\n* https://tools.ietf.org/html/rfc7478[RFC 7478]: Web Real-Time Communication Use Cases and Requirements\n\n==== Related\n\n* http://www.w3.org/TR/app-uri/[app: URL Scheme]\n* http://www.w3.org/TR/beacon/[Beacon]\n* http://www.w3.org/TR/FileAPI/[File API]\n* https://tools.ietf.org/html/rfc8030[Generic Event Delivery Using HTTP Push]\n* http://www.w3.org/TR/capability-urls/[Good Practices for Capability URLs]\n* https://html.spec.whatwg.org/multipage/[HTML Living Standard]\n* https://developers.whatwg.org/[HTML Living Standard for Web developers]\n* http://www.w3.org/TR/html401/[HTML4.01]\n* http://www.w3.org/TR/html5/[HTML5]\n* http://www.w3.org/TR/html51/[HTML5.1]\n* https://www.w3.org/TR/html52/[HTML5.2]\n* http://www.w3.org/TR/media-frags/[Media Fragments URI 1.0]\n* https://tools.ietf.org/html/rfc5829[RFC 5829]: Link Relation Types for Simple Version Navigation between Web Resources\n* https://tools.ietf.org/html/rfc6657[RFC 6657]: Update to MIME regarding \"charset\" Parameter Handling in Textual Media Types\n* https://tools.ietf.org/html/rfc6690[RFC 6690]: Constrained RESTful Environments (CoRE) Link Format\n* https://tools.ietf.org/html/rfc7807[RFC 7807]: Problem Details for HTTP APIs\n* https://tools.ietf.org/html/rfc6906[RFC 6906]: The 'profile' Link Relation Type\n* https://tools.ietf.org/html/rfc8631[RFC 8631]: Link Relation Types for Web Services\n* http://www.w3.org/TR/SRI/[Subresource Integrity]\n* http://www.w3.org/TR/tracking-compliance/[Tracking Compliance and Scope]\n* http://www.w3.org/TR/media-frags-reqs/[Use cases and requirements for Media Fragments]\n* http://www.w3.org/TR/webrtc/[WebRTC 1.0: Real-time Communication Between Browsers]\n* http://www.w3.org/TR/websockets/[Websocket API]\n* http://www.w3.org/TR/XMLHttpRequest/[XMLHttpRequest Level 1]\n* https://xhr.spec.whatwg.org/[XMLHttpRequest Living Standard]\n\n==== Seemingly obsolete\n\n* https://tools.ietf.org/html/rfc2227[RFC 2227]: Simple Hit-Metering and Usage-Limiting for HTTP\n* https://tools.ietf.org/html/rfc2310[RFC 2310]: The Safe Response Header Field\n* https://tools.ietf.org/html/rfc2324[RFC 2324]: Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0)\n* https://tools.ietf.org/html/rfc2660[RFC 2660]: The Secure HyperText Transfer Protocol\n* https://tools.ietf.org/html/rfc2774[RFC 2774]: An HTTP Extension Framework\n* https://tools.ietf.org/html/rfc2965[RFC 2965]: HTTP State Management Mechanism (Cookie2)\n* https://tools.ietf.org/html/rfc3229[RFC 3229]: Delta encoding in HTTP\n* https://tools.ietf.org/html/rfc7168[RFC 7168]: The Hyper Text Coffee Pot Control Protocol for Tea Efflux Appliances (HTCPCP-TEA)\n* https://tools.ietf.org/html/rfc8565[RFC 8565]: Hypertext Jeopardy Protocol (HTJP/1.0)\n* http://dev.chromium.org/spdy/spdy-protocol[SPDY]: SPDY Protocol\n* https://tools.ietf.org/html/draft-tyoshino-hybi-websocket-perframe-deflate-06[x-webkit-deflate-frame]: Deprecated Websocket compression\n\n=== URL\n\n* https://tools.ietf.org/html/rfc3986[RFC 3986]: URI Generic Syntax\n* https://tools.ietf.org/html/rfc6570[RFC 6570]: URI Template\n* https://tools.ietf.org/html/rfc6874[RFC 6874]: Representing IPv6 Zone Identifiers in Address Literals and URIs\n* https://tools.ietf.org/html/rfc7320[RFC 7320]: URI Design and Ownership\n* https://tools.ietf.org/html/rfc8615[RFC 8615]: Well-Known URIs\n* http://www.w3.org/TR/url-1/[URL]\n* https://url.spec.whatwg.org/[URL Living Standard]\n\n=== WebDAV\n\n* https://tools.ietf.org/html/rfc3253[RFC 3253]: Versioning Extensions to WebDAV\n* https://tools.ietf.org/html/rfc3648[RFC 3648]: WebDAV Ordered Collections Protocol\n* https://tools.ietf.org/html/rfc3744[RFC 3744]: WebDAV Access Control Protocol\n* https://tools.ietf.org/html/rfc4316[RFC 4316]: Datatypes for WebDAV Properties\n* https://tools.ietf.org/html/rfc4331[RFC 4331]: Quota and Size Properties for DAV Collections\n* https://tools.ietf.org/html/rfc4437[RFC 4437]: WebDAV Redirect Reference Resources\n* https://tools.ietf.org/html/rfc4709[RFC 4709]: Mounting WebDAV Servers\n* https://tools.ietf.org/html/rfc4791[RFC 4791]: Calendaring Extensions to WebDAV (CalDAV)\n* https://tools.ietf.org/html/rfc4918[RFC 4918]: HTTP Extensions for WebDAV\n* https://tools.ietf.org/html/rfc5323[RFC 5323]: WebDAV SEARCH\n* https://tools.ietf.org/html/rfc5397[RFC 5397]: WebDAV Current Principal Extension\n* https://tools.ietf.org/html/rfc5689[RFC 5689]: Extended MKCOL for WebDAV\n* https://tools.ietf.org/html/rfc5842[RFC 5842]: Binding Extensions to WebDAV\n* https://tools.ietf.org/html/rfc5995[RFC 5995]: Using POST to Add Members to WebDAV Collections\n* https://tools.ietf.org/html/rfc6352[RFC 6352]: CardDAV: vCard Extensions to WebDAV\n* https://tools.ietf.org/html/rfc6578[RFC 6578]: Collection Synchronization for WebDAV\n* https://tools.ietf.org/html/rfc6638[RFC 6638]: Scheduling Extensions to CalDAV\n* https://tools.ietf.org/html/rfc6764[RFC 6764]: Locating Services for Calendaring Extensions to WebDAV (CalDAV) and vCard Extensions to WebDAV (CardDAV)\n* https://tools.ietf.org/html/rfc7809[RFC 7809]: Calendaring Extensions to WebDAV (CalDAV): Time Zones by Reference\n* https://tools.ietf.org/html/rfc7953[RFC 7953]: Calendar Availability\n* https://tools.ietf.org/html/rfc8144[RFC 8144]: Use of the Prefer Header Field in WebDAV\n* https://tools.ietf.org/html/rfc8607[RFC 8607]: Calendaring Extensions to WebDAV (CalDAV): Managed Attachments\n\n=== CoAP\n\n* https://tools.ietf.org/html/rfc7252[RFC 7252]: The Constrained Application Protocol (CoAP)\n* https://tools.ietf.org/html/rfc7390[RFC 7390]: Group Communication for CoAP\n* https://tools.ietf.org/html/rfc7641[RFC 7641]: Observing Resources in CoAP\n* https://tools.ietf.org/html/rfc7650[RFC 7650]: A CoAP Usage for REsource LOcation And Discovery (RELOAD)\n* https://tools.ietf.org/html/rfc7959[RFC 7959]: Block-Wise Transfers in CoAP\n* https://tools.ietf.org/html/rfc7967[RFC 7967]: CoAP Option for No Server Response\n* https://tools.ietf.org/html/rfc8075[RFC 8075]: Guidelines for Mapping Implementations: HTTP to CoAP\n* https://tools.ietf.org/html/rfc8132[RFC 8132]: PATCH and FETCH Methods for CoAP\n* https://tools.ietf.org/html/rfc8323[RFC 8323]: CoAP over TCP, TLS, and WebSockets\n* https://tools.ietf.org/html/rfc8516[RFC 8516]: \"Too Many Requests\" Response Code for CoAP\n* https://tools.ietf.org/html/rfc8613[RFC 8613]: Object Security for Constrained RESTful Environments\n* https://tools.ietf.org/html/rfc8710[RFC 8710]: Multipart Content-Format for CoAP\n* https://tools.ietf.org/html/rfc8768[RFC 8768]: CoAP Hop-Limit Option\n"
  },
  {
    "path": "doc/src/guide/static_files.asciidoc",
    "content": "[[static_files]]\n== Static files\n\nCowboy comes with a ready to use handler for serving static\nfiles. It is provided as a convenience for serving files\nduring development.\n\nFor systems in production, consider using one of the many\nContent Distribution Network (CDN) available on the market,\nas they are the best solution for serving files.\n\nThe static handler can serve either one file or all files\nfrom a given directory. The etag generation and mime types\ncan be configured.\n\n=== Serve one file\n\nYou can use the static handler to serve one specific file\nfrom an application's private directory. This is particularly\nuseful to serve an 'index.html' file when the client requests\nthe `/` path, for example. The path configured is relative\nto the given application's private directory.\n\nThe following rule will serve the file 'static/index.html'\nfrom the application `my_app`'s priv directory whenever the\npath `/` is accessed:\n\n[source,erlang]\n{\"/\", cowboy_static, {priv_file, my_app, \"static/index.html\"}}\n\nYou can also specify the absolute path to a file, or the\npath to the file relative to the current directory:\n\n[source,erlang]\n{\"/\", cowboy_static, {file, \"/var/www/index.html\"}}\n\n=== Serve all files from a directory\n\nYou can also use the static handler to serve all files that\ncan be found in the configured directory. The handler will\nuse the `path_info` information to resolve the file location,\nwhich means that your route must end with a `[...]` pattern\nfor it to work. All files are served, including the ones that\nmay be found in subfolders.\n\nYou can specify the directory relative to the application's\nprivate directory (e.g. `my_app/priv`).\n\nThe following rule will serve any file found in the `my_app`\napplication's private directory in the `my_app/priv/static/assets`\nfolder whenever the requested path begins with `/assets/`:\n\n[source,erlang]\n{\"/assets/[...]\", cowboy_static, {priv_dir, my_app, \"static/assets\"}}\n\nYou can also specify the absolute path to the directory or\nset it relative to the current directory:\n\n[source,erlang]\n{\"/assets/[...]\", cowboy_static, {dir, \"/var/www/assets\"}}\n\n=== Customize the mimetype detection\n\nBy default, Cowboy will attempt to recognize the mimetype\nof your static files by looking at the extension.\n\nYou can override the function that figures out the mimetype\nof the static files. It can be useful when Cowboy is missing\na mimetype you need to handle, or when you want to reduce\nthe list to make lookups faster. You can also give a\nhard-coded mimetype that will be used unconditionally.\n\nCowboy comes with two functions built-in. The default\nfunction only handles common file types used when building\nWeb applications. The other function is an extensive list\nof hundreds of mimetypes that should cover almost any need\nyou may have. You can of course create your own function.\n\nTo use the default function, you should not have to configure\nanything, as it is the default. If you insist, though, the\nfollowing will do the job:\n\n[source,erlang]\n----\n{\"/assets/[...]\", cowboy_static, {priv_dir, my_app, \"static/assets\",\n    [{mimetypes, cow_mimetypes, web}]}}\n----\n\nAs you can see, there is an optional field that may contain\na list of less used options, like mimetypes or etag. All option\ntypes have this optional field.\n\nTo use the function that will detect almost any mimetype,\nthe following configuration will do:\n\n[source,erlang]\n----\n{\"/assets/[...]\", cowboy_static, {priv_dir, my_app, \"static/assets\",\n    [{mimetypes, cow_mimetypes, all}]}}\n----\n\nYou probably noticed the pattern by now. The configuration\nexpects a module and a function name, so you can use any\nof your own functions instead:\n\n[source,erlang]\n----\n{\"/assets/[...]\", cowboy_static, {priv_dir, my_app, \"static/assets\",\n    [{mimetypes, Module, Function}]}}\n----\n\nThe function that performs the mimetype detection receives\na single argument that is the path to the file on disk. It\nis recommended to return the mimetype in tuple form, although\na binary string is also allowed (but will require extra\nprocessing). If the function can't figure out the mimetype,\nthen it should return `{<<\"application\">>, <<\"octet-stream\">>, []}`.\n\nWhen the static handler fails to find the extension,\nit will send the file as `application/octet-stream`.\nA browser receiving such file will attempt to download it\ndirectly to disk.\n\nFinally, the mimetype can be hard-coded for all files.\nThis is especially useful in combination with the `file`\nand `priv_file` options as it avoids needless computation:\n\n[source,erlang]\n----\n{\"/\", cowboy_static, {priv_file, my_app, \"static/index.html\",\n    [{mimetypes, {<<\"text\">>, <<\"html\">>, []}}]}}\n----\n\n=== Generate an etag\n\nBy default, the static handler will generate an etag header\nvalue based on the size and modified time. This solution\ncan not be applied to all systems though. It would perform\nrather poorly over a cluster of nodes, for example, as the\nfile metadata will vary from server to server, giving a\ndifferent etag on each server.\n\nYou can however change the way the etag is calculated:\n\n[source,erlang]\n----\n{\"/assets/[...]\", cowboy_static, {priv_dir, my_app, \"static/assets\",\n    [{etag, Module, Function}]}}\n----\n\nThis function will receive three arguments: the path to the\nfile on disk, the size of the file and the last modification\ntime. In a distributed setup, you would typically use the\nfile path to retrieve an etag value that is identical across\nall your servers.\n\nYou can also completely disable etag handling:\n\n[source,erlang]\n----\n{\"/assets/[...]\", cowboy_static, {priv_dir, my_app, \"static/assets\",\n    [{etag, false}]}}\n----\n"
  },
  {
    "path": "doc/src/guide/streams.asciidoc",
    "content": "[[streams]]\n== Streams\n\nA stream is the set of messages that form an HTTP\nrequest/response pair.\n\nThe term stream comes from HTTP/2. In Cowboy, it is\nalso used when talking about HTTP/1.1 or HTTP/1.0.\nIt should not be confused with streaming the request\nor response body.\n\nAll versions of HTTP allow clients to initiate\nstreams. HTTP/2 is the only one also allowing servers,\nthrough its server push feature. Both client and\nserver-initiated streams go through the same process\nin Cowboy.\n\n=== Stream handlers\n\nlink:man:cowboy_stream(3)[Stream handlers]\nmust implement five different callbacks.\nFour of them are directly related; one is special.\n\nAll callbacks receives the stream ID as first argument.\n\nMost of them can return a list of commands to be executed\nby Cowboy. When callbacks are chained, it is possible to\nintercept and modify these commands. This can be useful\nfor modifying responses for example.\n\nThe `init/3` callback is invoked when a new request\ncomes in. It receives the Req object and the protocol options\nfor this listener.\n\nThe `data/4` callback is invoked when data from the request\nbody is received. It receives both this data and a flag\nindicating whether more is to be expected.\n\nThe `info/3` callback is invoked when an Erlang message is\nreceived for this stream. They will typically be messages\nsent by the request process.\n\nFinally the `terminate/3` callback is invoked with the\nterminate reason for the stream. The return value is ignored.\nNote that as with all terminate callbacks in Erlang, there\nis no strong guarantee that it will be called.\n\nThe special callback `early_error/5` is called when an error\noccurs before the request headers were fully received and\nCowboy is sending a response. It receives the partial Req\nobject, the error reason, the protocol options and the response\nCowboy will send. This response must be returned, possibly\nmodified.\n\n=== Built-in handlers\n\nCowboy comes with four handlers.\n\nlink:man:cowboy_stream_h(3)[cowboy_stream_h] is the default\nstream handler. It is the core of much of the functionality\nof Cowboy. All chains of stream handlers should call it last.\n\nlink:man:cowboy_compress_h(3)[cowboy_compress_h] will\nautomatically compress responses when possible. It is not\nenabled by default. It is a good example for writing your\nown handlers that will modify responses.\n\nlink:man:cowboy_decompress_h(3)[cowboy_decompress_h] will\nautomatically decompress request bodies when possible.\nIt is not enabled by default. It is a good example for\nwriting your own handlers that will modify requests.\n\nlink:man:cowboy_metrics_h(3)[cowboy_metrics_h] gathers\nmetrics about a stream then passes them to a configurable\nfunction. It is not enabled by default.\n\nlink:man:cowboy_tracer_h(3)[cowboy_tracer_h] can be used to\nconditionally trace streams based on the contents of the\nrequest or its origin. Trace events are passed to a\nconfigurable function. It is not enabled by default.\n"
  },
  {
    "path": "doc/src/guide/ws_handlers.asciidoc",
    "content": "[[ws_handlers]]\n== Websocket handlers\n\nWebsocket handlers provide an interface for upgrading HTTP/1.1\nconnections to Websocket and sending or receiving frames on\nthe Websocket connection.\n\nAs Websocket connections are established through the HTTP/1.1\nupgrade mechanism, Websocket handlers need to be able to first\nreceive the HTTP request for the upgrade, before switching to\nWebsocket and taking over the connection. They can then receive\nor send Websocket frames, handle incoming Erlang messages or\nclose the connection.\n\n=== Upgrade\n\nThe `init/2` callback is called when the request is received.\nTo establish a Websocket connection, you must switch to the\n`cowboy_websocket` module:\n\n[source,erlang]\n----\ninit(Req, State) ->\n    {cowboy_websocket, Req, State}.\n----\n\nCowboy will perform the Websocket handshake immediately. Note\nthat the handshake will fail if the client did not request an\nupgrade to Websocket.\n\nThe Req object becomes unavailable after this function returns.\nAny information required for proper execution of the Websocket\nhandler must be saved in the state.\n\n=== Subprotocol\n\nThe client may provide a list of Websocket subprotocols it\nsupports in the sec-websocket-protocol header. The server *must*\nselect one of them and send it back to the client or the\nhandshake will fail.\n\nFor example, a client could understand both STOMP and MQTT over\nWebsocket, and provide the header:\n\n----\nsec-websocket-protocol: v12.stomp, mqtt\n----\n\nIf the server only understands MQTT it can return:\n\n----\nsec-websocket-protocol: mqtt\n----\n\nThis selection must be done in `init/2`. An example usage could\nbe:\n\n[source,erlang]\n----\ninit(Req0, State) ->\n    case cowboy_req:parse_header(<<\"sec-websocket-protocol\">>, Req0) of\n        undefined ->\n            {cowboy_websocket, Req0, State};\n        Subprotocols ->\n            case lists:member(<<\"mqtt\">>, 1, Subprotocols) of\n                true ->\n                    Req = cowboy_req:set_resp_header(<<\"sec-websocket-protocol\">>,\n                        <<\"mqtt\">>, Req0),\n                    {cowboy_websocket, Req, State};\n                false ->\n                    Req = cowboy_req:reply(400, Req0),\n                    {ok, Req, State}\n            end\n    end.\n----\n\n=== Post-upgrade initialization\n\nCowboy has separate processes for handling the connection\nand requests. Because Websocket takes over the connection,\nthe Websocket protocol handling occurs in a different\nprocess than the request handling.\n\nThis is reflected in the different callbacks Websocket\nhandlers have. The `init/2` callback is called from the\ntemporary request process and the `websocket_` callbacks\nfrom the connection process.\n\nThis means that some initialization cannot be done from\n`init/2`. Anything that would require the current pid,\nor be tied to the current pid, will not work as intended.\nThe optional `websocket_init/1` can be used instead:\n\n[source,erlang]\n----\nwebsocket_init(State) ->\n    erlang:start_timer(1000, self(), <<\"Hello!\">>),\n    {ok, State}.\n----\n\nAll Websocket callbacks share the same return values. This\nmeans that we can send frames to the client right after\nthe upgrade:\n\n[source,erlang]\n----\nwebsocket_init(State) ->\n    {[{text, <<\"Hello!\">>}], State}.\n----\n\n=== Receiving frames\n\nCowboy will call `websocket_handle/2` whenever a text, binary,\nping or pong frame arrives from the client.\n\nThe handler can handle or ignore the frames. It can also\nsend frames back to the client or stop the connection.\n\nThe following snippet echoes back any text frame received and\nignores all others:\n\n[source,erlang]\n----\nwebsocket_handle(Frame = {text, _}, State) ->\n    {[Frame], State};\nwebsocket_handle(_Frame, State) ->\n    {ok, State}.\n----\n\nNote that ping and pong frames require no action from the\nhandler as Cowboy will automatically reply to ping frames.\nThey are provided for informative purposes only.\n\n=== Receiving Erlang messages\n\nCowboy will call `websocket_info/2` whenever an Erlang message\narrives.\n\nThe handler can handle or ignore the messages. It can also\nsend frames to the client or stop the connection.\n\nThe following snippet forwards log messages to the client\nand ignores all others:\n\n[source,erlang]\n----\nwebsocket_info({log, Text}, State) ->\n    {[{text, Text}], State};\nwebsocket_info(_Info, State) ->\n    {ok, State}.\n----\n\n=== Sending frames\n\n// @todo This will be deprecated and eventually replaced with a\n// {Commands, State} interface that allows providing more\n// functionality easily.\n\nAll `websocket_` callbacks share return values. They may\nsend zero, one or many frames to the client.\n\nTo send nothing, just return an ok tuple:\n\n[source,erlang]\n----\nwebsocket_info(_Info, State) ->\n    {ok, State}.\n----\n\nTo send one frame, return the frame to be sent:\n\n[source,erlang]\n----\nwebsocket_info(_Info, State) ->\n    {[{text, <<\"Hello!\">>}], State}.\n----\n\nYou can send frames of any type: text, binary, ping, pong\nor close frames.\n\nYou can send many frames at the same time:\n\n[source,erlang]\n----\nwebsocket_info(_Info, State) ->\n    {[\n        {text, \"Hello\"},\n        {text, <<\"world!\">>},\n        {binary, <<0:8000>>}\n    ], State}.\n----\n\nThey are sent in the given order.\n\n=== Keeping the connection alive\n\nCowboy will automatically respond to ping frames sent by\nthe client. They are still forwarded to the handler for\ninformative purposes, but no further action is required.\n\nCowboy does not send ping frames itself. The handler can\ndo it if required. A better solution in most cases is to\nlet the client handle pings. Doing it from the handler\nwould imply having an additional timer per connection and\nthis can be a considerable cost for servers that need to\nhandle large numbers of connections.\n\nCowboy can be configured to close idle connections\nautomatically. It is highly recommended to configure\na timeout here, to avoid having processes linger longer\nthan needed.\n\nThe `init/2` callback can set the timeout to be used\nfor the connection. For example, this would make Cowboy\nclose connections idle for more than 30 seconds:\n\n[source,erlang]\n----\ninit(Req, State) ->\n    {cowboy_websocket, Req, State, #{\n        idle_timeout => 30000}}.\n----\n\nThis value cannot be changed once it is set. It defaults to\n`60000`.\n\n=== Limiting frame sizes\n\nCowboy accepts frames of any size by default. You should\nlimit the size depending on what your handler may handle.\nYou can do this via the `init/2` callback:\n\n[source,erlang]\n----\ninit(Req, State) ->\n    {cowboy_websocket, Req, State, #{\n        max_frame_size => 8000000}}.\n----\n\nThe lack of limit is historical. A future version of\nCowboy will have a more reasonable default.\n\n=== Saving memory\n\nThe Websocket connection process can be set to hibernate\nafter the callback returns.\n\nSimply add an `hibernate` field to the returned tuple:\n\n[source,erlang]\n----\nwebsocket_init(State) ->\n    {[], State, hibernate}.\n\nwebsocket_handle(_Frame, State) ->\n    {[], State, hibernate}.\n\nwebsocket_info(_Info, State) ->\n    {[{text, <<\"Hello!\">>}], State, hibernate}.\n----\n\nIt is highly recommended to write your handlers with\nhibernate enabled, as this allows to greatly reduce the\nmemory usage. Do note however that an increase in the\nCPU usage or latency can be observed instead, in particular\nfor the more busy connections.\n\n=== Closing the connection\n\nThe connection can be closed at any time, either by telling\nCowboy to stop it or by sending a close frame.\n\nTo tell Cowboy to close the connection, use a stop tuple:\n\n[source,erlang]\n----\nwebsocket_info(_Info, State) ->\n    {stop, State}.\n----\n\nSending a `close` frame will immediately initiate the closing\nof the Websocket connection. Note that when sending a list of\nframes that include a close frame, any frame found after the\nclose frame will not be sent.\n\nThe following example sends a close frame with a reason message:\n\n[source,erlang]\n----\nwebsocket_info(_Info, State) ->\n    {[{close, 1000, <<\"some-reason\">>}], State}.\n----\n"
  },
  {
    "path": "doc/src/guide/ws_protocol.asciidoc",
    "content": "[[ws_protocol]]\n== The Websocket protocol\n\nThis chapter explains what Websocket is and why it is\na vital component of soft realtime Web applications.\n\n=== Description\n\nWebsocket is an extension to HTTP that emulates plain TCP\nconnections between the client, typically a Web browser,\nand the server. It uses the HTTP Upgrade mechanism to\nestablish the connection.\n\nWebsocket connections are fully asynchronous, unlike\nHTTP/1.1 (synchronous) and HTTP/2 (asynchronous, but the\nserver can only initiate streams in response to requests).\nWith Websocket, the client and the server can both send\nframes at any time without any restriction. It is closer\nto TCP than any of the HTTP protocols.\n\nWebsocket is an IETF standard. Cowboy supports the standard\nand all drafts that were previously implemented by browsers,\nexcluding the initial flawed draft sometimes known as\n\"version 0\".\n\n=== Websocket vs HTTP/2\n\nFor a few years Websocket was the only way to have a\nbidirectional asynchronous connection with the server.\nThis changed when HTTP/2 was introduced. While HTTP/2\nrequires the client to first perform a request before\nthe server can push data, this is only a minor restriction\nas the client can do so just as it connects.\n\nWebsocket was designed as a kind-of-TCP channel to a\nserver. It only defines the framing and connection\nmanagement and lets the developer implement a protocol\non top of it. For example you could implement IRC over\nWebsocket and use a Javascript IRC client to speak to\nthe server.\n\nHTTP/2 on the other hand is just an improvement over\nthe HTTP/1.1 connection and request/response mechanism.\nIt has the same semantics as HTTP/1.1.\n\nIf all you need is to access an HTTP API, then HTTP/2\nshould be your first choice. On the other hand, if what\nyou need is a different protocol, then you can use\nWebsocket to implement it.\n\n=== Implementation\n\nCowboy implements Websocket as a protocol upgrade. Once the\nupgrade is performed from the `init/2` callback, Cowboy\nswitches to Websocket. Please consult the next chapter for\nmore information on initiating and handling Websocket\nconnections.\n\nThe implementation of Websocket in Cowboy is validated using\nthe Autobahn test suite, which is an extensive suite of tests\ncovering all aspects of the protocol. Cowboy passes the\nsuite with 100% success, including all optional tests.\n\nCowboy's Websocket implementation also includes the\npermessage-deflate and x-webkit-deflate-frame compression\nextensions.\n\nCowboy will automatically use compression when the\n`compress` option is returned from the `init/2` function.\n"
  },
  {
    "path": "doc/src/manual/cowboy.asciidoc",
    "content": "= cowboy(3)\n\n== Name\n\ncowboy - HTTP server\n\n== Description\n\nThe module `cowboy` provides convenience functions for\nmanipulating Ranch listeners.\n\n== Exports\n\n* link:man:cowboy:start_clear(3)[cowboy:start_clear(3)] - Listen for connections using plain TCP\n* link:man:cowboy:start_tls(3)[cowboy:start_tls(3)] - Listen for connections using TLS\n* link:man:cowboy:stop_listener(3)[cowboy:stop_listener(3)] - Stop the given listener\n* link:man:cowboy:get_env(3)[cowboy:get_env(3)] - Retrieve a listener's environment value\n* link:man:cowboy:set_env(3)[cowboy:set_env(3)] - Update a listener's environment value\n\n== Types\n\n=== fields()\n\n[source,erlang]\n----\nfields() :: [Name\n           | {Name, Constraints}\n           | {Name, Constraints, Default}]\n\nName        :: atom()\nConstraints :: Constraint | [Constraint]\nConstraint  :: cowboy_constraints:constraint()\nDefault     :: any()\n----\n\nFields description for match operations.\n\nThis type is used in link:man:cowboy_router(3)[cowboy_router(3)]\nfor matching bindings and in the match functions found in\nlink:man:cowboy_req(3)[cowboy_req(3)].\n\n=== http_headers()\n\n[source,erlang]\n----\nhttp_headers() :: #{binary() => iodata()}\n----\n\nHTTP headers.\n\n=== http_status()\n\n[source,erlang]\n----\nhttp_status() :: non_neg_integer() | binary()\n----\n\nHTTP response status.\n\nA binary status can be used to set a reason phrase. Note\nhowever that HTTP/2 only sends the status code and drops\nthe reason phrase entirely.\n\n=== http_version()\n\n[source,erlang]\n----\nhttp_version() :: 'HTTP/2' | 'HTTP/1.1' | 'HTTP/1.0'\n----\n\nHTTP version.\n\nNote that semantically, HTTP/1.1 and HTTP/2 are equivalent.\n\n=== opts()\n\n[source,erlang]\n----\nopts() :: map()\n----\n\nOptions for the HTTP/1.1, HTTP/2 and Websocket protocols.\n\nThe protocol options are in a map containing all the options for\nthe different protocols that may be involved when connecting\nto the listener, including HTTP/1.1 and HTTP/2.\n\nThe HTTP/1.1 options are documented in the\nlink:man:cowboy_http(3)[cowboy_http(3)] manual\nand the HTTP/2 options in\nlink:man:cowboy_http2(3)[cowboy_http2(3)].\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:ranch(3)[ranch(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy.get_env.asciidoc",
    "content": "= cowboy:get_env(3)\n\n== Name\n\ncowboy:get_env - Retrieve a listener's environment value\n\n== Description\n\n[source,erlang]\n----\nget_env(Name :: ranch:ref(),\n        Key  :: atom())\n    -> any()\n\nget_env(Name    :: ranch:ref(),\n        Key     :: atom(),\n        Default :: any())\n    -> any()\n----\n\nRetrieve an environment value for a previously started\nlistener.\n\nThis function may crash when the key is missing from the\nenvironment and a default value is not provided.\n\n== Arguments\n\nName::\n\nThe name of the listener to access.\n+\nThe name of the listener is the first argument given to the\nlink:man:cowboy:start_clear(3)[cowboy:start_clear(3)],\nlink:man:cowboy:start_tls(3)[cowboy:start_tls(3)] or\nlink:man:ranch:start_listener(3)[ranch:start_listener(3)] function.\n\nKey::\n\nThe key in the environment map. Common keys include `dispatch`\nand `middlewares`.\n\nDefault::\n\nThe default value if the key is missing.\n\n== Return value\n\nThe environment value is returned on success.\n\nIf a default was provided and the key is missing, then the\ndefault value is returned.\n\nAn `exit:badarg` exception is thrown when the listener does\nnot exist.\n\nAn `exit:{badkey, Key}` exception is thrown when the key\nrequested is missing and no default was provided.\n\n== Changelog\n\n* *2.11*: Function introduced.\n\n== Examples\n\n.Retrieve a listener's routes\n[source,erlang]\n----\nDispatch = cowboy:get_env(example, dispatch).\n----\n\n== See also\n\nlink:man:cowboy(3)[cowboy(3)],\nlink:man:cowboy:start_clear(3)[cowboy:start_clear(3)],\nlink:man:cowboy:start_tls(3)[cowboy:start_tls(3)],\nlink:man:cowboy:set_env(3)[cowboy:set_env(3)],\nlink:man:ranch:get_protocol_options(3)[ranch:get_protocol_options(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy.set_env.asciidoc",
    "content": "= cowboy:set_env(3)\n\n== Name\n\ncowboy:set_env - Update a listener's environment value\n\n== Description\n\n[source,erlang]\n----\nset_env(Name  :: ranch:ref(),\n        Key   :: atom(),\n        Value :: any())\n    -> ok\n----\n\nSet or update an environment value for a previously started\nlistener.\n\nThis is most useful for updating the routes dynamically,\nwithout having to restart the listener.\n\nThe new value will only be available to new connections.\nPre-existing connections will still use the old value.\n\n== Arguments\n\nName::\n\nThe name of the listener to update.\n+\nThe name of the listener is the first argument given to the\nlink:man:cowboy:start_clear(3)[cowboy:start_clear(3)],\nlink:man:cowboy:start_tls(3)[cowboy:start_tls(3)] or\nlink:man:ranch:start_listener(3)[ranch:start_listener(3)] function.\n\nKey::\n\nThe key in the environment map. Common keys include `dispatch`\nand `middlewares`.\n\nValue::\n\nThe new value.\n+\nThe type of the value differs depending on the key.\n\n== Return value\n\nThe atom `ok` is returned on success.\n\nAn `exit:badarg` exception is thrown when the listener does\nnot exist.\n\n== Changelog\n\n* *1.0*: Function introduced.\n\n== Examples\n\n.Update a listener's routes\n[source,erlang]\n----\nDispatch = cowboy_router:compile([\n    {'_', [\n        {\"/\", toppage_h, []},\n        {\"/ws\", websocket_h, []}\n    ]}\n]),\n\ncowboy:set_env(example, dispatch, Dispatch).\n----\n\n== See also\n\nlink:man:cowboy(3)[cowboy(3)],\nlink:man:cowboy:start_clear(3)[cowboy:start_clear(3)],\nlink:man:cowboy:start_tls(3)[cowboy:start_tls(3)],\nlink:man:cowboy:get_env(3)[cowboy:get_env(3)],\nlink:man:ranch:set_protocol_options(3)[ranch:set_protocol_options(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy.start_clear.asciidoc",
    "content": "= cowboy:start_clear(3)\n\n== Name\n\ncowboy:start_clear - Listen for connections using plain TCP\n\n== Description\n\n[source,erlang]\n----\nstart_clear(Name          :: ranch:ref(),\n            TransportOpts :: ranch_tcp:opts(),\n            ProtocolOpts  :: opts())\n    -> {ok, ListenerPid :: pid()}\n     | {error, any()}\n----\n\nStart listening for connections over a clear TCP channel.\n\nBoth HTTP/1.1 and HTTP/2 are supported on this listener.\nHTTP/2 has two methods of establishing a connection over\na clear TCP channel. Both the upgrade and the prior knowledge\nmethods are supported.\n\n== Arguments\n\nName::\n\nThe listener name is used to refer to this listener in\nfuture calls, for example when stopping it or when\nupdating the routes defined.\n+\nIt can be any Erlang term. An atom is generally good enough,\nfor example `api`, `my_app_clear` or `my_app_tls`.\n\nTransportOpts::\n\nThe transport options are where the TCP options, including\nthe listener's port number, are defined. Transport options\nare provided as a list of keys and values, for example\n`[{port, 8080}]`.\n+\nThe available options are documented in the\nlink:man:ranch_tcp(3)[ranch_tcp(3)] manual.\n\nProtocolOpts::\n\nThe protocol options are in a map containing all the options for\nthe different protocols that may be involved when connecting\nto the listener, including HTTP/1.1 and HTTP/2.\n+\nThe HTTP/1.1 options are documented in the\nlink:man:cowboy_http(3)[cowboy_http(3)] manual;\nand the HTTP/2 options in\nlink:man:cowboy_http2(3)[cowboy_http2(3)]. Stream handlers\nsuch as link:man:cowboy_stream_h(3)[cowboy_stream_h(3)]\n(which is enabled by default) may also define options.\n\n== Return value\n\nAn ok tuple is returned on success. It contains the pid of\nthe top-level supervisor for the listener.\n\nAn error tuple is returned on error. The error reason may\nbe any Erlang term.\n\nA common error is `eaddrinuse`. It indicates that the port\nconfigured for Cowboy is already in use.\n\n== Changelog\n\n* *2.0*: HTTP/2 support added.\n* *2.0*: Function introduced. Replaces `cowboy:start_http/4`.\n\n== Examples\n\n.Start a listener\n[source,erlang]\n----\nDispatch = cowboy_router:compile([\n    {'_', [\n        {\"/\", toppage_h, []}\n    ]}\n]),\n\n{ok, _} = cowboy:start_clear(example, [{port, 8080}], #{\n    env => #{dispatch => Dispatch}\n}).\n----\n\n.Start a listener on a random port\n[source,erlang]\n----\nName = example,\n\n{ok, _} = cowboy:start_clear(Name, [], #{\n    env => #{dispatch => Dispatch}\n}),\n\nPort = ranch:get_port(Name).\n----\n\n== See also\n\nlink:man:cowboy(3)[cowboy(3)],\nlink:man:cowboy:start_tls(3)[cowboy:start_tls(3)],\nlink:man:cowboy:stop_listener(3)[cowboy:stop_listener(3)],\nlink:man:ranch(3)[ranch(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy.start_tls.asciidoc",
    "content": "= cowboy:start_tls(3)\n\n== Name\n\ncowboy:start_tls - Listen for connections using TLS\n\n== Description\n\n[source,erlang]\n----\nstart_tls(Name          :: ranch:ref(),\n          TransportOpts :: ranch_ssl:opts(),\n          ProtocolOpts  :: opts())\n    -> {ok, ListenerPid :: pid()}\n     | {error, any()}\n----\n\nStart listening for connections over a secure TLS channel.\n\nBoth HTTP/1.1 and HTTP/2 are supported on this listener.\nThe ALPN TLS extension must be used to initiate an HTTP/2\nconnection.\n\n== Arguments\n\nName::\n\nThe listener name is used to refer to this listener in\nfuture calls, for example when stopping it or when\nupdating the routes defined.\n+\nIt can be any Erlang term. An atom is generally good enough,\nfor example `api`, `my_app_clear` or `my_app_tls`.\n\nTransportOpts::\n\nThe transport options are where the TCP options, including\nthe listener's port number, are defined. They also contain\nthe TLS options, like the server's certificate. Transport options\nare provided as a list of keys and values, for example\n`[{port, 8443}, {certfile, \"path/to/cert.pem\"}]`.\n+\nThe available options are documented in the\nlink:man:ranch_ssl(3)[ranch_ssl(3)] manual.\n\nProtocolOpts::\n\nThe protocol options are in a map containing all the options for\nthe different protocols that may be involved when connecting\nto the listener, including HTTP/1.1 and HTTP/2.\n+\nThe HTTP/1.1 options are documented in the\nlink:man:cowboy_http(3)[cowboy_http(3)] manual;\nand the HTTP/2 options in\nlink:man:cowboy_http2(3)[cowboy_http2(3)]. Stream handlers\nsuch as link:man:cowboy_stream_h(3)[cowboy_stream_h(3)]\n(which is enabled by default) may also define options.\n\n== Return value\n\nAn ok tuple is returned on success. It contains the pid of\nthe top-level supervisor for the listener.\n\nAn error tuple is returned on error. The error reason may\nbe any Erlang term.\n\nA common error is `eaddrinuse`. It indicates that the port\nconfigured for Cowboy is already in use.\n\n== Changelog\n\n* *2.0*: HTTP/2 support added.\n* *2.0*: Function introduced. Replaces `cowboy:start_https/4`.\n\n== Examples\n\n.Start a listener\n[source,erlang]\n----\nDispatch = cowboy_router:compile([\n    {'_', [\n        {\"/\", toppage_h, []}\n    ]}\n]),\n\n{ok, _} = cowboy:start_tls(example, [\n    {port, 8443},\n    {certfile, \"path/to/cert.pem\"}\n], #{\n    env => #{dispatch => Dispatch}\n}).\n----\n\n.Start a listener on a random port\n[source,erlang]\n----\nName = example,\n\n{ok, _} = cowboy:start_tls(Name, [\n    {certfile, \"path/to/cert.pem\"}\n], #{\n    env => #{dispatch => Dispatch}\n}),\n\nPort = ranch:get_port(Name).\n----\n\n== See also\n\nlink:man:cowboy(3)[cowboy(3)],\nlink:man:cowboy:start_clear(3)[cowboy:start_clear(3)],\nlink:man:cowboy:stop_listener(3)[cowboy:stop_listener(3)],\nlink:man:ranch(3)[ranch(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy.stop_listener.asciidoc",
    "content": "= cowboy:stop_listener(3)\n\n== Name\n\ncowboy:stop_listener - Stop the given listener\n\n== Description\n\n[source,erlang]\n----\nstop_listener(Name :: ranch:ref())\n    -> ok | {error, not_found}.\n----\n\nStop a previously started listener.\n\nAlias of link:man:ranch:stop_listener(3)[ranch:stop_listener(3)].\n\n== Arguments\n\nName::\n\nThe name of the listener to be stopped.\n+\nThe name of the listener is the first argument given to the\nlink:man:cowboy:start_clear(3)[cowboy:start_clear(3)],\nlink:man:cowboy:start_tls(3)[cowboy:start_tls(3)] or\nlink:man:ranch:start_listener(3)[ranch:start_listener(3)] function.\n\n== Return value\n\nThe atom `ok` is returned on success.\n\nThe `{error, not_found}` tuple is returned when the listener\ndoes not exist.\n\n== Changelog\n\n* *1.0*: Function introduced.\n\n== Examples\n\n.Stop a listener\n[source,erlang]\n----\nok = cowboy:stop_listener(example).\n----\n\n== See also\n\nlink:man:cowboy(3)[cowboy(3)],\nlink:man:cowboy:start_clear(3)[cowboy:start_clear(3)],\nlink:man:cowboy:start_tls(3)[cowboy:start_tls(3)],\nlink:man:ranch(3)[ranch(3)],\nlink:man:ranch:start_listener(3)[ranch:start_listener(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_app.asciidoc",
    "content": "= cowboy(7)\n\n== Name\n\ncowboy - Small, fast, modern HTTP server for Erlang/OTP\n\n== Description\n\nCowboy is an HTTP server for Erlang/OTP with support for the\nHTTP/1.1, HTTP/2 and Websocket protocols.\n\nCowboy aims to provide a complete HTTP stack. This includes\nthe implementation of the HTTP RFCs but also any directly\nrelated standards, like Websocket or Server-Sent Events.\n\n== Modules\n\nFunctions:\n\n* link:man:cowboy(3)[cowboy(3)] - Listener management\n* link:man:cowboy_req(3)[cowboy_req(3)] - Request and response\n* link:man:cowboy_router(3)[cowboy_router(3)] - Router\n* link:man:cowboy_constraints(3)[cowboy_constraints(3)] - Constraints\n\nProtocols:\n\n* link:man:cowboy_http(3)[cowboy_http(3)] - HTTP/1.1\n* link:man:cowboy_http2(3)[cowboy_http2(3)] - HTTP/2\n* link:man:cowboy_websocket(3)[cowboy_websocket(3)] - Websocket\n\nHandlers:\n\n* link:man:cowboy_static(3)[cowboy_static(3)] - Static file handler\n\nStream handlers:\n\n* link:man:cowboy_stream_h(3)[cowboy_stream_h(3)] - Default stream handler\n* link:man:cowboy_compress_h(3)[cowboy_compress_h(3)] - Compress stream handler\n* link:man:cowboy_decompress_h(3)[cowboy_decompress_h(3)] - Decompress stream handler\n* link:man:cowboy_metrics_h(3)[cowboy_metrics_h(3)] - Metrics stream handler\n* link:man:cowboy_tracer_h(3)[cowboy_tracer_h(3)] - Tracer stream handler\n\nBehaviors:\n\n* link:man:cowboy_handler(3)[cowboy_handler(3)] - Plain HTTP handlers\n* link:man:cowboy_loop(3)[cowboy_loop(3)] - Loop handlers\n* link:man:cowboy_middleware(3)[cowboy_middleware(3)] - Middlewares\n* link:man:cowboy_rest(3)[cowboy_rest(3)] - REST handlers\n* link:man:cowboy_stream(3)[cowboy_stream(3)] - Stream handlers\n* link:man:cowboy_websocket(3)[cowboy_websocket(3)] - Websocket handlers\n\nMiddlewares:\n\n* link:man:cowboy_router(3)[cowboy_router(3)] - Router middleware\n* link:man:cowboy_handler(3)[cowboy_handler(3)] - Handler middleware\n\n// @todo http_status_codes is not linked to; what to do with it?\n\n== Dependencies\n\n* link:man:ranch(7)[ranch(7)] - Socket acceptor pool for TCP protocols\n* link:man:cowlib(7)[cowlib(7)] - Support library for manipulating Web protocols\n* ssl - Secure communication over sockets\n* crypto - Crypto functions\n\nAll these applications must be started before the `cowboy`\napplication. To start Cowboy and all dependencies at once:\n\n[source,erlang]\n----\n{ok, _} = application:ensure_all_started(cowboy).\n----\n\n== Environment\n\nThe `cowboy` application does not define any application\nenvironment configuration parameters.\n\n== See also\n\nlink:man:ranch(7)[ranch(7)],\nlink:man:cowlib(7)[cowlib(7)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_compress_h.asciidoc",
    "content": "= cowboy_compress_h(3)\n\n== Name\n\ncowboy_compress_h - Compress stream handler\n\n== Description\n\nThe module `cowboy_compress_h` compresses response bodies\nautomatically when the client supports it. It will not\ntry to compress responses that already have a content\nencoding or that have an etag header defined.\n\nNormal responses will only be compressed when their\nsize is lower than the configured threshold. Streamed\nresponses are always compressed, including when the\nsendfile command is used. Because the file must be\nread in memory to be compressed, this module is *not*\nsuitable for automatically compressing large files.\n\n== Options\n\n[source,erlang]\n----\nopts() :: #{\n    compress_buffering => boolean(),\n    compress_threshold => non_neg_integer()\n}\n----\n\nConfiguration for the compress stream handler.\n\nThe default value is given next to the option name:\n\ncompress_buffering (false)::\n\nWhether the output will be buffered. By default no\nbuffering is done to provide maximum compatibility\nat the cost of a lower compression rate.\n+\nThis option can be updated at any time using the\n`set_options` stream handler command.\n\ncompress_threshold (300)::\n\nHow large the response body must be to be compressed\nwhen the response isn't streamed.\n+\nThis option can be updated at any time using the\n`set_options` stream handler command.\n\n== Events\n\nThe compress stream handler does not produce any event.\n\n== Changelog\n\n* *2.11*: Compression is now disabled when the etag\n  header is in the response headers.\n* *2.11*: The vary: accept-encoding header is now\n  always set when this handler is enabled.\n* *2.6*: The options `compress_buffering` and\n  `compress_threshold` were added.\n* *2.0*: Module introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_stream(3)[cowboy_stream(3)],\nlink:man:cowboy_decompress_h(3)[cowboy_decompress_h(3)],\nlink:man:cowboy_metrics_h(3)[cowboy_metrics_h(3)],\nlink:man:cowboy_stream_h(3)[cowboy_stream_h(3)],\nlink:man:cowboy_tracer_h(3)[cowboy_tracer_h(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_constraints.asciidoc",
    "content": "= cowboy_constraints(3)\n\n== Name\n\ncowboy_constraints - Constraints\n\n== Description\n\nThe module `cowboy_constraints` defines the built-in\nconstraints in Cowboy and provides an interface for\nmanipulating these constraints.\n\nConstraints are functions that define what type of\ninput is allowed. They are used throughout Cowboy,\nfrom the router to query strings to cookies.\n\n== Exports\n\nBuilt-in constraints:\n\n* link:man:cowboy_constraints:int(3)[cowboy_constraints:int(3)] - Integer constraint\n* link:man:cowboy_constraints:nonempty(3)[cowboy_constraints:nonempty(3)] - Non-empty constraint\n\n== Types\n\n=== constraint()\n\n[source,erlang]\n----\nconstraint() :: int | nonempty | fun()\n----\n\nA constraint function.\n\nThe atom constraints are built-in, see the corresponding\nfunction in the exports list above.\n\n=== reason()\n\n[source,erlang]\n----\nreason() :: {constraint(), Reason, Value}\n\nReason :: any()\nValue  :: any()\n----\n\nReason for the constraint failure.\n\nIt includes the constraint function in question,\na machine-readable error reason and the value that\nmade the constraint fail.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy(3)[cowboy(3)],\nlink:man:cowboy_router(3)[cowboy_router(3)],\nlink:man:cowboy_req:match_cookies(3)[cowboy_req:match_cookies(3)],\nlink:man:cowboy_req:match_qs(3)[cowboy_req:match_qs(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_constraints.int.asciidoc",
    "content": "= cowboy_constraints:int(3)\n\n== Name\n\ncowboy_constraints:int - Integer constraint\n\n== Description\n\nConstraint functions implement a number of different operations.\n\n[source,erlang]\n----\nint(forward, Bin) -> {ok, Int} | {error, not_an_integer}\n\nBin :: binary()\nInt :: integer()\n----\n\nValidate and convert the text representation of an integer.\n\n[source,erlang]\n----\nint(reverse, Int) -> {ok, Bin} | {error, not_an_integer}\n----\n\nConvert an integer back to its text representation.\n\n[source,erlang]\n----\nint(format_error, Error) -> HumanReadable\n\nError         :: {not_an_integer, Bin | Int}\nHumanReadable :: iolist()\n----\n\nGenerate a human-readable error message.\n\n== Arguments\n\nArguments vary depending on the operation. Constraint\nfunctions always take the operation type as first argument,\nand the value as second argument.\n\n== Return value\n\nThe return value varies depending on the operation.\n\n== Changelog\n\n* *2.0*: Interface modified to allow for a variety of operations.\n* *1.0*: Constraint introduced.\n\n== Examples\n\nThis function is not meant to be called directly.\n\n== See also\n\nlink:man:cowboy_constraints(3)[cowboy_constraints(3)],\nlink:man:cowboy_constraints:nonempty(3)[cowboy_constraints:nonempty(3)],\nlink:man:cowboy_router(3)[cowboy_router(3)],\nlink:man:cowboy_req:match_cookies(3)[cowboy_req:match_cookies(3)],\nlink:man:cowboy_req:match_qs(3)[cowboy_req:match_qs(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_constraints.nonempty.asciidoc",
    "content": "= cowboy_constraints:nonempty(3)\n\n== Name\n\ncowboy_constraints:nonempty - Non-empty constraint\n\n== Description\n\nConstraint functions implement a number of different operations.\n\n[source,erlang]\n----\nnonempty(forward | reverse, <<>>) -> {error, empty}\n----\n\nReject empty values.\n\n[source,erlang]\n----\nnonempty(forward | reverse, Bin) -> {ok, Bin}\n\nBin :: binary()\n----\n\nAccept any other binary values.\n\n[source,erlang]\n----\nnonempty(format_error, Error) -> HumanReadable\n\nError         :: {empty, Bin}\nHumanReadable :: iolist()\n----\n\nGenerate a human-readable error message.\n\n== Arguments\n\nArguments vary depending on the operation. Constraint\nfunctions always take the operation type as first argument,\nand the value as second argument.\n\n== Return value\n\nThe return value varies depending on the operation.\n\n== Changelog\n\n* *2.0*: Interface modified to allow for a variety of operations.\n* *1.0*: Constraint introduced.\n\n== Examples\n\nThis function is not meant to be called directly.\n\n== See also\n\nlink:man:cowboy_constraints(3)[cowboy_constraints(3)],\nlink:man:cowboy_constraints:int(3)[cowboy_constraints:int(3)],\nlink:man:cowboy_router(3)[cowboy_router(3)],\nlink:man:cowboy_req:match_cookies(3)[cowboy_req:match_cookies(3)],\nlink:man:cowboy_req:match_qs(3)[cowboy_req:match_qs(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_decompress_h.asciidoc",
    "content": "= cowboy_decompress_h(3)\n\n== Name\n\ncowboy_decompress_h - Decompress stream handler\n\n== Description\n\nThe module `cowboy_decompress_h` decompresses request bodies\nautomatically when the server supports it.\n\nThe only compression algorithm currently supported is the\ngzip algorithm. Another limitation is that decompression\nis only attempted when gzip is the only content-encoding\nin the request.\n\nThis stream handler always adds a field to the Req object\nwith the name `content_decoded` which is treated as a\nlist of decoded content-encoding values. Currently this\nlist may only contain the `<<\"gzip\">>` binary if content\nwas decoded; or be empty otherwise.\n\n== Options\n\n[source,erlang]\n----\nopts() :: #{\n\tdecompress_enabled => boolean(),\n\tdecompress_ratio_limit => non_neg_integer()\n}\n----\n\nConfiguration for the decompress stream handler.\n\nThe default value is given next to the option name:\n\ndecompress_ratio_limit (20)::\nThe max ratio of the compressed and decompressed body\nbefore it is rejected with a `413 Payload Too Large`\nerror response.\n+\nThis option can be updated at any time using the\n`set_options` stream handler command.\n\ndecompress_enabled (true)::\n\nWhether the handler is enabled by default.\n+\nThis option can be updated using the `set_options`\nstream handler command. This allows disabling\ndecompression for the current stream. Attempts\nto enable or disable decompression after starting\nto read the body will be ignored.\n\n== Events\n\nThe decompress stream handler does not produce any event.\n\n== Changelog\n\n* *2.11*: Module introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_stream(3)[cowboy_stream(3)],\nlink:man:cowboy_compress_h(3)[cowboy_compress_h(3)],\nlink:man:cowboy_metrics_h(3)[cowboy_metrics_h(3)],\nlink:man:cowboy_stream_h(3)[cowboy_stream_h(3)],\nlink:man:cowboy_tracer_h(3)[cowboy_tracer_h(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_handler.asciidoc",
    "content": "= cowboy_handler(3)\n\n== Name\n\ncowboy_handler - Plain HTTP handlers\n\n== Description\n\nThe `cowboy_handler` middleware executes the handler selected\nby the router or any other preceding middleware.\n\nThis middleware takes the handler module and initial state\nfrom the `handler` and `handler_opts` environment values,\nrespectively. On completion, it adds a `result` value to\nthe middleware environment, containing the return value\nof the `terminate/3` callback (if defined) and `ok` otherwise.\n\nThis module also defines a callback interface for handling\nHTTP requests.\n\n== Callbacks\n\nPlain HTTP handlers implement the following interface:\n\n[source,erlang]\n----\ninit(Req, State) -> {ok, Req, State}\n\nterminate(Reason, Req, State) -> ok  %% optional\n\nReq    :: cowboy_req:req()\nState  :: any()\nReason :: normal\n        | {crash, error | exit | throw, any()}\n----\n\nThese two callbacks are common to all handlers.\n\nPlain HTTP handlers do all their work in the `init/2`\ncallback. Returning `ok` terminates the handler. If no\nresponse is sent, Cowboy will send a `204 No Content`.\n\nThe optional `terminate/3` callback will ultimately be called\nwith the reason for the termination of the handler.\nCowboy will terminate the process right after this. There\nis no need to perform any cleanup in this callback.\n\nThe following terminate reasons are defined for plain HTTP\nhandlers:\n\nnormal::\n    The connection was closed normally.\n\n{crash, Class, Reason}::\n    A crash occurred in the handler. `Class` and `Reason` can be\n    used to obtain more information about the crash.\n\n== Exports\n\nThe following function should be called by modules implementing\ncustom handlers to execute the optional terminate callback:\n\n* link:man:cowboy_handler:terminate(3)[cowboy_handler:terminate(3)] - Terminate the handler\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_handler.terminate.asciidoc",
    "content": "= cowboy_handler:terminate(3)\n\n== Name\n\ncowboy_handler:terminate - Terminate the handler\n\n== Description\n\n[source,erlang]\n----\nterminate(Reason, PartialReq, State, Handler) -> ok\n\nReason     :: any()\nPartialReq :: map()\nState      :: any()\nHandler    :: module()\n----\n\nCall the optional terminate callback if it is defined.\n\nMake sure to use this function at the end of the execution\nof modules that implement custom handler behaviors.\n\n== Arguments\n\nReason::\n\nReason for termination.\n\nPartialReq::\n\nThe Req object.\n+\nIt is possible to remove fields from the Req object to save memory\nwhen the handler has no concept of requests/responses. The only\nrequirement is that a map is provided.\n\nState::\n\nHandler state.\n\nHandler::\n\nHandler module.\n\n== Return value\n\nThe atom `ok` is always returned. It can be safely ignored.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Terminate a handler normally\n[source,erlang]\n----\ncowboy_handler:terminate(normal, Req, State, Handler).\n----\n\n== See also\n\nlink:man:cowboy_handler(3)[cowboy_handler(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_http.asciidoc",
    "content": "= cowboy_http(3)\n\n== Name\n\ncowboy_http - HTTP/1.1\n\n== Description\n\nThe module `cowboy_http` implements HTTP/1.1 and HTTP/1.0\nas a Ranch protocol.\n\n== Options\n\n// @todo Might be worth moving cowboy_clear/tls options\n// to their respective manual, when they are added.\n\n[source,erlang]\n----\nopts() :: #{\n    active_n                   => pos_integer(),\n    alpn_default_protocol      => http | http2,\n    chunked                    => boolean(),\n    connection_type            => worker | supervisor,\n    dynamic_buffer             => false | {pos_integer(), pos_integer()},\n    hibernate                  => boolean(),\n    http10_keepalive           => boolean(),\n    idle_timeout               => timeout(),\n    inactivity_timeout         => timeout(),\n    initial_stream_flow_size   => non_neg_integer(),\n    linger_timeout             => timeout(),\n    logger                     => module(),\n    max_authorization_header_value_length => non_neg_integer(),\n    max_cookie_header_value_length        => non_neg_integer(),\n    max_empty_lines            => non_neg_integer(),\n    max_header_name_length     => non_neg_integer(),\n    max_header_value_length    => non_neg_integer(),\n    max_headers                => non_neg_integer(),\n    max_keepalive              => non_neg_integer(),\n    max_method_length          => non_neg_integer(),\n    max_request_line_length    => non_neg_integer(),\n    max_skip_body_length       => non_neg_integer(),\n    protocols                  => [http | http2],\n    proxy_header               => boolean(),\n    request_timeout            => timeout(),\n    reset_idle_timeout_on_send => boolean(),\n    sendfile                   => boolean(),\n    stream_handlers            => [module()]\n}\n----\n\nConfiguration for the HTTP/1.1 protocol.\n\nThis configuration is passed to Cowboy when starting listeners\nusing `cowboy:start_clear/3` or `cowboy:start_tls/3` functions.\n\nIt can be updated without restarting listeners using the\nRanch functions `ranch:get_protocol_options/1` and\n`ranch:set_protocol_options/2`.\n\nThe default value is given next to the option name:\n\nactive_n (1)::\n\nThe number of packets Cowboy will request from the socket at once.\nThis can be used to tweak the performance of the server. Higher\nvalues reduce the number of times Cowboy need to request more\npackets from the port driver at the expense of potentially\nhigher memory being used.\n\nalpn_default_protocol (http)::\n\nDefault protocol to use when the client connects over TLS\nwithout ALPN. Can be set to `http2` to disable HTTP/1.1\nentirely.\n\nchunked (true)::\n\nWhether chunked transfer-encoding is enabled for HTTP/1.1 connections.\nNote that a response streamed to the client without the chunked\ntransfer-encoding and without a content-length header will result\nin the connection being closed at the end of the response body.\n+\nThis option can be updated at any time using the\n`set_options` stream handler command.\n\nconnection_type (supervisor)::\n\nWhether the connection process also acts as a supervisor.\n\ndynamic_buffer ({1024, 131072})::\n\nCowboy will dynamically change the socket's `buffer` size\ndepending on the size of the data it receives from the socket.\nThis lets Cowboy use the optimal buffer size for the current\nworkload.\n+\nThe dynamic buffer size functionality can be disabled by\nsetting this option to `false`. Cowboy will also disable\nit by default when the `buffer` transport option is configured.\n\nhibernate (false)::\n\nWhether the connection process will hibernate automatically.\n\nhttp10_keepalive (true)::\n\nWhether keep-alive is enabled for HTTP/1.0 connections.\n\nidle_timeout (60000)::\n\nTime in ms with no data received before Cowboy closes the connection.\n+\nThis option can be updated at any time using the\n`set_options` stream handler command.\n\ninactivity_timeout (300000)::\n\n**DEPRECATED** Time in ms with nothing received at all before Cowboy closes the connection.\n\ninitial_stream_flow_size (65535)::\n\nAmount of data in bytes Cowboy will read from the socket\nright after a request was fully received. This is a soft\nlimit.\n\nlinger_timeout (1000)::\n\nTime in ms that Cowboy will wait when closing the connection. This is\nnecessary to avoid the TCP reset problem as described in the\nhttps://tools.ietf.org/html/rfc7230#section-6.6[section 6.6 of RFC7230].\n\nlogger (error_logger)::\n\nThe module that will be used to write log messages.\n\nmax_authorization_header_value_length::\n\nMaximum length of the `authorization` header value.\nDefaults to the value of the option `max_header_value_length`.\n\nmax_cookie_header_value_length::\n\nMaximum length of the `cookie` header value.\nDefaults to the value of the option `max_header_value_length`.\n\nmax_empty_lines (5)::\n\nMaximum number of empty lines before a request.\n\nmax_header_name_length (64)::\n\nMaximum length of header names.\n\nmax_header_value_length (4096)::\n\nMaximum length of header values.\n\nmax_headers (100)::\n\nMaximum number of headers allowed per request.\n\nmax_keepalive (1000)::\n\nMaximum number of requests allowed per connection.\n\nmax_method_length (32)::\n\nMaximum length of the method.\n\nmax_request_line_length (8000)::\n\nMaximum length of the request line.\n\nmax_skip_body_length (1000000)::\n\nMaximum length Cowboy is willing to skip when the user code did not read the body fully.\nWhen the remaining length is too large or unknown Cowboy will close the connection.\n\nprotocols ([http2, http])::\n\nProtocols that may be used when the client connects over\ncleartext TCP. The default is to allow both HTTP/1.1 and\nHTTP/2. HTTP/1.1 and HTTP/2 can be disabled entirely by\nomitting them from the list.\n\nproxy_header (false)::\n\nWhether incoming connections have a PROXY protocol header. The\nproxy information will be passed forward via the `proxy_header`\nkey of the Req object.\n\nrequest_timeout (5000)::\n\nTime in ms with no requests before Cowboy closes the connection.\n\nreset_idle_timeout_on_send (false)::\n\nWhether the `idle_timeout` gets reset when sending data\nto the socket.\n\nsendfile (true)::\n\nWhether the sendfile syscall may be used. It can be useful to disable\nit on systems where the syscall has a buggy implementation, for example\nunder VirtualBox when using shared folders.\n\nstream_handlers ([cowboy_stream_h])::\n\nOrdered list of stream handlers that will handle all stream events.\n\n== Changelog\n\n* *2.13*: The `inactivity_timeout` option was deprecated.\n* *2.13*: The `active_n` default value was changed to `1`.\n* *2.13*: The `dynamic_buffer` and `hibernate` options were added.\n* *2.11*: The `reset_idle_timeout_on_send` option was added.\n* *2.8*: The `active_n` option was added.\n* *2.7*: The `initial_stream_flow_size` and `logger` options were added.\n* *2.6*: The `chunked`, `http10_keepalive`, `proxy_header` and `sendfile` options were added.\n* *2.5*: The `linger_timeout` option was added.\n* *2.2*: The `max_skip_body_length` option was added.\n* *2.0*: The `timeout` option was renamed `request_timeout`.\n* *2.0*: The `idle_timeout`, `inactivity_timeout` and `shutdown_timeout` options were added.\n* *2.0*: The `max_method_length` option was added.\n* *2.0*: The `max_request_line_length` default was increased from 4096 to 8000.\n* *2.0*: The `connection_type` option was added.\n* *2.0*: The `env` option is now a map instead of a proplist.\n* *2.0*: The `stream_handlers` option was added.\n* *2.0*: The `compress` option was removed in favor of the `cowboy_compress_h` stream handler.\n* *2.0*: Options are now a map instead of a proplist.\n* *2.0*: Protocol introduced. Replaces `cowboy_protocol`.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_http2(3)[cowboy_http2(3)],\nlink:man:cowboy_websocket(3)[cowboy_websocket(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_http2.asciidoc",
    "content": "= cowboy_http2(3)\n\n== Name\n\ncowboy_http2 - HTTP/2\n\n== Description\n\nThe module `cowboy_http2` implements HTTP/2\nas a Ranch protocol.\n\n== Options\n\n// @todo Might be worth moving cowboy_clear/tls/stream_h options\n// to their respective manual, when they are added.\n\n[source,erlang]\n----\nopts() :: #{\n    active_n                       => pos_integer(),\n    alpn_default_protocol          => http | http2,\n    connection_type                => worker | supervisor,\n    connection_window_margin_size  => 0..16#7fffffff,\n    connection_window_update_threshold => 0..16#7fffffff,\n    dynamic_buffer                 => false | {pos_integer(), pos_integer()},\n    enable_connect_protocol        => boolean(),\n    goaway_initial_timeout         => timeout(),\n    goaway_complete_timeout        => timeout(),\n    hibernate                      => boolean(),\n    idle_timeout                   => timeout(),\n    inactivity_timeout             => timeout(),\n    initial_connection_window_size => 65535..16#7fffffff,\n    initial_stream_window_size     => 0..16#7fffffff,\n    linger_timeout                 => timeout(),\n    logger                         => module(),\n    max_concurrent_streams         => non_neg_integer() | infinity,\n    max_connection_buffer_size     => non_neg_integer(),\n    max_connection_window_size     => 0..16#7fffffff,\n    max_decode_table_size          => non_neg_integer(),\n    max_encode_table_size          => non_neg_integer(),\n    max_fragmented_header_block_size => 16384..16#7fffffff,\n    max_frame_size_received        => 16384..16777215,\n    max_frame_size_sent            => 16384..16777215 | infinity,\n    max_received_frame_rate        => {pos_integer(), timeout()},\n    max_reset_stream_rate          => {pos_integer(), timeout()},\n    max_cancel_stream_rate         => {pos_integer(), timeout()},\n    max_stream_buffer_size         => non_neg_integer(),\n    max_stream_window_size         => 0..16#7fffffff,\n    preface_timeout                => timeout(),\n    protocols                      => [http | http2],\n    proxy_header                   => boolean(),\n    reset_idle_timeout_on_send     => boolean(),\n    sendfile                       => boolean(),\n    settings_timeout               => timeout(),\n    stream_handlers                => [module()],\n    stream_window_data_threshold   => 0..16#7fffffff,\n    stream_window_margin_size      => 0..16#7fffffff,\n    stream_window_update_threshold => 0..16#7fffffff\n}\n----\n\nConfiguration for the HTTP/2 protocol.\n\nThis configuration is passed to Cowboy when starting listeners\nusing `cowboy:start_clear/3` or `cowboy:start_tls/3` functions.\n\nIt can be updated without restarting listeners using the\nRanch functions `ranch:get_protocol_options/1` and\n`ranch:set_protocol_options/2`.\n\nThe default value is given next to the option name:\n\nactive_n (1)::\n\nThe number of packets Cowboy will request from the socket at once.\nThis can be used to tweak the performance of the server. Higher\nvalues reduce the number of times Cowboy need to request more\npackets from the port driver at the expense of potentially\nhigher memory being used.\n\nalpn_default_protocol (http)::\n\nDefault protocol to use when the client connects over TLS\nwithout ALPN. Can be set to `http2` to disable HTTP/1.1\nentirely.\n\nconnection_type (supervisor)::\n\nWhether the connection process also acts as a supervisor.\n\nconnection_window_margin_size (65535)::\n\nExtra amount in bytes to be added to the window size when\nupdating the connection window. This is used to\nensure that there is always some space available in\nthe window.\n\nconnection_window_update_threshold (163840)::\n\nThe connection window will only get updated when its size\nbecomes lower than this threshold, in bytes. This is to\navoid sending too many `WINDOW_UPDATE` frames.\n\ndynamic_buffer ({1024, 131072})::\n\nCowboy will dynamically change the socket's `buffer` size\ndepending on the size of the data it receives from the socket.\nThis lets Cowboy use the optimal buffer size for the current\nworkload.\n+\nThe dynamic buffer size functionality can be disabled by\nsetting this option to `false`. Cowboy will also disable\nit by default when the `buffer` transport option is configured.\n\nenable_connect_protocol (false)::\n\nWhether to enable the extended CONNECT method to allow\nprotocols like Websocket to be used over an HTTP/2 stream.\n+\nFor backward compatibility reasons, this option is disabled\nby default. It must be enabled to use Websocket over HTTP/2.\nIt will be enabled by default in a future release.\n\ngoaway_initial_timeout (1000)::\n\nTime in ms to wait for any in-flight stream creations before stopping to accept\nnew streams on an existing connection during a graceful shutdown.\n\ngoaway_complete_timeout (3000)::\n\nTime in ms to wait for ongoing streams to complete before closing the connection\nduring a graceful shutdown.\n\nhibernate (false)::\n\nWhether the connection process will hibernate automatically.\n\nidle_timeout (60000)::\n\nTime in ms with no data received before Cowboy closes the connection.\n\ninactivity_timeout (300000)::\n\n**DEPRECATED** Time in ms with nothing received at all before Cowboy closes the connection.\n\ninitial_connection_window_size (65535)::\n\nInitial window size in bytes for the connection. This is the total amount\nof data (from request bodies for example) that may be buffered\nby the connection across all streams before the user code\nexplicitly requests it.\n+\nNote that this value cannot be lower than the default.\n\ninitial_stream_window_size (65535)::\n\nInitial window size in bytes for new streams. This is the total amount\nof data (from request bodies for example) that may be buffered\nby a single stream before the user code explicitly requests it.\n\nlinger_timeout (1000)::\n\nTime in ms that Cowboy will wait when closing the connection. This is\nnecessary to avoid the TCP reset problem as described in the\nhttps://tools.ietf.org/html/rfc7230#section-6.6[section 6.6 of RFC7230].\nIn HTTP/2's case the GOAWAY message might also be lost when\nclosing the connection immediately.\n\nlogger (error_logger)::\n\nThe module that will be used to write log messages.\n\nmax_concurrent_streams (infinity)::\n\nMaximum number of concurrent streams allowed on the connection.\n\nmax_connection_buffer_size (16000000)::\n\nMaximum size of all stream buffers for this connection, in bytes.\nThis is a soft limit used to apply backpressure to handlers that\nsend data faster than the HTTP/2 connection allows.\n\nmax_connection_window_size (16#7fffffff)::\n\nMaximum connection window size in bytes. This is used as an upper bound\nwhen calculating the window size, either when reading the request\nbody or receiving said body.\n\nmax_decode_table_size (4096)::\n\nMaximum header table size in bytes used by the decoder. This is the value\nadvertised to the client. The client can then choose a header table size\nequal or lower to the advertised value.\n\nmax_encode_table_size (4096)::\n\nMaximum header table size in bytes used by the encoder. The server will\ncompare this value to what the client advertises and choose the smallest\none as the encoder's header table size.\n\nmax_fragmented_header_block_size (32768)::\n\nMaximum header block size when headers are split over multiple HEADERS\nand CONTINUATION frames. Clients that attempt to send header blocks\nlarger than this value will receive an ENHANCE_YOUR_CALM connection\nerror. Note that this value is not advertised and should be large\nenough for legitimate requests.\n\nmax_frame_size_received (16384)::\n\nMaximum size in bytes of the frames received by the server. This value is\nadvertised to the remote endpoint which can then decide to use\nany value lower or equal for its frame sizes.\n+\nIt is highly recommended to increase this value for performance reasons.\nIn a future Cowboy version the default will be increased to 1MB (1048576).\nToo low values may result in very large file uploads failing because\nCowboy will detect the large number of frames as flood and close the\nconnection.\n\nmax_frame_size_sent (infinity)::\n\nMaximum size in bytes of the frames sent by the server. This option allows\nsetting an upper limit to the frame sizes instead of blindly\nfollowing the client's advertised maximum.\n+\nNote that actual frame sizes may be lower than the limit when\nthere is not enough space left in the flow control window.\n\nmax_received_frame_rate ({10000, 10000})::\n\nMaximum frame rate allowed per connection. The rate is expressed\nas a tuple `{NumFrames, TimeMs}` indicating how many frames are\nallowed over the given time period. This is similar to a supervisor\nrestart intensity/period.\n\nmax_reset_stream_rate ({10, 10000})::\n\nMaximum reset stream rate per connection. This can be used to\nprotect against misbehaving or malicious peers that do not follow\nthe protocol, leading to the server resetting streams, by limiting\nthe number of streams that can be reset over a certain time period.\nThe rate is expressed as a tuple `{NumResets, TimeMs}`. This is\nsimilar to a supervisor restart intensity/period.\n\nmax_cancel_stream_rate ({500, 10000})::\n\nMaximum cancel stream rate per connection. This can be used to\nprotect against misbehaving or malicious peers, by limiting the\nnumber of streams that the peer can reset over a certain time period.\nThe rate is expressed as a tuple `{NumCancels, TimeMs}`. This is\nsimilar to a supervisor restart intensity/period.\n\nmax_stream_buffer_size (8000000)::\n\nMaximum stream buffer size in bytes. This is a soft limit used\nto apply backpressure to handlers that send data faster than\nthe HTTP/2 connection allows.\n\nmax_stream_window_size (16#7fffffff)::\n\nMaximum stream window size in bytes. This is used as an upper bound\nwhen calculating the window size, either when reading the request\nbody or receiving said body.\n\npreface_timeout (5000)::\n\nTime in ms Cowboy is willing to wait for the connection preface.\n\nprotocols ([http2, http])::\n\nProtocols that may be used when the client connects over\ncleartext TCP. The default is to allow both HTTP/1.1 and\nHTTP/2. HTTP/1.1 and HTTP/2 can be disabled entirely by\nomitting them from the list.\n\nproxy_header (false)::\n\nWhether incoming connections have a PROXY protocol header. The\nproxy information will be passed forward via the `proxy_header`\nkey of the Req object.\n\nreset_idle_timeout_on_send (false)::\n\nWhether the `idle_timeout` gets reset when sending data\nto the socket.\n\nsendfile (true)::\n\nWhether the sendfile syscall may be used. It can be useful to disable\nit on systems where the syscall has a buggy implementation, for example\nunder VirtualBox when using shared folders.\n\nsettings_timeout (5000)::\n\nTime in ms Cowboy is willing to wait for a SETTINGS ack.\n\nstream_handlers ([cowboy_stream_h])::\n\nOrdered list of stream handlers that will handle all stream events.\n\nstream_window_data_threshold (16384)::\n\nWindow threshold in bytes below which Cowboy will not attempt\nto send data, with one exception. When Cowboy has data to send\nand the window is high enough, Cowboy will always send the data,\nregardless of this option.\n\nstream_window_margin_size (65535)::\n\nExtra amount in bytes to be added to the window size when\nupdating a stream's window. This is used to\nensure that there is always some space available in\nthe window.\n\nstream_window_update_threshold (163840)::\n\nA stream's window will only get updated when its size\nbecomes lower than this threshold, in bytes. This is to avoid sending\ntoo many `WINDOW_UPDATE` frames.\n\n== Changelog\n\n* *2.13*: The `inactivity_timeout` option was deprecated.\n* *2.13*: The `active_n` default value was changed to `1`.\n* *2.13*: The `dynamic_buffer` and `hibernate` options were added.\n* *2.11*: Websocket over HTTP/2 is now considered stable.\n* *2.11*: The `reset_idle_timeout_on_send` option was added.\n* *2.11*: Add the option `max_cancel_stream_rate` to protect\n          against another flood scenario.\n* *2.9*: The `goaway_initial_timeout` and `goaway_complete_timeout`\n         options were added.\n* *2.8*: The `active_n` option was added.\n* *2.8*: The `linger_timeout` option was added.\n* *2.8*: The `max_received_frame_rate` default value has\n         been multiplied by 10 as the default was too low.\n* *2.7*: Add the options `connection_window_margin_size`,\n         `connection_window_update_threshold`,\n         `max_connection_window_size`, `max_stream_window_size`,\n         `stream_window_margin_size` and\n         `stream_window_update_threshold` to configure\n         behavior on sending WINDOW_UPDATE frames;\n         `max_connection_buffer_size` and\n         `max_stream_buffer_size` to apply backpressure\n         when sending data too fast;\n         `max_received_frame_rate` and `max_reset_stream_rate`\n         to protect against various flood scenarios; and\n         `stream_window_data_threshold` to control how small\n         the DATA frames that Cowboy sends can get.\n* *2.7*: The `logger` option was added.\n* *2.6*: The `proxy_header` and `sendfile` options were added.\n* *2.4*: Add the options `initial_connection_window_size`,\n         `initial_stream_window_size`, `max_concurrent_streams`,\n         `max_decode_table_size`, `max_encode_table_size`,\n         `max_frame_size_received`, `max_frame_size_sent`\n         and `settings_timeout` to configure HTTP/2 SETTINGS\n         and related behavior.\n* *2.4*: Add the option `enable_connect_protocol`.\n* *2.0*: Protocol introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_http(3)[cowboy_http(3)],\nlink:man:cowboy_websocket(3)[cowboy_websocket(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_loop.asciidoc",
    "content": "= cowboy_loop(3)\n\n== Name\n\ncowboy_loop - Loop handlers\n\n== Description\n\nThe module `cowboy_loop` defines a callback interface for\nlong running HTTP connections.\n\nYou should switch to this behavior for long polling,\nserver-sent events and similar long-running requests.\n\nThere are generally two usage patterns:\n\n* Loop until receiving a specific message, then send\n  a response and stop execution (for example long polling);\n\n* Or initiate a response in `init/2` and stream the\n  body in `info/3` as necessary (for example server-sent events).\n\n== Callbacks\n\nLoop handlers implement the following interface:\n\n[source,erlang]\n----\ninit(Req, State)\n    -> {cowboy_loop, Req, State}\n     | {cowboy_loop, Req, State, hibernate | timeout()}\n\ninfo(Info, Req, State)\n    -> {ok, Req, State}\n     | {ok, Req, State, hibernate | timeout()}\n     | {stop, Req, State}\n\nterminate(Reason, Req, State) -> ok  %% optional\n\nReq    :: cowboy_req:req()\nState  :: any()\nInfo   :: any()\nReason :: stop\n        | {crash, error | exit | throw, any()}\n----\n\nThe `init/2` callback is common to all handlers. To switch\nto the loop behavior, it must return `cowboy_loop` as the\nfirst element of the tuple.\n\nThe `info/3` callback will be called for every Erlang message\nreceived. It may choose to continue the receive loop or stop\nit.\n\nThe optional `terminate/3` callback will ultimately be called\nwith the reason for the termination of the handler.\nCowboy will terminate the process right after this. There\nis no need to perform any cleanup in this callback.\n\nThe following terminate reasons are defined for loop handlers:\n\nstop::\n    The handler requested to close the connection by returning\n    a `stop` tuple.\n\n{crash, Class, Reason}::\n    A crash occurred in the handler. `Class` and `Reason` can be\n    used to obtain more information about the crash.\n\n== Changelog\n\n* *2.11*: A timeout may be returned instead of `hibernate`.\n          It functions the same way as the `gen_server` timeout.\n* *2.0*: Loop handlers no longer need to handle socket events.\n* *1.0*: Behavior introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_handler(3)[cowboy_handler(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_metrics_h.asciidoc",
    "content": "= cowboy_metrics_h(3)\n\n== Name\n\ncowboy_metrics_h - Metrics stream handler\n\n== Description\n\nThe module `cowboy_metrics_h` gathers metrics and\nother information about a stream. It then calls\nthe configured callback with this data.\n\n== Types\n\n=== metrics()\n\n[source,erlang]\n----\nmetrics() :: #{\n    %% The identifier for this listener.\n    ref := ranch:ref(),\n\n    %% The pid for this connection.\n    pid := pid(),\n\n    %% The streamid also indicates the total number of requests on\n    %% this connection (StreamID div 2 + 1).\n    streamid := cowboy_stream:streamid(),\n\n    %% The terminate reason is always useful.\n    reason := cowboy_stream:reason(),\n\n    %% A filtered Req object or a partial Req object\n    %% depending on how far the request got to.\n    req => cowboy_req:req(),\n    partial_req => cowboy_stream:partial_req(),\n\n    %% Response status.\n    resp_status := cowboy:http_status(),\n\n    %% Filtered response headers.\n    resp_headers := cowboy:http_headers(),\n\n    %% Start/end of the processing of the request.\n    %%\n    %% This represents the time from this stream handler's init\n    %% to terminate.\n    req_start => integer(),\n    req_end => integer(),\n\n    %% Start/end of the receiving of the request body.\n    %% Begins when the first packet has been received.\n    req_body_start => integer(),\n    req_body_end => integer(),\n\n    %% Start/end of the sending of the response.\n    %% Begins when we send the headers and ends on the final\n    %% packet of the response body. If everything is sent at\n    %% once these values are identical.\n    resp_start => integer(),\n    resp_end => integer(),\n\n    %% For early errors all we get is the time we received it.\n    early_error_time => integer(),\n\n    %% Start/end of spawned processes. This is where most of\n    %% the user code lies, excluding stream handlers. On a\n    %% default Cowboy configuration there should be only one\n    %% process: the request process.\n    procs => ProcMetrics,\n\n    %% Informational responses sent before the final response.\n    informational => [InformationalMetrics],\n\n    %% Length of the request and response bodies. This does\n    %% not include the framing.\n    req_body_length => non_neg_integer(),\n    resp_body_length => non_neg_integer(),\n\n    %% Additional metadata set by the user.\n    user_data => map()\n}\n\nInformationalMetrics :: #{\n    %% Informational response status.\n    status := cowboy:http_status(),\n\n    %% Headers sent with the informational response.\n    headers := cowboy:http_headers(),\n\n    %% Time when the informational response was sent.\n    time := integer()\n}\n\nProcMetrics :: #{pid() => #{\n    %% Time at which the process spawned.\n    spawn := integer(),\n\n    %% Time at which the process exited.\n    exit => integer(),\n\n    %% Reason for the process exit.\n    reason => any()\n}}\n----\n\nMetrics given to the callback function.\n\nDepending on the life of the stream the metrics may include\nmore or less information.\n\nThe `set_options` command can be used to add additional\nmetadata in the `user_data` metric. This can be used for\nexample to add the handler module which was selected by\nthe router. The option to be set is `metrics_user_data`.\nIt takes a map which will be merged in the existing\n`user_data` map.\n\n== Options\n\n[source,erlang]\n----\nopts() :: #{\n    metrics_callback => fun((metrics()) -> any()),\n    metrics_req_filter => fun((cowboy_req:req()) -> map()),\n    metrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers())\n}\n----\n\nConfiguration for the metrics stream handler.\n\nmetrics_callback - mandatory::\n\nThe function that will be called upon completion\nof the stream. It only takes a single argument,\nthe `metrics()`.\n\nmetrics_req_filter::\n\nA function applied to the Req to compact it and\nonly keep required information. By default no\nfiltering is done.\n\nmetrics_resp_headers_filter::\n\nA function applied to the response headers to\nfilter them and only keep required information.\nBy default no filtering is done.\n\n== Events\n\nThe metrics stream handler does not produce any event.\n\n== Changelog\n\n* *2.7*: Module introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_stream(3)[cowboy_stream(3)],\nlink:man:cowboy_compress_h(3)[cowboy_compress_h(3)],\nlink:man:cowboy_decompress_h(3)[cowboy_decompress_h(3)],\nlink:man:cowboy_stream_h(3)[cowboy_stream_h(3)],\nlink:man:cowboy_tracer_h(3)[cowboy_tracer_h(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_middleware.asciidoc",
    "content": "= cowboy_middleware(3)\n\n== Name\n\ncowboy_middleware - Middlewares\n\n== Description\n\nThe module `cowboy_middleware` defines a callback interface for\nCowboy middlewares.\n\nMiddlewares process the request sequentially in the order they\nare configured.\n\n== Callbacks\n\nMiddlewares implement the following interface:\n\n[source,erlang]\n----\nexecute(Req, Env)\n    -> {ok, Req, Env}\n     | {suspend, module(), atom(), [any()]}\n     | {stop, Req}\n\nReq :: cowboy_req:req()\nEnv :: cowboy_middleware:env()\n----\n\nThe `execute/2` is the only callback that needs to be\nimplemented. It must execute the middleware and return\nwith instructions for Cowboy.\n\nok::\n\nCowboy should continue processing the request using the\nreturned Req object and environment.\n\nsuspend::\n\nCowboy will hibernate the process. When resuming, Cowboy\nwill apply the returned module, function and arguments.\n\nstop::\n\nCowboy will stop middleware execution. No other middleware\nwill be executed. This effectively ends the processing of\nthe request.\n\n// @todo No need to return the Req when stopping. Fix in 3.0.\n\n== Types\n\n=== env()\n\n[source,erlang]\n----\nenv() :: #{atom() => any()}\n----\n\nMiddleware environment.\n\nA new environment is created for every request. The initial\nenvironment contained the user configured environment values\n(like `dispatch` for example) plus the `listener` value which\ncontains the name of the listener for this connection.\n\nMiddlewares may modify the environment as necessary.\n\n== Changelog\n\n* *2.0*: The `env` type is now a map instead of a proplist.\n* *1.0*: Behavior introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.asciidoc",
    "content": "= cowboy_req(3)\n\n== Name\n\ncowboy_req - HTTP request and response\n\n== Description\n\nThe module `cowboy_req` provides functions to access, manipulate\nand respond to requests.\n\nThere are four types of functions in this module. They can be\ndifferentiated by their name and their return type:\n\n[options=\"header\"]\n|===\n| Type         | Name pattern              | Return type\n| access       | no verb, parse_*, match_* | `Value`\n| question     | has_*                     | `boolean()`\n| modification | set_*                     | `Req`\n| action       | any other verb            | `ok \\| {Result, Value, Req}`\n|===\n\nAny `Req` returned must be used in place of the one passed as\nargument. Functions that perform an action in particular write\nstate in the Req object to make sure you are using the function\ncorrectly. For example, it's only possible to send one response,\nand to read the body once.\n\n== Exports\n\nConnection:\n\n* link:man:cowboy_req:peer(3)[cowboy_req:peer(3)] - Peer address and port\n* link:man:cowboy_req:sock(3)[cowboy_req:sock(3)] - Socket address and port\n* link:man:cowboy_req:cert(3)[cowboy_req:cert(3)] - Client TLS certificate\n\nRaw request:\n\n* link:man:cowboy_req:method(3)[cowboy_req:method(3)] - HTTP method\n* link:man:cowboy_req:version(3)[cowboy_req:version(3)] - HTTP version\n* link:man:cowboy_req:scheme(3)[cowboy_req:scheme(3)] - URI scheme\n* link:man:cowboy_req:host(3)[cowboy_req:host(3)] - URI host name\n* link:man:cowboy_req:port(3)[cowboy_req:port(3)] - URI port number\n* link:man:cowboy_req:path(3)[cowboy_req:path(3)] - URI path\n* link:man:cowboy_req:qs(3)[cowboy_req:qs(3)] - URI query string\n* link:man:cowboy_req:uri(3)[cowboy_req:uri(3)] - Reconstructed URI\n* link:man:cowboy_req:header(3)[cowboy_req:header(3)] - HTTP header\n* link:man:cowboy_req:headers(3)[cowboy_req:headers(3)] - HTTP headers\n\nProcessed request:\n\n* link:man:cowboy_req:parse_qs(3)[cowboy_req:parse_qs(3)] - Parse the query string\n* link:man:cowboy_req:match_qs(3)[cowboy_req:match_qs(3)] - Match the query string against constraints\n* link:man:cowboy_req:parse_header(3)[cowboy_req:parse_header(3)] - Parse the given HTTP header\n* link:man:cowboy_req:filter_cookies(3)[cowboy_req:filter_cookies(3)] - Filter cookie headers\n* link:man:cowboy_req:parse_cookies(3)[cowboy_req:parse_cookies(3)] - Parse cookie headers\n* link:man:cowboy_req:match_cookies(3)[cowboy_req:match_cookies(3)] - Match cookies against constraints\n* link:man:cowboy_req:binding(3)[cowboy_req:binding(3)] - Access a value bound from the route\n* link:man:cowboy_req:bindings(3)[cowboy_req:bindings(3)] - Access all values bound from the route\n* link:man:cowboy_req:host_info(3)[cowboy_req:host_info(3)] - Access the route's heading host segments\n* link:man:cowboy_req:path_info(3)[cowboy_req:path_info(3)] - Access the route's trailing path segments\n\nRequest body:\n\n* link:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)] - Is there a request body?\n* link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)] - Body length\n* link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)] - Read the request body\n* link:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)] - Read and parse a urlencoded request body\n* link:man:cowboy_req:read_and_match_urlencoded_body(3)[cowboy_req:read_and_match_urlencoded_body(3)] - Read, parse and match a urlencoded request body against constraints\n* link:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)] - Read the next multipart headers\n* link:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)] - Read the current part's body\n\nResponse:\n\n* link:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)] - Set a cookie\n* link:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)] - Set a response header\n* link:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)] - Set several response headers\n* link:man:cowboy_req:has_resp_header(3)[cowboy_req:has_resp_header(3)] - Is the given response header set?\n* link:man:cowboy_req:resp_header(3)[cowboy_req:resp_header(3)] - Response header\n* link:man:cowboy_req:resp_headers(3)[cowboy_req:resp_headers(3)] - Response headers\n* link:man:cowboy_req:delete_resp_header(3)[cowboy_req:delete_resp_header(3)] - Delete a response header\n* link:man:cowboy_req:set_resp_body(3)[cowboy_req:set_resp_body(3)] - Set the response body\n* link:man:cowboy_req:has_resp_body(3)[cowboy_req:has_resp_body(3)] - Is there a response body?\n* link:man:cowboy_req:inform(3)[cowboy_req:inform(3)] - Send an informational response\n* link:man:cowboy_req:reply(3)[cowboy_req:reply(3)] - Send the response\n* link:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)] - Send the response headers\n* link:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)] - Stream the response body\n* link:man:cowboy_req:stream_events(3)[cowboy_req:stream_events(3)] - Stream events\n* link:man:cowboy_req:stream_trailers(3)[cowboy_req:stream_trailers(3)] - Send the response trailers\n* link:man:cowboy_req:push(3)[cowboy_req:push(3)] - Push a resource to the client\n\nStream handlers:\n\n* link:man:cowboy_req:cast(3)[cowboy_req:cast(3)] - Cast a stream handler event\n\n== Types\n\n=== push_opts()\n\n[source,erlang]\n----\npush_opts() :: #{\n    method => binary(),            %% case sensitive\n    scheme => binary(),            %% lowercase; case insensitive\n    host   => binary(),            %% lowercase; case insensitive\n    port   => inet:port_number(),\n    qs     => binary()             %% case sensitive\n}\n----\n\nPush options.\n\nBy default, Cowboy will use the GET method, an empty query string,\nand take the scheme, host and port directly from the current\nrequest's URI.\n\n=== read_body_opts()\n\n[source,erlang]\n----\nread_body_opts() :: #{\n    length  => non_neg_integer() | auto,\n    period  => non_neg_integer() | infinity,\n    timeout => timeout()\n}\n----\n\nBody reading options.\n\nThe defaults are function-specific.\n\nAuto mode can be enabled by setting `length` to `auto`\nand `period` to `infinity`. The period cannot be set\nto `infinity` when auto mode isn't used.\n\n=== req()\n\n[source,erlang]\n----\nreq() :: #{\n    method  := binary(),               %% case sensitive\n    version := cowboy:http_version() | atom(),\n    scheme  := binary(),               %% lowercase; case insensitive\n    host    := binary(),               %% lowercase; case insensitive\n    port    := inet:port_number(),\n    path    := binary(),               %% case sensitive\n    qs      := binary(),               %% case sensitive\n    headers := cowboy:http_headers(),\n    peer    := {inet:ip_address(), inet:port_number()},\n    sock    := {inet:ip_address(), inet:port_number()},\n    cert    := binary() | undefined\n}\n----\n\nThe Req object.\n\nContains information about the request and response. While\nsome fields are publicly documented, others aren't and shouldn't\nbe used.\n\nYou may add custom fields if required. Make sure to namespace\nthem by prepending an underscore and the name of your application:\n\n.Setting a custom field\n[source,erlang]\n----\nReq#{'_myapp_auth_method' => pubkey}.\n----\n\n=== resp_body()\n\n[source,erlang]\n----\nresp_body() :: iodata()\n    | {sendfile, Offset, Length, Filename}\n\nOffset   :: non_neg_integer()\nLength   :: non_neg_integer()\nFilename :: file:name_all()\n----\n\nResponse body.\n\nIt can take two forms: the actual data to be sent or a\ntuple indicating which file to send.\n\nWhen sending data directly, the type is either a binary or\nan iolist. Iolists are an efficient way to build output.\nInstead of concatenating strings or binaries, you can simply\nbuild a list containing the fragments you want to send in the\norder they should be sent:\n\n.Example iolists usage\n[source,erlang]\n----\n1> RespBody = [\"Hello \", [<<\"world\">>, $!]].\n[\"Hello \",[<<\"world\">>,33]]\n2> io:format(\"~s~n\", [RespBody]).\nHello world!\n----\n\nNote that the length must be greater than zero for any data\nto be sent. Cowboy will send an empty body when the length\nis zero.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.binding.asciidoc",
    "content": "= cowboy_req:binding(3)\n\n== Name\n\ncowboy_req:binding - Access a value bound from the route\n\n== Description\n\n[source,erlang]\n----\nbinding(Name, Req)          -> binding(Name, Req, undefined)\nbinding(Name, Req, Default) -> any() | Default\n\nName    :: atom()\nReq     :: cowboy_req:req()\nDefault :: any()\n----\n\nReturn the value for the given binding.\n\n== Arguments\n\nName::\n\nDesired binding name as an atom.\n\nReq::\n\nThe Req object.\n\nDefault::\n\nDefault value returned when the binding is missing.\n\n== Return value\n\nBy default the value is a case sensitive binary string, however\nconstraints may change the type of this value (for example\nautomatically converting numbers to integer).\n\n== Changelog\n\n* *2.0*: Only the value is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the username from the path\n[source,erlang]\n----\n%% Route is \"/users/:user\"\nUsername = cowboy_req:binding(user, Req).\n----\n\n.Get the branch name, with a default\n[source,erlang]\n----\n%% Route is \"/log[/:branch]\"\nBranch = cowboy_req:binding(branch, Req, <<\"master\">>)\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:bindings(3)[cowboy_req:bindings(3)],\nlink:man:cowboy_req:host_info(3)[cowboy_req:host_info(3)],\nlink:man:cowboy_req:path_info(3)[cowboy_req:path_info(3)],\nlink:man:cowboy_router(3)[cowboy_router(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.bindings.asciidoc",
    "content": "= cowboy_req:bindings(3)\n\n== Name\n\ncowboy_req:bindings - Access all values bound from the route\n\n== Description\n\n[source,erlang]\n----\nbindings(Req :: cowboy_req:req()) -> cowboy_router:bindings()\n----\n\nReturn a map containing all bindings.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nBy default values are case sensitive binary strings, however\nconstraints may change the type of this value (for example\nautomatically converting numbers to integer).\n\n== Changelog\n\n* *2.0*: Only the values are returned, they are no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get all bindings\n[source,erlang]\n----\nBindings = cowboy_req:bindings(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:binding(3)[cowboy_req:binding(3)],\nlink:man:cowboy_req:host_info(3)[cowboy_req:host_info(3)],\nlink:man:cowboy_req:path_info(3)[cowboy_req:path_info(3)],\nlink:man:cowboy_router(3)[cowboy_router(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.body_length.asciidoc",
    "content": "= cowboy_req:body_length(3)\n\n== Name\n\ncowboy_req:body_length - Body length\n\n== Description\n\n[source,erlang]\n----\nbody_length(Req :: cowboy_req:req()) -> undefined | non_neg_integer()\n----\n\nReturn the length of the request body.\n\nThe length is not always known before reading the body.\nIn those cases Cowboy will return `undefined`. The body\nlength is available after the body has been fully read.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe length of the request body, or `undefined` if it is\nnot known.\n\n== Changelog\n\n* *2.0*: Only the length is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the body length\n[source,erlang]\n----\nLength = cowboy_req:body_length(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],\nlink:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],\nlink:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)],\nlink:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.cast.asciidoc",
    "content": "= cowboy_req:cast(3)\n\n== Name\n\ncowboy_req:cast - Cast a stream handler event\n\n== Description\n\n[source,erlang]\n----\ncast(Event :: any(), Req :: cowboy_req:req()) -> ok\n----\n\nCast a stream handler event.\n\nThe event will be passed to stream handlers through the\n`info/3` callback.\n\n== Arguments\n\nEvent::\n\nThe event to be sent to stream handlers.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe atom `ok` is always returned. It can be safely ignored.\n\n== Changelog\n\n* *2.7*: Function introduced.\n\n== Examples\n\n.Read the body using auto mode\n[source,erlang]\n----\nread_body_auto_async(Req) ->\n    read_body_auto_async(Req, make_ref(), <<>>).\n\nread_body_auto_async(Req, Ref, Acc) ->\n    cowboy_req:cast({read_body, self(), Ref, auto, infinity}, Req),\n    receive\n        {request_body, Ref, nofin, Data} ->\n            read_body_auto_async(Req, Ref, <<Acc/binary, Data/binary>>);\n        {request_body, Ref, fin, _BodyLen, Data} ->\n            {ok, <<Acc/binary, Data/binary>>, Req}\n    end.\n----\n\n.Increase the HTTP/1.1 idle timeout\n[source,erlang]\n----\ncowboy_req:cast({set_options, #{\n    idle_timeout => 3600000\n}}, Req).\n----\n\n.Add user data to metrics\n----\ncowboy_req:cast({set_options, #{\n    metrics_user_data => #{handler => ?MODULE}\n}}, Req).\n----\n\n.Enable compression buffering\n----\ncowboy_req:cast({set_options, #{\n    compress_buffering => true\n}}, Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_stream(3)[cowboy_stream(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.cert.asciidoc",
    "content": "= cowboy_req:cert(3)\n\n== Name\n\ncowboy_req:cert - Client TLS certificate\n\n== Description\n\n[source,erlang]\n----\ncert(Req :: cowboy_req:req()) -> binary() | undefined\n----\n\nReturn the peer's TLS certificate.\n\nUsing the default configuration this function will always return\n`undefined`. You need to explicitly configure Cowboy to request\nthe client certificate. To do this you need to set the `verify`\ntransport option to `verify_peer`:\n\n[source,erlang]\n----\n{ok, _} = cowboy:start_tls(example, [\n    {port, 8443},\n    {certfile, \"path/to/cert.pem\"},\n    {verify, verify_peer}\n], #{\n    env => #{dispatch => Dispatch}\n}).\n----\n\nYou may also want to customize the `verify_fun` function. Please\nconsult the `ssl` application's manual for more details.\n\nTCP connections do not allow a certificate and this function\nwill therefore always return `undefined`.\n\nThe certificate can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{cert := Cert} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe client TLS certificate.\n\n== Changelog\n\n* *2.1*: Function introduced.\n\n== Examples\n\n.Get the client TLS certificate.\n[source,erlang]\n----\nCert = cowboy_req:cert(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:peer(3)[cowboy_req:peer(3)],\nlink:man:cowboy_req:sock(3)[cowboy_req:sock(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.delete_resp_header.asciidoc",
    "content": "= cowboy_req:delete_resp_header(3)\n\n== Name\n\ncowboy_req:delete_resp_header - Delete a response header\n\n== Description\n\n[source,erlang]\n----\ndelete_resp_header(Name, Req :: cowboy_req:req()) -> Req\n\nName :: binary()  %% lowercase; case insensitive\n----\n\nDelete the given response header.\n\nThe header name must be given as a lowercase binary string.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\n== Arguments\n\nName::\n\nHeader name as a lowercase binary string.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nA new Req object is returned.\n\nThe returned Req object must be used from that point onward,\notherwise the header will still be sent in the response.\n\n== Changelog\n\n* *1.0*: Function introduced.\n\n== Examples\n\n.Remove the content-type header from the response\n[source,erlang]\n----\nReq = cowboy_req:delete_resp_header(<<\"content-type\">>, Req0),\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],\nlink:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)],\nlink:man:cowboy_req:has_resp_header(3)[cowboy_req:has_resp_header(3)],\nlink:man:cowboy_req:resp_header(3)[cowboy_req:resp_header(3)],\nlink:man:cowboy_req:resp_headers(3)[cowboy_req:resp_headers(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.filter_cookies.asciidoc",
    "content": "= cowboy_req:filter_cookies(3)\n\n== Name\n\ncowboy_req:filter_cookies - Filter cookie headers\n\n== Description\n\n[source,erlang]\n----\nfilter_cookies(Names, Req) -> Req\n\nNames :: [atom() | binary()]\n----\n\nFilter cookie headers.\n\nThis function is meant to be used before attempting to parse\nor match cookies in order to remove cookies that are not\nrelevant and are potentially malformed. Because Cowboy by\ndefault crashes on malformed cookies, this function allows\nprocessing requests that would otherwise result in a 400\nerror.\n\nMalformed cookies are unfortunately fairly common due to\nthe string-based interface provided by browsers and this\nfunction provides a middle ground between Cowboy's strict\nbehavior and chaotic real world use cases.\n\nNote that there may still be crashes even after filtering\ncookies because this function does not correct malformed\nvalues. Cookies that have malformed values should probably\nbe unset in an error response or in a redirect.\n\nThis function can be called even if there are no cookies\nin the request.\n\n== Arguments\n\nNames::\n\nThe cookies that should be kept.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe Req object is returned with its cookie header value\nfiltered.\n\n== Changelog\n\n* *2.7*: Function introduced.\n\n== Examples\n\n.Filter then parse cookies\n[source,erlang]\n----\nReq = cowboy_req:filter_cookies([session_id, token], Req0),\nCookies = cowboy_req:parse_cookies(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:parse_cookies(3)[cowboy_req:parse_cookies(3)],\nlink:man:cowboy_req:match_cookies(3)[cowboy_req:match_cookies(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.has_body.asciidoc",
    "content": "= cowboy_req:has_body(3)\n\n== Name\n\ncowboy_req:has_body - Is there a request body?\n\n== Description\n\n[source,erlang]\n----\nhas_body(Req :: cowboy_req:req()) -> boolean()\n----\n\nReturn whether the request has a body.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nA boolean indicating whether the request has a body.\n\n== Changelog\n\n* *1.0*: Function introduced.\n\n== Examples\n\n.Ensure the request has a body\n[source,erlang]\n----\ntrue = cowboy_req:has_body(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],\nlink:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],\nlink:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)],\nlink:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.has_resp_body.asciidoc",
    "content": "= cowboy_req:has_resp_body(3)\n\n== Name\n\ncowboy_req:has_resp_body - Is there a response body?\n\n== Description\n\n[source,erlang]\n----\nhas_resp_body(Req :: cowboy_req:req()) -> boolean()\n----\n\nReturn whether a response body has been set.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nA boolean indicating whether a response body has been set.\n\nThis function will return `false` when an empty response\nbody has been set.\n\n== Changelog\n\n* *1.0*: Function introduced.\n\n== Examples\n\n.Check whether a body has been set\n[source,erlang]\n----\nfalse = cowboy_req:has_resp_body(Req0),\nReq1 = cowboy_req:set_resp_body(<<\"Hello!\">>, Req0),\ntrue = cowboy_req:has_resp_body(Req1),\nReq = cowboy_req:set_resp_body(<<>>, Req1),\nfalse = cowboy_req:has_resp_body(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:set_resp_body(3)[cowboy_req:set_resp_body(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.has_resp_header.asciidoc",
    "content": "= cowboy_req:has_resp_header(3)\n\n== Name\n\ncowboy_req:has_resp_header - Is the given response header set?\n\n== Description\n\n[source,erlang]\n----\nhas_resp_header(Name, Req :: cowboy_req:req()) -> boolean()\n\nName :: binary()  %% lowercase; case insensitive\n----\n\nReturn whether the given response header has been set.\n\nThe header name must be given as a lowercase binary string.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\n== Arguments\n\nName::\n\nHeader name as a lowercase binary string.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nA boolean indicating whether the given response header has been set.\n\n== Changelog\n\n* *1.0*: Function introduced.\n\n== Examples\n\n.Check whether the content-type header has been set\n[source,erlang]\n----\nfalse = cowboy_req:has_resp_header(<<\"content-type\">>, Req0),\nReq = cowboy_req:set_resp_header(<<\"content-type\">>, <<\"text/html\">>, Req0),\ntrue = cowboy_req:has_resp_header(<<\"content-type\">>, Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],\nlink:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)],\nlink:man:cowboy_req:resp_header(3)[cowboy_req:resp_header(3)],\nlink:man:cowboy_req:resp_headers(3)[cowboy_req:resp_headers(3)],\nlink:man:cowboy_req:delete_resp_header(3)[cowboy_req:delete_resp_header(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.header.asciidoc",
    "content": "= cowboy_req:header(3)\n\n== Name\n\ncowboy_req:header - HTTP header\n\n== Description\n\n[source,erlang]\n----\nheader(Name, Req)          -> header(Name, Req, undefined)\nheader(Name, Req, Default) -> binary() | Default\n\nName    :: binary()          %% lowercase; case insensitive\nReq     :: cowboy_req:req()\nDefault :: any()\n----\n\nReturn the value for the given HTTP header.\n\nThe header name must be given as a lowercase binary string.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\nHeaders can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{headers := #{Name := Value}} = Req.\n----\n\nNote that this snippet will crash if the header is missing.\n\n== Arguments\n\nName::\n\nDesired HTTP header name as a lowercase binary string.\n\nReq::\n\nThe Req object.\n\nDefault::\n\nDefault value returned when the header is missing.\n\n== Return value\n\nThe header value is returned as a binary string. When the\nheader is missing, the default argument is returned.\n\n== Changelog\n\n* *2.0*: Only the header value is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the accept header\n[source,erlang]\n----\nAccept = cowboy_req:header(<<\"accept\">>, Req).\n----\n\n.Get the content-length header with a default value\n[source,erlang]\n----\nLength = cowboy_req:header(<<\"content-length\">>, Req, <<\"0\">>).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:headers(3)[cowboy_req:headers(3)],\nlink:man:cowboy_req:parse_header(3)[cowboy_req:parse_header(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.headers.asciidoc",
    "content": "= cowboy_req:headers(3)\n\n== Name\n\ncowboy_req:headers - HTTP headers\n\n== Description\n\n[source,erlang]\n----\nheaders(Req :: cowboy_req:req()) -> cowboy:http_headers()\n----\n\nReturn all request headers.\n\nRequest headers can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{headers := Headers} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nHeaders are returned as a map with keys being lowercase\nbinary strings, and values as binary strings.\n\n== Changelog\n\n* *2.0*: Only the headers are returned, they are no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get all headers\n[source,erlang]\n----\nHeaders = cowboy_req:headers(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:header(3)[cowboy_req:header(3)],\nlink:man:cowboy_req:parse_header(3)[cowboy_req:parse_header(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.host.asciidoc",
    "content": "= cowboy_req:host(3)\n\n== Name\n\ncowboy_req:host - URI host name\n\n== Description\n\n[source,erlang]\n----\nhost(Req :: cowboy_req:req()) -> Host :: binary()\n----\n\nReturn the host name of the effective request URI.\n\nThe host name can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{host := Host} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe host name is returned as a lowercase binary string.\nIt is case insensitive.\n\n== Changelog\n\n* *2.0*: Only the host name is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the effective request URI's host name\n[source,erlang]\n----\nHost = cowboy_req:host(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:binding(3)[cowboy_req:binding(3)],\nlink:man:cowboy_req:bindings(3)[cowboy_req:bindings(3)],\nlink:man:cowboy_req:host_info(3)[cowboy_req:host_info(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.host_info.asciidoc",
    "content": "= cowboy_req:host_info(3)\n\n== Name\n\ncowboy_req:host_info - Access the route's heading host segments\n\n== Description\n\n[source,erlang]\n----\nhost_info(Req :: cowboy_req:req()) -> cowboy_router:tokens()\n----\n\nReturn the tokens for the heading host segments.\n\nThis is the part of the host name that was matched using\nthe `...` notation.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe tokens are returned as a list of case insensitive\nbinary strings.\n\n== Changelog\n\n* *2.0*: Only the tokens are returned, they are no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the host_info tokens\n[source,erlang]\n----\nHostInfo = cowboy_req:host_info(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:binding(3)[cowboy_req:binding(3)],\nlink:man:cowboy_req:bindings(3)[cowboy_req:bindings(3)],\nlink:man:cowboy_req:path_info(3)[cowboy_req:path_info(3)],\nlink:man:cowboy_router(3)[cowboy_router(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.inform.asciidoc",
    "content": "= cowboy_req:inform(3)\n\n== Name\n\ncowboy_req:inform - Send an informational response\n\n== Description\n\n[source,erlang]\n----\ninform(Status, Req :: cowboy_req:req())\n    -> inform(StatusCode, #{}, Req)\n\ninform(Status, Headers, Req :: cowboy_req:req())\n    -> ok\n\nStatus  :: cowboy:http_status()\nHeaders :: cowboy:http_headers()\n----\n\nSend an informational response.\n\nInformational responses use a status code between 100 and 199.\nThey cannot include a body. This function will not use any\nof the previously set headers. All headers to be sent must\nbe given directly.\n\nAny number of informational responses can be sent as long as\nthey are sent before the proper response. Attempting to use\nthis function after sending a normal response will result\nin an error.\n\nThe header names must be given as lowercase binary strings.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\n== Arguments\n\nStatus::\n\nThe status code for the response.\n\nHeaders::\n\nThe response headers.\n+\nHeader names must be given as lowercase binary strings.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe atom `ok` is always returned. It can be safely ignored.\n\n== Changelog\n\n* *2.1*: Function introduced.\n\n== Examples\n\n.Send an informational response\n[source,erlang]\n----\nReq = cowboy_req:inform(102, Req0).\n----\n\n.Send an informational response with headers\n[source,erlang]\n----\nReq = cowboy_req:inform(103, #{\n    <<\"link\">> => <<\"</style.css>; rel=preload; as=style, \"\n        \"</script.js>; rel=preload; as=script\">>\n}, Req0).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:reply(3)[cowboy_req:reply(3)],\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)],\nlink:man:cowboy_req:push(3)[cowboy_req:push(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.match_cookies.asciidoc",
    "content": "= cowboy_req:match_cookies(3)\n\n== Name\n\ncowboy_req:match_cookies - Match cookies against constraints\n\n== Description\n\n[source,erlang]\n----\nmatch_cookies(Fields :: cowboy:fields(), Req :: cowboy_req:req())\n    -> #{atom() => any()}\n----\n\nParse the cookies and match specific values against\nconstraints.\n\nCowboy will only return the cookie values specified in the\nfields list, and ignore all others. Fields can be either\nthe name of the cookie requested; the name along with a\nlist of constraints; or the name, a list of constraints\nand a default value in case the cookie is missing.\n\nThis function will crash if the cookie is missing and no\ndefault value is provided. This function will also crash\nif a constraint fails.\n\nThe name of the cookie must be provided as an atom. The\nkey of the returned map will be that atom. The value may\nbe converted through the use of constraints, making this\nfunction able to extract, validate and convert values all\nin one step.\n\nThis function will crash on invalid cookie data. How to\nhandle this is explained in details in the manual page for\nlink:man:cowboy_req:parse_cookies(3)[cowboy_req:parse_cookies(3)].\n\n== Arguments\n\nFields::\n\nCookies to retrieve.\n+\nSee link:man:cowboy(3)[cowboy(3)] for a complete description.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nDesired values are returned as a map. The key is the atom\nthat was given in the list of fields, and the value is the\noptionally converted value after applying constraints.\n\nThe map contains the same keys that were given in the fields.\n\nAn exception is triggered when the match fails.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Match fields\n[source,erlang]\n----\n%% ID and Lang are binaries.\n#{id := ID, lang := Lang}\n    = cowboy_req:match_cookies([id, lang], Req).\n----\n\n.Match fields and apply constraints\n[source,erlang]\n----\n%% ID is an integer and Lang a non-empty binary.\n#{id := ID, lang := Lang}\n    = cowboy_req:match_cookies([{id, int}, {lang, nonempty}], Req).\n----\n\n.Match fields with default values\n[source,erlang]\n----\n#{lang := Lang}\n    = cowboy_req:match_cookies([{lang, [], <<\"en-US\">>}], Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:filter_cookies(3)[cowboy_req:filter_cookies(3)],\nlink:man:cowboy_req:parse_cookies(3)[cowboy_req:parse_cookies(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.match_qs.asciidoc",
    "content": "= cowboy_req:match_qs(3)\n\n== Name\n\ncowboy_req:match_qs - Match the query string against constraints\n\n== Description\n\n[source,erlang]\n----\nmatch_qs(Fields :: cowboy:fields(), Req :: cowboy_req:req())\n    -> #{atom() => any()}\n----\n\nParse the query string and match specific values against\nconstraints.\n\nCowboy will only return the query string values specified\nin the fields list, and ignore all others. Fields can be\neither the key requested; the key along with a list of\nconstraints; or the key, a list of constraints and a\ndefault value in case the key is missing.\n\nThis function will crash if the key is missing and no\ndefault value is provided. This function will also crash\nif a constraint fails.\n\nThe key must be provided as an atom. The key of the\nreturned map will be that atom. The value may be converted\nthrough the use of constraints, making this function able\nto extract, validate and convert values all in one step.\n\n== Arguments\n\nFields::\n\nFields to retrieve from the query string.\n+\nSee link:man:cowboy(3)[cowboy(3)] for a complete description.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nDesired values are returned as a map. The key is the atom\nthat was given in the list of fields, and the value is the\noptionally converted value after applying constraints.\n\nThe map contains the same keys that were given in the fields.\n\nAn exception is triggered when the match fails.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Match fields\n[source,erlang]\n----\n%% ID and Lang are binaries.\n#{id := ID, lang := Lang}\n    = cowboy_req:match_qs([id, lang], Req).\n----\n\n.Match fields and apply constraints\n[source,erlang]\n----\n%% ID is an integer and Lang a non-empty binary.\n#{id := ID, lang := Lang}\n    = cowboy_req:match_qs([{id, int}, {lang, nonempty}], Req).\n----\n\n.Match fields with default values\n[source,erlang]\n----\n#{lang := Lang}\n    = cowboy_req:match_qs([{lang, [], <<\"en-US\">>}], Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:qs(3)[cowboy_req:qs(3)],\nlink:man:cowboy_req:parse_qs(3)[cowboy_req:parse_qs(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.method.asciidoc",
    "content": "= cowboy_req:method(3)\n\n== Name\n\ncowboy_req:method - HTTP method\n\n== Description\n\n[source,erlang]\n----\nmethod(Req :: cowboy_req:req()) -> Method :: binary()\n----\n\nReturn the request's HTTP method.\n\nThe method can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{method := Method} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe request's HTTP method is returned as a binary string.\nWhile methods are case sensitive, standard methods are\nalways uppercase.\n\n== Changelog\n\n* *2.0*: Only the method is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Ensure the request's method is GET\n[source,erlang]\n----\n<<\"GET\">> = cowboy_req:method(Req).\n----\n\n.Allow methods from list\n[source,erlang]\n----\ninit(Req, State) ->\n    case lists:member(cowboy_req:method(Req), [<<\"GET\">>, <<\"POST\">>]) of\n        true -> handle(Req, State);\n        false -> method_not_allowed(Req, State)\n    end.\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.parse_cookies.asciidoc",
    "content": "= cowboy_req:parse_cookies(3)\n\n== Name\n\ncowboy_req:parse_cookies - Parse cookie headers\n\n== Description\n\n[source,erlang]\n----\nparse_cookies(Req) -> [{Name, Value}]\n\nName  :: binary() %% case sensitive\nValue :: binary() %% case sensitive\n----\n\nParse cookie headers.\n\nAlias for link:man:cowboy_req:parse_header(3)[cowboy_req:parse_header(<<\"cookie\">>, Req)].\n\nWhen the cookie header is missing or empty, `[]` is returned.\n\nThis function will crash on invalid cookie data. Because\ninvalid cookies are fairly common when dealing with browsers\n(because of the string interface that the Javascript API provides),\nit is recommended to filter the cookie header value before\nattempting to parse it. This can be accomplished by calling\nthe function link:man:cowboy_req:filter_cookies(3)[cowboy_req:filter_cookies(3)]\nfirst. This does not guarantee that parsing succeeds. If it\nstill fails it is recommended to send an error response or\nredirect with instructions to delete the relevant cookies:\n\n.Recover from cookie parsing errors\n[source,erlang]\n----\nReq1 = cowboy_req:filter_cookies([session_id, token], Req0),\ntry cowboy_req:parse_cookies(Req1) of\n    Cookies ->\n        do_something(Req1, Cookies)\ncatch _:_ ->\n    %% We can't parse the cookies we need, unset them\n    %% otherwise the browser will continue sending them.\n    Req2 = cowboy_req:set_resp_cookie(<<\"session_id\">>,\n        <<>>, Req1, #{max_age => 0}),\n    Req = cowboy_req:set_resp_cookie(<<\"token\">>,\n        <<>>, Req2, #{max_age => 0}),\n    cowboy_req:reply(500, Req)\nend.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe cookies are returned as a list of key/values. Keys and\nvalues are case sensitive binary strings.\n\n== Changelog\n\n* *2.0*: Only the parsed header value is returned, it is no longer wrapped in a tuple.\n* *2.0*: Function introduced. Replaces `cookie/2,3` and `cookies/1`.\n\n== Examples\n\n.Look for a specific cookie\n[source,erlang]\n----\nCookies = cowboy_req:parse_cookies(Req),\n{_, Token} = lists:keyfind(<<\"token\">>, 1, Cookies).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:parse_header(3)[cowboy_req:parse_header(3)],\nlink:man:cowboy_req:filter_cookies(3)[cowboy_req:filter_cookies(3)],\nlink:man:cowboy_req:match_cookies(3)[cowboy_req:match_cookies(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.parse_header.asciidoc",
    "content": "= cowboy_req:parse_header(3)\n\n== Name\n\ncowboy_req:parse_header - Parse the given HTTP header\n\n== Description\n\n[source,erlang]\n----\nparse_header(Name, Req)          -> ParsedValue | Default\nparse_header(Name, Req, Default) -> ParsedValue | Default\n\nName        :: binary()\nReq         :: cowboy_req:req()\nParsedValue :: any()\nDefault     :: any()\n----\n\nParse the given HTTP header.\n\nThe header name must be given as a lowercase binary string.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\nThe type of the parsed value varies depending on\nthe header. Similarly, the default value when calling\n`cowboy_req:parse_header/2` differs depending on the\nheader.\n\n== Arguments\n\nName::\n\nDesired HTTP header name as a lowercase binary string.\n\nReq::\n\nThe Req object.\n\nDefault::\n\nDefault value returned when the header is missing.\n\n== Return value\n\nThe parsed header value varies depending on the header.\nWhen the header is missing, the default argument is returned.\n\n== Headers\n\nThe following snippets detail the types returned by the\ndifferent headers. Unless mentioned otherwise, the\ndefault value when the header is missing will be `undefined`:\n\n.accept\n[source,erlang]\n----\nparse_header(<<\"accept\">>, Req)\n    -> [{{Type, SubType, Params}, Quality, AcceptExt}]\n\nType      :: binary()               %% case insensitive\nSubType   :: binary()               %% case insensitive\nParams    :: [{Key, Value}]\nQuality   :: 0..1000\nAcceptExt :: [Key | {Key, Value}]\nKey       :: binary()               %% case insensitive\nValue     :: binary()               %% case sensitive\n----\n\n.accept-charset, accept-encoding and accept-language\n[source,erlang]\n----\nparse_header(Name, Req) -> [{Value, Quality}]\n\nName    :: <<\"accept-charset\">>\n         | <<\"accept-encoding\">>\n         | <<\"accept-language\">>\nValue   :: binary()                 %% case insensitive\nQuality :: 0..1000\n----\n\n.access-control-request-headers\n[source,erlang]\n----\nparse_header(<<\"access-control-request-headers\">>, Req)\n    -> [Header]\n\nHeader :: binary()   %% case insensitive\n----\n\n.access-control-request-method\n[source,erlang]\n----\nparse_header(<<\"access-control-request-method\">>)\n    -> Method\n\nMethod :: binary()   %% case sensitive\n----\n\n.authorization and proxy-authorization\n[source,erlang]\n----\nparse_header(<<\"authorization\">>, Req)\n    -> {basic, Username :: binary(), Password :: binary()}\n     | {bearer, Token :: binary()}\n     | {digest, [{Key :: binary(), Value :: binary()}]}\n----\n\n// @todo Currently also parses connection. Do we want this? Should it be documented? Use case?\n\n.content-encoding and content-language\n[source,erlang]\n----\nparse_header(Name, Req) -> [Value]\n\nName  :: <<\"content-encoding\">>\n       | <<\"content-language\">>\nValue :: binary()                 %% case insensitive\n----\n\n.content-length\n[source,erlang]\n----\nparse_header(<<\"content-length\">>, Req) -> non_neg_integer()\n----\n\nWhen the content-length header is missing, `0` is returned.\n\n.content-type\n[source,erlang]\n----\nparse_header(<<\"content-type\">>, Req)\n    -> {Type, SubType, Params}\n\nType      :: binary()               %% case insensitive\nSubType   :: binary()               %% case insensitive\nParams    :: [{Key, Value}]\nKey       :: binary()               %% case insensitive\nValue     :: binary()               %% case sensitive;\n----\n\nNote that the value for the charset parameter is case insensitive\nand returned as a lowercase binary string.\n\n.cookie\n[source,erlang]\n----\nparse_header(<<\"cookie\">>, Req) -> [{Name, Value}]\n\nName  :: binary()                   %% case sensitive\nValue :: binary()                   %% case sensitive\n----\n\nWhen the cookie header is missing, `[]` is returned.\n\nWhile an empty cookie header is not valid, some clients do\nsend it. Cowboy will in this case also return `[]`.\n\n.expect\n[source,erlang]\n----\nparse_header(<<\"expect\">>, Req) -> continue\n----\n\n.if-match and if-none-match\n[source,erlang]\n----\nparse_header(Name, Req)\n    -> '*' | [{weak | strong, OpaqueTag}]\n\nName      :: <<\"if-match\">>\n           | <<\"if-none-match\">>\nOpaqueTag :: binary()               %% case sensitive\n----\n\n.if-modified-since and if-unmodified-since\n[source,erlang]\n----\nparse_header(Name, Req) -> calendar:datetime()\n----\n\n.max-forwards\n[source,erlang]\n----\nparse_header(<<\"max-forwards\">>, Req) -> non_neg_integer()\n----\n\n.origin\n[source,erlang]\n----\nparse_header(<<\"origin\">>, Req)\n    -> [{Scheme, Host, Port} | GUID]\n\nScheme :: <<\"http\">> | <<\"https\">>\nHost   :: binary()                   %% case insensitive\nPort   :: 0..65535\nGUID   :: reference()\n----\n\nCowboy generates a reference in place of a GUID when the URI\nuses an unsupported uri-scheme or is not an absolute URI.\n\n[source,erlang]\n----\nparse_header(<<\"range\">>, Req) -> {From, To} | Final\n\nFrom  :: non_neg_integer()\nTo    :: non_neg_integer() | infinity\nFinal :: neg_integer()\n----\n\n.sec-websocket-extensions\n[source,erlang]\n----\nparse_header(<<\"sec-websocket-extensions\">>, Req)\n    -> [{Extension, Params}]\n\nExtension :: binary()               %% case sensitive\nParams    :: [Key | {Key, Value}]\nKey       :: binary()               %% case sensitive\nValue     :: binary()               %% case sensitive\n----\n\n.sec-websocket-protocol and upgrade\n[source,erlang]\n----\nparse_header(Name, Req) -> [Token]\n\nName  :: <<\"sec-websocket-protocol\">>\n       | <<\"upgrade\">>\nToken :: binary()                   %% case insensitive\n----\n\n.trailer\n[source,erlang]\n----\nparse_header(Name, Req) -> [Header]\n\nHeader :: binary()   %% case insensitive\n----\n\n.x-forwarded-for\n[source,erlang]\n----\nparse_header(<<\"x-forwarded-for\">>, Req) -> [Token]\n\nToken :: binary()                   %% case sensitive\n----\n\nThis function will crash when attempting to parse a\nheader Cowboy does not currently understand.\n\n== Changelog\n\n* *2.8*: The function now parses `access-control-request-headers`,\n         `access-control-request-method`, `content-encoding`,\n         `content-language`, `max-forwards`, `origin`,\n         `proxy-authorization` and `trailer`.\n* *2.0*: Only the parsed header value is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Parse the accept header with a custom default value\n[source,erlang]\n----\n%% Accept everything when header is missing.\nAccept = cowboy_req:parse_header(<<\"accept\">>, Req,\n    [{{ <<\"*\">>, <<\"*\">>, []}, 1000, []}]).\n----\n\n.Parse the content-length header\n[source,erlang]\n----\n%% Default content-length is 0.\nLength = cowboy_req:header(<<\"content-length\">>, Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:header(3)[cowboy_req:header(3)],\nlink:man:cowboy_req:headers(3)[cowboy_req:headers(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.parse_qs.asciidoc",
    "content": "= cowboy_req:parse_qs(3)\n\n== Name\n\ncowboy_req:parse_qs - Parse the query string\n\n== Description\n\n[source,erlang]\n----\nparse_qs(Req :: cowboy_req:req())\n    -> [{Key :: binary(), Value :: binary() | true}]\n----\n\nParse the query string as a list of key/value pairs.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe parsed query string is returned as a list of key/value pairs.\nThe key is a binary string. The value is either a binary string,\nor the atom `true`. Both key and value are case sensitive.\n\nThe atom `true` is returned when a key is present in the query\nstring without a value. For example, in the following URIs\nthe key `<<\"edit\">>` will always have the value `true`:\n\n* `/posts/42?edit`\n* `/posts/42?edit&exclusive=1`\n* `/posts/42?exclusive=1&edit`\n* `/posts/42?exclusive=1&edit&from=web`\n\n== Changelog\n\n* *2.0*: The parsed value is not longer cached in the Req object.\n* *2.0*: Only the parsed query string is returned, it is no longer wrapped in a tuple.\n* *2.0*: Function introduced. Replaces `qs_val/1` and `qs_vals/1`.\n\n== Examples\n\n.Parse the query string and convert the keys to atoms\n[source,erlang]\n----\nParsedQs = cowboy_req:parse_qs(Req),\nAtomsQs = [{binary_to_existing_atom(K, latin1), V}\n    || {K, V} <- ParsedQs].\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:qs(3)[cowboy_req:qs(3)],\nlink:man:cowboy_req:match_qs(3)[cowboy_req:match_qs(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.path.asciidoc",
    "content": "= cowboy_req:path(3)\n\n== Name\n\ncowboy_req:path - URI path\n\n== Description\n\n[source,erlang]\n----\npath(Req :: cowboy_req:req()) -> Path :: binary()\n----\n\nReturn the path of the effective request URI.\n\nThe path can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{path := Path} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe path is returned as a binary string. It is case sensitive.\n\n== Changelog\n\n* *2.0*: Only the path is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the effective request URI's path\n[source,erlang]\n----\nPath = cowboy_req:path(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:binding(3)[cowboy_req:binding(3)],\nlink:man:cowboy_req:bindings(3)[cowboy_req:bindings(3)],\nlink:man:cowboy_req:path_info(3)[cowboy_req:path_info(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.path_info.asciidoc",
    "content": "= cowboy_req:path_info(3)\n\n== Name\n\ncowboy_req:path_info - Access the route's trailing path segments\n\n== Description\n\n[source,erlang]\n----\npath_info(Req :: cowboy_req:req()) -> cowboy_router:tokens()\n----\n\nReturn the tokens for the trailing path segments.\n\nThis is the part of the host name that was matched using\nthe `...` notation.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe tokens are returned as a list of case sensitive\nbinary strings.\n\n== Changelog\n\n* *2.0*: Only the tokens are returned, they are no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the path_info tokens\n[source,erlang]\n----\nPathInfo = cowboy_req:path_info(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:binding(3)[cowboy_req:binding(3)],\nlink:man:cowboy_req:bindings(3)[cowboy_req:bindings(3)],\nlink:man:cowboy_req:host_info(3)[cowboy_req:host_info(3)],\nlink:man:cowboy_router(3)[cowboy_router(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.peer.asciidoc",
    "content": "= cowboy_req:peer(3)\n\n== Name\n\ncowboy_req:peer - Peer address and port\n\n== Description\n\n[source,erlang]\n----\npeer(Req :: cowboy_req:req()) -> Info\n\nInfo :: {inet:ip_address(), inet:port_number()}\n----\n\nReturn the peer's IP address and port number.\n\nThe peer information can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{peer := {IP, Port}} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe peer's IP address and port number.\n\nThe peer is not necessarily the client's IP address and port.\nIt is the IP address of the endpoint connecting directly to\nthe server, which may be a gateway or a proxy.\n\nThe forwarded header can be used to get better information\nabout the different endpoints from the client to the server.\nNote however that it is only informative; there is no reliable\nway of determining the source of an HTTP request.\n\n== Changelog\n\n* *2.0*: Only the peer is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the peer IP address and port number.\n[source,erlang]\n----\n{IP, Port} = cowboy_req:peer(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:sock(3)[cowboy_req:sock(3)],\nlink:man:cowboy_req:cert(3)[cowboy_req:cert(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.port.asciidoc",
    "content": "= cowboy_req:port(3)\n\n== Name\n\ncowboy_req:port - URI port number\n\n== Description\n\n[source,erlang]\n----\nport(Req :: cowboy_req:req()) -> Port :: inet:port_number()\n----\n\nReturn the port number of the effective request URI.\n\nNote that the port number returned by this function is obtained\nby parsing the host header. It may be different from the port\nthe peer used to connect to Cowboy.\n\nThe port number can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{port := Port} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe port number is returned as an integer.\n\n== Changelog\n\n* *2.0*: Only the port number is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the effective request URI's port number\n[source,erlang]\n----\nPort = cowboy_req:port(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.push.asciidoc",
    "content": "= cowboy_req:push(3)\n\n== Name\n\ncowboy_req:push - Push a resource to the client\n\n== Description\n\n[source,erlang]\n----\npush(Path, Headers, Req :: cowboy_req:req())\n    -> push(Path, Headers, Req, #{})\n\npush(Path, Headers, Req :: cowboy_req:req(), Opts)\n    -> ok\n\nPath    :: iodata()                %% case sensitive\nHeaders :: cowboy:http_headers()\nOpts    :: cowboy_req:push_opts()\n----\n\nPush a resource to the client.\n\nCowboy handles push requests the same way as if they came\nfrom the client, including the creation of a request handling\nprocess, routing and middlewares and so on.\n\nThis function does nothing when the HTTP/1.1 protocol is\nused. You may call it safely without first checking whether\nthe connection uses HTTP/2.\n\nThe header names must be given as lowercase binary strings.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\nNote that the headers must be the headers the client is expected\nto send if it were to perform the request. They are therefore\nrequest headers, and not response headers.\n\nBy default, Cowboy will use the GET method, an empty query string,\nand take the scheme, host and port directly from the current\nrequest's URI. You can override them by passing options.\n\nNote that clients may cancel the push or ignore it entirely.\nFor example browsers may ignore the resource when the connection\nis not considered secure.\n\nIt is not possible to push resources after sending a response.\nAny attempt will result in an error.\n\n== Arguments\n\nPath::\n\nThe status code for the response.\n\nHeaders::\n\nThe response headers.\n+\nHeader names must be given as lowercase binary strings.\n\nReq::\n\nThe Req object.\n\nOpts::\n\nCustomize the HTTP method or the URI scheme, host, port\nor query string.\n\n== Return value\n\nThe atom `ok` is always returned. It can be safely ignored.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Push a resource\n[source,erlang]\n----\ncowboy_req:push(\"/static/style.css\", #{\n    <<\"accept\">> => <<\"text/css\">>\n}, Req),\n----\n\n.Push a resource with a custom host\n[source,erlang]\n----\ncowboy_req:push(\"/static/style.css\", #{\n    <<\"accept\">> => <<\"text/css\">>\n}, #{host => <<\"cdn.example.org\">>}, Req),\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:inform(3)[cowboy_req:inform(3)],\nlink:man:cowboy_req:reply(3)[cowboy_req:reply(3)],\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.qs.asciidoc",
    "content": "= cowboy_req:qs(3)\n\n== Name\n\ncowboy_req:qs - URI query string\n\n== Description\n\n[source,erlang]\n----\nqs(Req :: cowboy_req:req()) -> Qs :: binary()\n----\n\nReturn the query string of the effective request URI.\n\nThe query string can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{qs := Qs} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe query string is returned as a binary string. It is case sensitive.\n\n== Changelog\n\n* *2.0*: Only the query string is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the effective request URI's query string\n[source,erlang]\n----\nQs = cowboy_req:qs(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:parse_qs(3)[cowboy_req:parse_qs(3)],\nlink:man:cowboy_req:match_qs(3)[cowboy_req:match_qs(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.read_and_match_urlencoded_body.asciidoc",
    "content": "= cowboy_req:read_and_match_urlencoded_body(3)\n\n== Name\n\ncowboy_req:read_and_match_urlencoded_body - Read, parse\nand match a urlencoded request body against constraints\n\n== Description\n\n[source,erlang]\n----\nread_and_match_urlencoded_body(Fields, Req)\n    -> read_and_match_urlencoded_body(Fields, Req, #{})\n\nread_and_match_urlencoded_body(Fields, Req, Opts)\n    -> {ok, Body, Req}\n\nFields :: cowboy:fields()\nReq    :: cowboy_req:req()\nOpts   :: cowboy_req:read_body_opts()\nBody   :: #{atom() => any()}\n----\n\nRead, parse and match a urlencoded request body against\nconstraints.\n\nThis function reads the request body and parses it as\n`application/x-www-form-urlencoded`. It then applies\nthe given field constraints to the urlencoded data\nand returns the result as a map.\n\nThe urlencoded media type is used by Web browsers when\nsubmitting HTML forms using the POST method.\n\nCowboy will only return the values specified\nin the fields list, and ignore all others. Fields can be\neither the key requested; the key along with a list of\nconstraints; or the key, a list of constraints and a\ndefault value in case the key is missing.\n\nThis function will crash if the key is missing and no\ndefault value is provided. This function will also crash\nif a constraint fails.\n\nThe key must be provided as an atom. The key of the\nreturned map will be that atom. The value may be converted\nthrough the use of constraints, making this function able\nto extract, validate and convert values all in one step.\n\nCowboy needs to read the full body before parsing. By default\nit will read bodies of size up to 64KB. It is possible to\nprovide options to read larger bodies if required.\n\nCowboy will automatically handle protocol details including\nthe expect header, chunked transfer-encoding and others.\n\nOnce the body has been read, Cowboy sets the content-length\nheader if it was not previously provided.\n\nThis function can only be called once. Calling it again will\nresult in undefined behavior.\n\n== Arguments\n\nFields::\n\nFields to retrieve from the urlencoded body.\n+\nSee link:man:cowboy(3)[cowboy(3)] for a complete description.\n\nReq::\n\nThe Req object.\n\nOpts::\n\nA map of body reading options. Please refer to\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)]\nfor details about each option.\n+\nThis function defaults the `length` to 64KB and the `period`\nto 5 seconds.\n\n== Return value\n\nAn `ok` tuple is returned.\n\nDesired values are returned as a map. The key is the atom\nthat was given in the list of fields, and the value is the\noptionally converted value after applying constraints.\n\nThe map contains the same keys that were given in the fields.\n\nAn exception is triggered when the match fails.\n\nThe Req object returned in the tuple must be used from that point\nonward. It contains a more up to date representation of the request.\nFor example it may have an added content-length header once the\nbody has been read.\n\n== Changelog\n\n* *2.5*: Function introduced.\n\n== Examples\n\n.Match fields\n[source,erlang]\n----\n%% ID and Lang are binaries.\n#{id := ID, lang := Lang}\n    = cowboy_req:read_and_match_urlencoded_body(\n        [id, lang], Req).\n----\n\n.Match fields and apply constraints\n[source,erlang]\n----\n%% ID is an integer and Lang a non-empty binary.\n#{id := ID, lang := Lang}\n    = cowboy_req:read_and_match_urlencoded_body(\n        [{id, int}, {lang, nonempty}], Req).\n----\n\n.Match fields with default values\n[source,erlang]\n----\n#{lang := Lang}\n    = cowboy_req:read_and_match_urlencoded_body(\n        [{lang, [], <<\"en-US\">>}], Req).\n----\n\n.Allow large urlencoded bodies\n[source,erlang]\n----\n{ok, Body, Req} = cowboy_req:read_and_match_urlencoded_body(\n    Fields, Req0, #{length => 1000000}).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],\nlink:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],\nlink:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],\nlink:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)],\nlink:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.read_body.asciidoc",
    "content": "= cowboy_req:read_body(3)\n\n== Name\n\ncowboy_req:read_body - Read the request body\n\n== Description\n\n[source,erlang]\n----\nread_body(Req :: cowboy_req:req())\n    -> read_body(Req, #{})\n\nread_body(Req :: cowboy_req:req(), Opts)\n    -> {ok,   Data :: binary(), Req}\n     | {more, Data :: binary(), Req}\n\nOpts :: cowboy_req:read_body_opts()\n----\n\nRead the request body.\n\nThis function reads a chunk of the request body. A `more` tuple\nis returned when more data remains to be read. Call the function\nrepeatedly until an `ok` tuple is returned to read the entire body.\n\nAn `ok` tuple with empty data is returned when the request has no body,\nor when calling this function again after the body has already\nbeen read. It is therefore safe to call this function directly.\nNote that the body can only be read once.\n\nThis function reads the request body from the connection process.\nThe connection process is responsible for reading from the socket.\nThe exact behavior varies depending on the protocol.\n\nThe options therefore are only related to the communication\nbetween the request process and the connection process.\n\nCowboy will automatically handle protocol details including\nthe expect header, chunked transfer-encoding and others.\n\nOnce the body has been read fully, Cowboy sets the content-length\nheader if it was not previously provided.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\nOpts::\n\nA map of body reading options.\n+\nThe `length` option can be used to request smaller or bigger\nchunks of data to be sent. It is a best effort approach, Cowboy\nmay send more data than configured on occasions. It defaults\nto 8MB.\n+\nThe `period` indicates how long the connection process will wait\nbefore it provides us with the data it received. It defaults\nto 15 seconds.\n+\nThe connection process sends data to the request process when\neither the `length` of data or the `period` of time is reached.\n+\nThe `timeout` option is a safeguard in case the connection\nprocess becomes unresponsive. The function will crash if no\nmessage was received in that interval. The timeout should be\nlarger than the period. It defaults to the period + 1 second.\n+\nAuto mode can be enabled by setting the `length` to `auto` and\nthe `period` to `infinity`. When auto mode is used, Cowboy will\nsend data to the handler as soon as it receives it, regardless\nof its size. It will wait indefinitely until data is available.\nAuto mode's main purpose is asynchronous body reading using\nlink:man:cowboy_req:cast(3)[cowboy_req:cast(3)].\n\n== Return value\n\nA `more` tuple is returned when there are more data to be read.\n\nAn `ok` tuple is returned when there are no more data to be read,\neither because this is the last chunk of data, the body has already\nbeen read, or there was no body to begin with.\n\nThe data is always returned as a binary.\n\nThe Req object returned in the tuple must be used from that point\nonward. It contains a more up to date representation of the request.\nFor example it may have an added content-length header once the\nbody has been read.\n\n== Changelog\n\n* *2.11*: The `length` option now accepts `auto` and the\n  period now accepts `infinity`. This adds support for\n  reading the body in auto mode.\n* *2.0*: Function introduced. Replaces `body/1,2`.\n\n== Examples\n\n.Read the entire body\n[source,erlang]\n----\nread_body(Req0, Acc) ->\n    case cowboy_req:read_body(Req0) of\n        {ok, Data, Req} -> {ok, << Acc/binary, Data/binary >>, Req};\n        {more, Data, Req} -> read_body(Req, << Acc/binary, Data/binary >>)\n    end.\n----\n\n.Read the body in small chunks\n[source,erlang]\n----\ncowboy_req:read_body(Req, #{length => 64000}).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],\nlink:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],\nlink:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],\nlink:man:cowboy_req:read_and_match_urlencoded_body(3)[cowboy_req:read_and_match_urlencoded_body(3)],\nlink:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)],\nlink:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.read_part.asciidoc",
    "content": "= cowboy_req:read_part(3)\n\n== Name\n\ncowboy_req:read_part - Read the next multipart headers\n\n== Description\n\n[source,erlang]\n----\nread_part(Req :: cowboy_req:req())\n    -> read_part(Req, #{})\n\nread_part(Req :: cowboy_req:req(), Opts)\n    -> {ok, Headers, Req} | {done, Req}\n\nOpts    :: cowboy_req:read_body_opts()\nHeaders :: #{binary() => binary()}\n----\n\nRead the next part of a multipart body.\n\nThis function reads the request body and parses it as\nmultipart. Each parts of a multipart representation have\ntheir own headers and body. This function parses and returns\nheaders. Examples of multipart media types are\n`multipart/form-data` and `multipart/byteranges`.\n\nCowboy will skip any data remaining until the beginning of\nthe next part. This includes the preamble to the multipart\nmessage but also the body of a previous part if it hasn't\nbeen read. Both are skipped automatically when calling this\nfunction.\n\nCowboy will read the body before parsing in chunks of size\nup to 64KB, with a period of 5 seconds. This is tailored for\nreading part headers and might not be the most efficient for\nskipping the previous part's body.\n\nThe headers returned are MIME headers, *NOT* HTTP headers.\nThey can be parsed using the functions from the `cow_multipart`\nmodule. In addition, the `cow_multipart:form_data/1` function\ncan be used to quickly extract information from `multipart/form-data`\nrepresentations.\n\n// @todo Proper link to cow_multipart:form_data.\n\nOnce a part has been read, it can not be read again.\n\nOnce the body has been read, Cowboy sets the content-length\nheader if it was not previously provided.\n\n// @todo Limit the maximum size of multipart headers.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\nOpts::\n\nA map of body reading options. Please refer to\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)]\nfor details about each option.\n+\nThis function defaults the `length` to 64KB and the `period`\nto 5 seconds.\n\n== Return value\n\nAn `ok` tuple is returned containing the next part's headers\nas a map.\n\nA `done` tuple is returned if there are no more parts to read.\n\nThe Req object returned in the tuple must be used from that point\nonward. It contains a more up to date representation of the request.\nFor example it may have an added content-length header once the\nbody has been read.\n\n== Changelog\n\n* *2.0*: Function introduced. Replaces `part/1,2`.\n\n== Examples\n\n.Read all parts\n[source,erlang]\n----\nacc_multipart(Req0, Acc) ->\n    case cowboy_req:read_part(Req0) of\n        {ok, Headers, Req1} ->\n            {ok, Body, Req} = stream_body(Req1, <<>>),\n            acc_multipart(Req, [{Headers, Body}|Acc]);\n        {done, Req} ->\n            {lists:reverse(Acc), Req}\n    end.\n\nstream_body(Req0, Acc) ->\n    case cowboy_req:read_part_body(Req0) of\n        {more, Data, Req} ->\n            stream_body(Req, << Acc/binary, Data/binary >>);\n        {ok, Data, Req} ->\n            {ok, << Acc/binary, Data/binary >>, Req}\n    end.\n----\n\n.Read all part headers, skipping bodies\n[source,erlang]\n----\nskip_body_multipart(Req0, Acc) ->\n    case cowboy_req:read_part(Req0) of\n        {ok, Headers, Req} ->\n            skip_body_multipart(Req, [Headers|Acc]);\n        {done, Req} ->\n            {lists:reverse(Acc), Req}\n    end.\n----\n\n.Read a part header in larger chunks\n[source,erlang]\n----\n{ok, Headers, Req} = cowboy_req:read_part(Req0, #{length => 1000000}).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],\nlink:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],\nlink:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],\nlink:man:cowboy_req:read_and_match_urlencoded_body(3)[cowboy_req:read_and_match_urlencoded_body(3)],\nlink:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.read_part_body.asciidoc",
    "content": "= cowboy_req:read_part_body(3)\n\n== Name\n\ncowboy_req:read_part_body - Read the current part's body\n\n== Description\n\n[source,erlang]\n----\nread_part_body(Req :: cowboy_req:req())\n    -> read_part_body(Req, #{})\n\nread_part_body(Req :: cowboy_req:req(), Opts)\n    -> {ok,   Data :: binary(), Req}\n     | {more, Data :: binary(), Req}\n\nOpts :: cowboy_req:read_body_opts()\n----\n\nRead the body of the current part of the multipart message.\n\nThis function reads the request body and parses it as\nmultipart. Each parts of a multipart representation have\ntheir own headers and body. This function returns the\nbody of the current part. Examples of multipart media types\nare `multipart/form-data` and `multipart/byteranges`.\n\nThis function reads a chunk of the part's body. A `more` tuple\nis returned when more data remains to be read. Call the function\nrepeatedly until an `ok` tuple is returned to read the entire body.\n\nOnce a part has been read, it can not be read again.\n\nOnce the body has been read, Cowboy sets the content-length\nheader if it was not previously provided.\n\n// @todo Limit the maximum size of multipart headers.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\nOpts::\n\nA map of body reading options. Please refer to\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)]\nfor details about each option.\n+\nThis function uses the same default options as the\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)]\nfunction.\n\n== Return value\n\nA `more` tuple is returned when there are more data to be read.\n\nAn `ok` tuple is returned when there are no more data to be read.\n\nThe data is always returned as a binary.\n\nThe Req object returned in the tuple must be used from that point\nonward. It contains a more up to date representation of the request.\nFor example it may have an added content-length header once the\nbody has been read.\n\n== Changelog\n\n* *2.0*: Function introduced. Replaces `part_body/1,2`.\n\n== Examples\n\n.Read a full part's body\n[source,erlang]\n----\nstream_body(Req0, Acc) ->\n    case cowboy_req:read_part_body(Req0) of\n        {more, Data, Req} ->\n            stream_body(Req, << Acc/binary, Data/binary >>);\n        {ok, Data, Req} ->\n            {ok, << Acc/binary, Data/binary >>, Req}\n    end.\n----\n\n.Ensure a part's body is smaller than 64KB\n[source,erlang]\n----\n{ok, Body, Req} = cowboy_req:read_part_body(Req0, #{length => 64000}).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],\nlink:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],\nlink:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],\nlink:man:cowboy_req:read_and_match_urlencoded_body(3)[cowboy_req:read_and_match_urlencoded_body(3)],\nlink:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.read_urlencoded_body.asciidoc",
    "content": "= cowboy_req:read_urlencoded_body(3)\n\n== Name\n\ncowboy_req:read_urlencoded_body - Read and parse a urlencoded request body\n\n== Description\n\n[source,erlang]\n----\nread_urlencoded_body(Req :: cowboy_req:req())\n    -> read_urlencoded_body(Req, #{})\n\nread_urlencoded_body(Req :: cowboy_req:req(), Opts)\n    -> {ok, Body, Req}\n\nOpts :: cowboy_req:read_body_opts()\nBody :: [{Key :: binary(), Value :: binary() | true}]\n----\n\nRead and parse a urlencoded request body.\n\nThis function reads the request body and parses it as\n`application/x-www-form-urlencoded`. It returns a list\nof key/values.\n\nThe urlencoded media type is used by Web browsers when\nsubmitting HTML forms using the POST method.\n\nCowboy needs to read the full body before parsing. By default\nit will read bodies of size up to 64KB. It is possible to\nprovide options to read larger bodies if required.\n\nCowboy will automatically handle protocol details including\nthe expect header, chunked transfer-encoding and others.\n\nOnce the body has been read, Cowboy sets the content-length\nheader if it was not previously provided.\n\nThis function can only be called once. Calling it again will\nresult in undefined behavior.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\nOpts::\n\nA map of body reading options. Please refer to\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)]\nfor details about each option.\n+\nThis function defaults the `length` to 64KB and the `period`\nto 5 seconds.\n\n== Return value\n\nAn `ok` tuple is returned containing a list of key/values found\nin the body.\n\nThe Req object returned in the tuple must be used from that point\nonward. It contains a more up to date representation of the request.\nFor example it may have an added content-length header once the\nbody has been read.\n\n== Changelog\n\n* *2.0*: Function introduced. Replaces `body_qs/1,2`.\n\n== Examples\n\n.Read a urlencoded body\n[source,erlang]\n----\n{ok, Body, Req} = cowboy_req:read_urlencoded_body(Req0),\n{_, Lang} = lists:keyfind(<<\"lang\">>, 1, Body).\n----\n\n.Allow large urlencoded bodies\n[source,erlang]\n----\n{ok, Body, Req} = cowboy_req:read_urlencoded_body(Req0, #{length => 1000000}).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],\nlink:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],\nlink:man:cowboy_req:read_and_match_urlencoded_body(3)[cowboy_req:read_and_match_urlencoded_body(3)],\nlink:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)],\nlink:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.reply.asciidoc",
    "content": "= cowboy_req:reply(3)\n\n== Name\n\ncowboy_req:reply - Send the response\n\n== Description\n\n[source,erlang]\n----\nreply(Status, Req :: cowboy_req:req())\n    -> reply(StatusCode, #{}, Req)\n\nreply(Status, Headers, Req :: cowboy_req:req())\n    -> Req\n\nreply(Status, Headers, Body, Req :: cowboy_req:req())\n    -> Req\n\nStatus  :: cowboy:http_status()\nHeaders :: cowboy:http_headers()\nBody    :: cowboy_req:resp_body()\n----\n\nSend the response.\n\nThe header names must be given as lowercase binary strings.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\nCowboy does not allow duplicate header names. Headers set\nby this function may overwrite those set by `set_resp_header/3`\nand `set_resp_headers/2`.\n\nUse link:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)]\ninstead of this function to set cookies.\n\nThe `reply/2,3` functions will send the body set previously,\nif any. The `reply/4` function always sends the given body,\noverriding any previously set.\n\nYou do not need to set the content-length header when\nsending a response body. Cowboy takes care of it automatically.\nYou should however provide a content-type header.\n\nNo further data can be transmitted after this function\nreturns. This includes the push mechanism. Attempting to\nsend two replies, or to push resources after a reply has\nbeen sent, will result in an error.\n\n== Arguments\n\nStatus::\n\nThe status code for the response.\n\nHeaders::\n\nThe response headers.\n+\nHeader names must be given as lowercase binary strings.\n\nBody::\n\nThe body can be either a binary value, an iolist or a\n`sendfile` tuple telling Cowboy to send the contents of\na file.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nA new Req object is returned.\n\nThe returned Req object should be used from that point onward\nas it contains updated information about the state of the request.\n\n== Changelog\n\n* *2.0*: Only the Req is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Reply\n[source,erlang]\n----\nReq = cowboy_req:reply(404, Req0).\n----\n\n.Reply with custom headers\n[source,erlang]\n----\nReq = cowboy_req:reply(401, #{\n    <<\"www-authenticate\">> => <<\"Basic realm=\\\"erlang.org\\\"\">>\n}, Req0).\n----\n\n.Reply with custom headers and a body\n[source,erlang]\n----\nReq = cowboy_req:reply(200, #{\n    <<\"content-type\">> => <<\"text/plain\">>\n}, \"Hello world!\", Req0).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)],\nlink:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],\nlink:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)],\nlink:man:cowboy_req:set_resp_body(3)[cowboy_req:set_resp_body(3)],\nlink:man:cowboy_req:inform(3)[cowboy_req:inform(3)],\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)],\nlink:man:cowboy_req:push(3)[cowboy_req:push(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.resp_header.asciidoc",
    "content": "= cowboy_req:resp_header(3)\n\n== Name\n\ncowboy_req:resp_header - Response header\n\n== Description\n\n[source,erlang]\n----\nresp_header(Name, Req)          -> resp_header(Name, Req, undefined)\nresp_header(Name, Req, Default) -> binary() | Default\n\nName    :: binary()          %% lowercase; case insensitive\nReq     :: cowboy_req:req()\nDefault :: any()\n----\n\nReturn the value for the given response header.\n\nThe response header must have been set previously using\nlink:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)] or\nlink:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)].\n\nThe header name must be given as a lowercase binary string.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\n== Arguments\n\nName::\n\nDesired response header name as a lowercase binary string.\n\nReq::\n\nThe Req object.\n\nDefault::\n\nDefault value returned when the header is missing.\n\n== Return value\n\nThe header value is returned as a binary string. When the\nheader is missing, the default argument is returned.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Get the content-type response header\n[source,erlang]\n----\nType = cowboy_req:resp_header(<<\"content-type\">>, Req).\n----\n\n.Get the content-type response header with a default value\n[source,erlang]\n----\nType = cowboy_req:resp_header(<<\"content-type\">>, Req, <<\"text/html\">>).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:resp_headers(3)[cowboy_req:resp_headers(3)],\nlink:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],\nlink:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.resp_headers.asciidoc",
    "content": "= cowboy_req:resp_headers(3)\n\n== Name\n\ncowboy_req:resp_headers - Response headers\n\n== Description\n\n[source,erlang]\n----\nresp_headers(Req :: cowboy_req:req()) -> cowboy:http_headers()\n----\n\nReturn all response headers.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nHeaders are returned as a map with keys being lowercase\nbinary strings, and values as binary strings.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Get all response headers\n[source,erlang]\n----\nHeaders = cowboy_req:resp_headers(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:resp_header(3)[cowboy_req:resp_header(3)],\nlink:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],\nlink:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.scheme.asciidoc",
    "content": "= cowboy_req:scheme(3)\n\n== Name\n\ncowboy_req:scheme - URI scheme\n\n== Description\n\n[source,erlang]\n----\nscheme(Req :: cowboy_req:req()) -> Scheme :: binary()\n----\n\nReturn the scheme of the effective request URI.\n\nThe scheme can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{scheme := Scheme} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe scheme is returned as a binary. It is case insensitive.\n\nCowboy will only set the scheme to `<<\"http\">>` or `<<\"https\">>`.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Redirect HTTP to HTTPS\n[source,erlang]\n----\ninit(Req0=#{scheme := <<\"http\">>}, State) ->\n    Req = cowboy_req:reply(302, #{\n        <<\"location\">> => cowboy_req:uri(Req, #{scheme => <<\"https\">>})\n    }, Req0),\n    {ok, Req, State};\ninit(Req, State) ->\n    {cowboy_rest, Req, State}.\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.set_resp_body.asciidoc",
    "content": "= cowboy_req:set_resp_body(3)\n\n== Name\n\ncowboy_req:set_resp_body - Set the response body\n\n== Description\n\n[source,erlang]\n----\nset_resp_body(Body, Req :: cowboy_req:req())\n\t-> Req\n\nBody :: cowboy_req:resp_body()\n----\n\nSet the response body.\n\nThe response body will be sent when a reply is initiated.\nNote that the functions `stream_reply/2,3` and `reply/4`\nwill override the body set by this function.\n\nThis function can also be used to remove a response body\nthat was set previously. To do so, simply call this function\nwith an empty body.\n\n== Arguments\n\nBody::\n\nThe body can be either a binary value, an iolist or a\n`sendfile` tuple telling Cowboy to send the contents of\na file.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nA new Req object is returned.\n\nThe returned Req object must be used from that point onward,\notherwise the body will not be sent in the response.\n\n== Changelog\n\n* *2.0*: The function now accepts a `sendfile` tuple.\n* *2.0*: The `set_resp_body_fun/2,3` functions were removed.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Set the response body\n[source,erlang]\n----\nReq = cowboy_req:set_resp_body(<<\"Hello world!\">>, Req0).\n----\n\n.Set the response body as an iolist\n[source,erlang]\n----\nReq = cowboy_req:set_resp_body([\n    \"<html><head><title>\",\n    page_title(),\n    \"</title></head><body>\",\n    page_body(),\n    \"</body></html>\"\n], Req0).\n----\n\n.Tell Cowboy to send data from a file\n[source,erlang]\n----\n{ok, #file_info{size=Size}} = file:read_file_info(Filename),\nReq = cowboy_req:set_resp_body({sendfile, 0, Size, Filename}, Req0).\n----\n\n.Clear any previously set response body\n[source,erlang]\n----\nReq = cowboy_req:set_resp_body(<<>>, Req0).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],\nlink:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)],\nlink:man:cowboy_req:reply(3)[cowboy_req:reply(3)],\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.set_resp_cookie.asciidoc",
    "content": "= cowboy_req:set_resp_cookie(3)\n\n== Name\n\ncowboy_req:set_resp_cookie - Set a cookie\n\n== Description\n\n[source,erlang]\n----\nset_resp_cookie(Name, Value, Req :: cowboy_req:req())\n    -> set_resp_cookie(Name, Value, Req, #{})\n\nset_resp_cookie(Name, Value, Req :: cowboy_req:req(), Opts)\n    -> Req\n\nName  :: binary()                  %% case sensitive\nValue :: iodata()                  %% case sensitive\nOpts  :: cow_cookie:cookie_opts()\n----\n\nSet a cookie to be sent with the response.\n\nNote that cookie names are case sensitive.\n\n== Arguments\n\nName::\n\nCookie name.\n\nValue::\n\nCookie value.\n\nReq::\n\nThe Req object.\n\nOpts::\n\nCookie options.\n\n== Return value\n\nA new Req object is returned.\n\nThe returned Req object must be used from that point onward,\notherwise the cookie will not be sent in the response.\n\n== Changelog\n\n* *2.0*: `set_resp_cookie/3` introduced as an alias to `set_resp_cookie/4` with no options.\n* *2.0*: The first argument type is now `binary()` instead of `iodata()`.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Set a session cookie\n[source,erlang]\n----\nSessionID = base64:encode(crypto:strong_rand_bytes(32)),\nReq = cowboy_req:set_resp_cookie(<<\"sessionid\">>, SessionID, Req0).\n----\n\n.Set a cookie with an expiration time\n[source,erlang]\n----\nReq = cowboy_req:set_resp_cookie(<<\"lang\">>, <<\"fr-FR\">>,\n    Req0, #{max_age => 3600}).\n----\n\n.Delete a cookie\n[source,erlang]\n----\nReq = cowboy_req:set_resp_cookie(<<\"sessionid\">>, <<>>,\n    Req0, #{max_age => 0}).\n----\n\n.Set a cookie for a specific domain and path\n[source,erlang]\n----\nReq = cowboy_req:set_resp_cookie(<<\"inaccount\">>, <<\"1\">>,\n    Req0, #{domain => \"my.example.org\", path => \"/account\"}).\n----\n\n.Restrict a cookie to HTTPS\n[source,erlang]\n----\nSessionID = base64:encode(crypto:strong_rand_bytes(32)),\nReq = cowboy_req:set_resp_cookie(<<\"sessionid\">>, SessionID,\n    Req0, #{secure => true}).\n----\n\n.Restrict a cookie to HTTP\n[source,erlang]\n----\nSessionID = base64:encode(crypto:strong_rand_bytes(32)),\nReq = cowboy_req:set_resp_cookie(<<\"sessionid\">>, SessionID,\n    Req0, #{http_only => true}).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],\nlink:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)],\nlink:man:cowboy_req:reply(3)[cowboy_req:reply(3)],\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.set_resp_header.asciidoc",
    "content": "= cowboy_req:set_resp_header(3)\n\n== Name\n\ncowboy_req:set_resp_header - Set a response header\n\n== Description\n\n[source,erlang]\n----\nset_resp_header(Name, Value, Req :: cowboy_req:req())\n\t-> Req\n\nName  :: binary()  %% lowercase; case insensitive\nValue :: iodata()  %% case depends on header\n----\n\nSet a header to be sent with the response.\n\nThe header name must be given as a lowercase binary string.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\nCowboy does not allow duplicate header names. Headers set\nby this function may be overwritten by those set from the\nreply functions.\n\nUse link:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)]\ninstead of this function to set cookies.\n\n== Arguments\n\nName::\n\nHeader name as a lowercase binary string.\n\nValue::\n\nHeader value.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nA new Req object is returned.\n\nThe returned Req object must be used from that point onward,\notherwise the header will not be sent in the response.\n\n== Changelog\n\n* *1.0*: Function introduced.\n\n== Examples\n\n.Set a header in the response\n[source,erlang]\n----\nReq = cowboy_req:set_resp_header(<<\"allow\">>, \"GET\", Req0).\n----\n\n.Construct a header using iolists\n[source,erlang]\n----\nReq = cowboy_req:set_resp_header(<<\"allow\">>,\n    [allowed_methods(), \", OPTIONS\"], Req0).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)],\nlink:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)],\nlink:man:cowboy_req:has_resp_header(3)[cowboy_req:has_resp_header(3)],\nlink:man:cowboy_req:resp_header(3)[cowboy_req:resp_header(3)],\nlink:man:cowboy_req:resp_headers(3)[cowboy_req:resp_headers(3)],\nlink:man:cowboy_req:delete_resp_header(3)[cowboy_req:delete_resp_header(3)],\nlink:man:cowboy_req:reply(3)[cowboy_req:reply(3)],\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.set_resp_headers.asciidoc",
    "content": "= cowboy_req:set_resp_headers(3)\n\n== Name\n\ncowboy_req:set_resp_headers - Set several response headers\n\n== Description\n\n[source,erlang]\n----\nset_resp_headers(Headers, Req :: cowboy_req:req())\n    -> Req\n\nHeaders :: cowboy:http_headers() | [{binary(), iodata()}]\n----\n\nSet several headers to be sent with the response.\n\nThe header name must be given as a lowercase binary string.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\nCowboy does not allow duplicate header names. Headers set\nby this function may be overwritten by those set from the\nreply functions. Likewise, headers set by this function may\noverwrite headers that were set previously.\n\nUse link:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)]\ninstead of this function to set cookies.\n\n== Arguments\n\nHeaders::\n\nHeaders as a map with names being lowercase binary strings,\nand values as iodata; or as a list with the same requirements\nfor names and values.\n+\nWhen a list is given it is converted to its equivalent map,\nwith duplicate headers concatenated with a comma inserted\nin-between. Support for lists is meant to simplify using\ndata from clients or other applications.\n+\nThe set-cookie header must not be set using this function.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nA new Req object is returned.\n\nThe returned Req object must be used from that point onward,\notherwise the headers will not be sent in the response.\n\n== Changelog\n\n* *2.13*: The function now accepts a list of headers.\n* *2.0*: Function introduced.\n\n== Examples\n\n.Set several response headers\n[source,erlang]\n----\nReq = cowboy_req:set_resp_headers(#{\n    <<\"content-type\">>     => <<\"text/html\">>,\n    <<\"content-encoding\">> => <<\"gzip\">>\n}, Req0).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)],\nlink:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],\nlink:man:cowboy_req:has_resp_header(3)[cowboy_req:has_resp_header(3)],\nlink:man:cowboy_req:resp_header(3)[cowboy_req:resp_header(3)],\nlink:man:cowboy_req:resp_headers(3)[cowboy_req:resp_headers(3)],\nlink:man:cowboy_req:delete_resp_header(3)[cowboy_req:delete_resp_header(3)],\nlink:man:cowboy_req:reply(3)[cowboy_req:reply(3)],\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.sock.asciidoc",
    "content": "= cowboy_req:sock(3)\n\n== Name\n\ncowboy_req:sock - Socket address and port\n\n== Description\n\n[source,erlang]\n----\nsock(Req :: cowboy_req:req()) -> Info\n\nInfo :: {inet:ip_address(), inet:port_number()}\n----\n\nReturn the socket's IP address and port number.\n\nThe socket information can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{sock := {IP, Port}} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe socket's local IP address and port number.\n\n== Changelog\n\n* *2.1*: Function introduced.\n\n== Examples\n\n.Get the socket's IP address and port number.\n[source,erlang]\n----\n{IP, Port} = cowboy_req:sock(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:peer(3)[cowboy_req:peer(3)],\nlink:man:cowboy_req:cert(3)[cowboy_req:cert(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.stream_body.asciidoc",
    "content": "= cowboy_req:stream_body(3)\n\n== Name\n\ncowboy_req:stream_body - Stream the response body\n\n== Description\n\n[source,erlang]\n----\nstream_body(Data, IsFin, Req :: cowboy_req:req()) -> ok\n\nData  :: cowboy_req:resp_body()\nIsFin :: fin | nofin\n----\n\nStream the response body.\n\nThis function may be called as many times as needed after\ninitiating a response using the\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]\nfunction.\n\nThe second argument indicates if this call is the final\ncall. Use the `nofin` value until you know no more data\nwill be sent. The final call should use `fin` (possibly\nwith an empty data value) or be a call to the\nlink:man:cowboy_req:stream_trailers(3)[cowboy_req:stream_trailers(3)]\nfunction.\n\nNote that not using `fin` for the final call is not an\nerror; Cowboy will take care of it when the request\nhandler terminates if needed. Depending on the resource\nit may however be more efficient to do it as early as\npossible.\n\nYou do not need to handle HEAD requests specifically as\nCowboy will ensure no data is sent when you call this function.\n\n== Arguments\n\nData::\n\nThe data to be sent.\n\nIsFin::\n\nA flag indicating whether this is the final piece of data\nto be sent.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe atom `ok` is always returned. It can be safely ignored.\n\n== Changelog\n\n* *2.6*: The `Data` argument can now be a sendfile tuple.\n* *2.0*: Function introduced. Replaces `chunk/2`.\n\n== Examples\n\n.Stream the response body\n[source,erlang]\n----\nReq = cowboy_req:stream_reply(200, #{\n    <<\"content-type\">> => <<\"text/plain\">>\n}, Req0),\ncowboy_req:stream_body(<<\"Hello\\n\">>, nofin, Req),\ntimer:sleep(1000),\ncowboy_req:stream_body(<<\"World!\\n\">>, fin, Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)],\nlink:man:cowboy_req:stream_events(3)[cowboy_req:stream_events(3)],\nlink:man:cowboy_req:stream_trailers(3)[cowboy_req:stream_trailers(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.stream_events.asciidoc",
    "content": "= cowboy_req:stream_events(3)\n\n== Name\n\ncowboy_req:stream_events - Stream events\n\n== Description\n\n[source,erlang]\n----\nstream_events(Events, IsFin, Req :: cowboy_req:req()) -> ok\n\nEvents :: Event | [Event]\nIsFin  :: fin | nofin\n\nEvent  :: #{\n    comment => iodata(),\n    data    => iodata(),\n    event   => iodata() | atom(),\n    id      => iodata(),\n    retry   => non_neg_integer()\n}\n----\n\nStream events.\n\nThis function should only be used for `text/event-stream`\nresponses when using server-sent events. Cowboy will\nautomatically encode the given events to their text\nrepresentation.\n\nThis function may be called as many times as needed after\ninitiating a response using the\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]\nfunction.\n\nThe second argument indicates if this call is the final\ncall. Use the `nofin` value until you know no more data\nwill be sent. The final call should use `fin` (possibly\nwith an empty data value) or be a call to the\nlink:man:cowboy_req:stream_trailers(3)[cowboy_req:stream_trailers(3)]\nfunction.\n\nNote that not using `fin` for the final call is not an\nerror; Cowboy will take care of it when the request\nhandler terminates if needed. Depending on the resource\nit may however be more efficient to do it as early as\npossible.\n\nYou do not need to handle HEAD requests specifically as\nCowboy will ensure no data is sent when you call this function.\n\n== Arguments\n\nEvents::\n\nEvents to be sent. All fields are optional.\n\nIsFin::\n\nA flag indicating whether this is the final piece of data\nto be sent.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe atom `ok` is always returned. It can be safely ignored.\n\n== Changelog\n\n* *2.5*: Function introduced.\n\n== Examples\n\n.Stream events\n[source,erlang]\n----\nReq = cowboy_req:stream_reply(200, #{\n    <<\"content-type\">> => <<\"text/event-stream\">>\n}, Req0),\ncowboy_req:stream_events(#{\n    id => <<\"comment-123\">>,\n    event => <<\"add_comment\">>,\n    data => <<\"Hello,\\n\\nI noticed something wrong in ...\">>\n}, nofin, Req),\ntimer:sleep(1000),\ncowboy_req:stream_events(#{\n    event => <<\"debug\">>,\n    data => io_lib:format(\"An error occurred: ~p~n\", [Error])\n}, fin, Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)],\nlink:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)],\nlink:man:cowboy_req:stream_trailers(3)[cowboy_req:stream_trailers(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.stream_reply.asciidoc",
    "content": "= cowboy_req:stream_reply(3)\n\n== Name\n\ncowboy_req:stream_reply - Send the response headers\n\n== Description\n\n[source,erlang]\n----\nstream_reply(Status, Req :: cowboy_req:req())\n    -> stream_reply(StatusCode, #{}, Req)\n\nstream_reply(Status, Headers, Req :: cowboy_req:req())\n    -> Req\n\nStatus  :: cowboy:http_status()\nHeaders :: cowboy:http_headers()\n----\n\nSend the response headers.\n\nThe header names must be given as lowercase binary strings.\nWhile header names are case insensitive, Cowboy requires them\nto be given as lowercase to function properly.\n\nCowboy does not allow duplicate header names. Headers set\nby this function may overwrite those set by `set_resp_header/3`.\n\nUse link:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)]\ninstead of this function to set cookies.\n\nIf a response body was set before calling this function,\nit will not be sent.\n\nUse link:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)]\nto stream the response body and optionally\nlink:man:cowboy_req:stream_trailers(3)[cowboy_req:stream_trailers(3)]\nto send response trailer field values.\n\nYou may want to set the content-length header when using\nthis function, if it is known in advance. This will allow\nclients using HTTP/2 and HTTP/1.0 to process the response\nmore efficiently.\n\nThe streaming method varies depending on the protocol being\nused. HTTP/2 will use the usual DATA frames. HTTP/1.1 will\nuse chunked transfer-encoding, if the content-length\nresponse header is set the body will be sent without chunked\nchunked transfer-encoding. HTTP/1.0 will send the body\nunmodified and close the connection at the end if no\ncontent-length was set.\n\nIt is not possible to push resources after this function\nreturns. Any attempt will result in an error.\n\n== Arguments\n\nStatus::\n\nThe status code for the response.\n\nHeaders::\n\nThe response headers.\n+\nHeader names must be given as lowercase binary strings.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nA new Req object is returned.\n\nThe returned Req object must be used from that point onward\nin order to be able to stream the response body.\n\n== Changelog\n\n* *2.0*: Only the Req is returned, it is no longer wrapped in a tuple.\n* *2.0*: Function introduced. Replaces `chunked_reply/1,2`.\n\n== Examples\n\n.Initiate the response\n[source,erlang]\n----\nReq = cowboy_req:stream_reply(200, Req0).\n----\n\n.Stream the response with custom headers\n[source,erlang]\n----\nReq = cowboy_req:stream_reply(200, #{\n    <<\"content-type\">> => <<\"text/plain\">>\n}, Req0),\ncowboy_req:stream_body(<<\"Hello\\n\">>, nofin, Req),\ntimer:sleep(1000),\ncowboy_req:stream_body(<<\"World!\\n\">>, fin, Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:set_resp_cookie(3)[cowboy_req:set_resp_cookie(3)],\nlink:man:cowboy_req:set_resp_header(3)[cowboy_req:set_resp_header(3)],\nlink:man:cowboy_req:set_resp_headers(3)[cowboy_req:set_resp_headers(3)],\nlink:man:cowboy_req:inform(3)[cowboy_req:inform(3)],\nlink:man:cowboy_req:reply(3)[cowboy_req:reply(3)],\nlink:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)],\nlink:man:cowboy_req:stream_events(3)[cowboy_req:stream_events(3)],\nlink:man:cowboy_req:stream_trailers(3)[cowboy_req:stream_trailers(3)],\nlink:man:cowboy_req:push(3)[cowboy_req:push(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.stream_trailers.asciidoc",
    "content": "= cowboy_req:stream_trailers(3)\n\n== Name\n\ncowboy_req:stream_trailers - Send the response trailers\n\n== Description\n\n[source,erlang]\n----\nstream_trailers(Trailers, Req :: cowboy_req:req()) -> ok\n\nTrailers :: cowboy:http_headers()\n----\n\nSend the response trailers and terminate the stream.\n\nThis function can only be called once, after initiating\na response using\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)]\nand sending zero or more body chunks using\nlink:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)]\nwith the `nofin` argument set. The function `stream_trailers/2`\nimplies `fin` and automatically terminate the response.\n\nYou must list all field names sent in trailers in the\ntrailer header, otherwise they might be dropped by intermediaries\nor clients.\n\n== Arguments\n\nTrailers::\n\nTrailer field values to be sent.\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe atom `ok` is always returned. It can be safely ignored.\n\n== Changelog\n\n* *2.2*: Function introduced.\n\n== Examples\n\n.Stream a response body with trailers\n[source,erlang]\n----\nReq = cowboy_req:stream_reply(200, #{\n    <<\"content-type\">> => <<\"text/plain\">>,\n    <<\"trailer\">> => <<\"expires, content-md5\">>\n}, Req0),\ncowboy_req:stream_body(<<\"Hello\\n\">>, nofin, Req),\ntimer:sleep(1000),\ncowboy_req:stream_body(<<\"World!\\n\">>, nofin, Req).\ncowboy_req:stream_trailers(#{\n    <<\"expires\">> => <<\"Sun, 10 Dec 2017 19:13:47 GMT\">>,\n    <<\"content-md5\">> => <<\"fbf68a8e34b2ded53bba54e68794b4fe\">>\n}, Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:stream_reply(3)[cowboy_req:stream_reply(3)],\nlink:man:cowboy_req:stream_body(3)[cowboy_req:stream_body(3)],\nlink:man:cowboy_req:stream_events(3)[cowboy_req:stream_events(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.uri.asciidoc",
    "content": "= cowboy_req:uri(3)\n\n== Name\n\ncowboy_req:uri - Reconstructed URI\n\n== Description\n\n[source,erlang]\n----\nuri(Req :: cowboy_req:req())       -> uri(Req, #{})\nuri(Req :: cowboy_req:req(), Opts) -> URI :: iodata()\n\nOpts :: #{\n    scheme   => iodata()           | undefined,\n    host     => iodata()           | undefined,\n    port     => inet:port_number() | undefined,\n    path     => iodata()           | undefined,\n    qs       => iodata()           | undefined,\n    fragment => iodata()           | undefined\n}\n----\n\nReconstruct the effective request URI, optionally modifying components.\n\nBy default Cowboy will build a URI using the components found\nin the request. Options allow disabling or replacing individual\ncomponents.\n\n== Arguments\n\nReq::\n\nThe Req object.\n\nOpts::\n\nMap for overriding individual components.\n+\nTo replace a component, provide its new value as a binary\nstring or an iolist. To disable a component, set its value\nto `undefined`.\n+\nAs this function always returns a valid URI, there are some\nthings to note:\n+\n    * Disabling the host also disables the scheme and port.\n    * There is no fragment component by default as these are\n      not sent with the request.\n    * The port number may not appear in the resulting URI if\n      it is the default port for the given scheme (http: 80; https: 443).\n\n== Return value\n\nThe reconstructed URI is returned as an iolist or a binary string.\n\n== Changelog\n\n* *2.0*: Individual components can be replaced or disabled.\n* *2.0*: Only the URI is returned, it is no longer wrapped in a tuple.\n* *2.0*: Function introduced. Replaces `host_url/1` and `url/1`.\n\n== Examples\n\nWith an effective request URI http://example.org/path/to/res?edit=1\nwe can have:\n\n.Protocol relative form\n[source,erlang]\n----\n%% //example.org/path/to/res?edit=1\ncowboy_req:uri(Req, #{scheme => undefined}).\n----\n\n.Serialized origin for use in the origin header\n[source,erlang]\n----\n%% http://example.org\ncowboy_req:uri(Req, #{path => undefined, qs => undefined}).\n----\n\n.HTTP/1.1 origin form (path and query string only)\n[source,erlang]\n----\n%% /path/to/res?edit=1\ncowboy_req:uri(Req, #{host => undefined}).\n----\n\n.Add a fragment to the URI\n[source,erlang]\n----\n%% http://example.org/path/to/res?edit=1#errors\ncowboy_req:uri(Req, #{fragment => <<\"errors\">>}).\n----\n\n.Ensure the scheme is HTTPS\n[source,erlang]\n----\n%% https://example.org/path/to/res?edit=1\ncowboy_req:uri(Req, #{scheme => <<\"https\">>}).\n----\n\n.Convert the URI to a binary string\n[source,erlang]\n----\niolist_to_binary(cowboy_req:uri(Req)).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)],\nlink:man:cowboy_req:scheme(3)[cowboy_req:scheme(3)],\nlink:man:cowboy_req:host(3)[cowboy_req:host(3)],\nlink:man:cowboy_req:port(3)[cowboy_req:port(3)],\nlink:man:cowboy_req:path(3)[cowboy_req:path(3)],\nlink:man:cowboy_req:qs(3)[cowboy_req:qs(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_req.version.asciidoc",
    "content": "= cowboy_req:version(3)\n\n== Name\n\ncowboy_req:version - HTTP version\n\n== Description\n\n[source,erlang]\n----\nversion(Req :: cowboy_req:req()) -> Version :: cowboy:http_version()\n----\n\nReturn the HTTP version used for the request.\n\nThe version can also be obtained using pattern matching:\n\n[source,erlang]\n----\n#{version := Version} = Req.\n----\n\n== Arguments\n\nReq::\n\nThe Req object.\n\n== Return value\n\nThe HTTP version used for the request is returned as an\natom. It is provided for informative purposes only.\n\n== Changelog\n\n* *2.0*: Only the version is returned, it is no longer wrapped in a tuple.\n* *1.0*: Function introduced.\n\n== Examples\n\n.Get the HTTP version\n[source,erlang]\n----\nVersion = cowboy_req:version(Req).\n----\n\n== See also\n\nlink:man:cowboy_req(3)[cowboy_req(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_rest.asciidoc",
    "content": "= cowboy_rest(3)\n\n== Name\n\ncowboy_rest - REST handlers\n\n== Description\n\nThe module `cowboy_rest` implements the HTTP state machine.\n\nImplementing REST handlers is not enough to provide a REST\ninterface; this interface must also follow the REST\nconstraints including HATEOAS (hypermedia as the engine\nof application state).\n\n== Callbacks\n\nREST handlers implement the following interface:\n\n[source,erlang]\n----\ninit(Req, State)\n    -> {cowboy_rest, Req, State}\n\nCallback(Req, State)\n    -> {Result, Req, State}\n     | {stop, Req, State}\n     | {{switch_handler, Module}, Req, State}\n     | {{switch_handler, Module, Opts}, Req, State}\n\nterminate(Reason, Req, State) -> ok  %% optional\n\nReq    :: cowboy_req:req()\nState  :: any()\nModule :: module()\nOpts   :: any()\nReason :: normal\n        | {crash, error | exit | throw, any()}\n\nCallback - see below\nResult   - see below\nDefault  - see below\n----\n\nThe `init/2` callback is common to all handlers. To switch\nto the REST handler behavior, it must return `cowboy_rest`\nas the first element of the tuple.\n\nThe `Callback/2` above represents all the REST-specific\ncallbacks. They are described in the following section\nof this manual. REST-specific callbacks differ by their\nname, semantics, result and default values. The default\nvalue is the one used when the callback has not been\nimplemented. They otherwise all follow the same interface.\n\nThe `stop` tuple can be returned to stop REST processing.\nIf no response was sent before then, Cowboy will send a\n'204 No Content'. The `stop` tuple can be returned from\nany callback, excluding `expires`, `generate_etag`,\n`last_modified` and `variances`.\n\nA `switch_handler` tuple can be returned from these same\ncallbacks to stop REST processing and switch to a different\nhandler type. This is very useful to, for example, to stream\nthe response body.\n\nThe optional `terminate/3` callback will ultimately be called\nwith the reason for the termination of the handler.\nCowboy will terminate the process right after this. There\nis no need to perform any cleanup in this callback.\n\nThe following terminate reasons are defined for loop handlers:\n\nnormal::\n    The handler terminated normally.\n\n{crash, Class, Reason}::\n    A crash occurred in the handler. `Class` and `Reason` can be\n    used to obtain more information about the crash.\n\n== REST callbacks\n\n=== AcceptCallback\n\n[source,erlang]\n----\nAcceptCallback(Req, State) -> {Result, Req, State}\n\nResult  :: true\n         | {created, URI :: iodata()}\n         | {see_other, URI :: iodata()}\n         | false\nDefault  - crash\n----\n\nProcess the request body.\n\nThis function should create or update the resource using the\nrequest body.\n\nFor PUT requests, the body is a representation of the resource\nthat is being created or replaced.\n\nFor POST requests, the body is typically application-specific\ninstructions on how to process the request, but it may also be a\nrepresentation of the resource. When creating a new resource with POST\nat a different location, return `{created, URI}` or `{see_other, URI}`\nwith `URI` the new location.\n\nThe `see_other` tuple will redirect the client to the new location\nautomatically.\n\nFor PATCH requests, the body is a series of instructions on\nhow to update the resource. Patch files or JSON Patch are\nexamples of such media types.\n\nA response body may be sent. The appropriate media type, charset\nand language for the response can be retrieved from the Req\nobject using the `media_type`, `charset` and `language` keys,\nrespectively. The body can be set using\nlink:man:cowboy_req:set_resp_body(3)[cowboy_req:set_resp_body(3)].\n\n=== allowed_methods\n\n[source,erlang]\n----\nallowed_methods(Req, State) -> {Result, Req, State}\n\nResult  :: [binary()]  %% case sensitive\nDefault :: [<<\"GET\">>, <<\"HEAD\">>, <<\"OPTIONS\">>]\n----\n\nReturn the list of allowed methods.\n\n=== allow_missing_post\n\n[source,erlang]\n----\nallow_missing_post(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: true\n----\n\nReturn whether POST is allowed when the resource doesn't exist.\n\nReturning `true` here means that a new resource will be\ncreated. The URI for the newly created resource should be\nreturned from the `AcceptCallback` function.\n\n=== charsets_provided\n\n[source,erlang]\n----\ncharsets_provided(Req, State) -> {Result, Req, State}\n\nResult  :: [binary()]  %% lowercase; case insensitive\nDefault  - skip this step\n----\n\nReturn the list of charsets the resource provides in order\nof preference.\n\nDuring content negotiation Cowboy will pick the most\nappropriate charset for the client. The client advertises\ncharsets it prefers with the accept-charset header. When\nthat header is missing, Cowboy picks the first charset\nfrom the resource.\n\n// @todo We should explain precisely how charsets are picked.\n\nCowboy will add the negotiated `charset` to the Req object\nafter this step completes:\n\n[source,erlang]\n----\nreq() :: #{\n    charset => binary()  %% lowercase; case insensitive\n}\n----\n\nNote that Cowboy will only append the charset to the\ncontent-type header of the response if the media type is text.\n\n=== content_types_accepted\n\n[source,erlang]\n----\ncontent_types_accepted(Req, State) -> {Result, Req, State}\n\nResult     :: [{'*' | binary() | ParsedMime, AcceptCallback :: atom()}]\nParsedMime :: {Type :: binary(), SubType :: binary(), '*' | Params}\nParams     :: [{Key :: binary(), Value :: binary()}]\n\nDefault     - crash\n----\n\n// @todo Case sensitivity of parsed mime content?\n\nReturn the list of media types the resource accepts in\norder of preference.\n\nA media type is made of different parts. The media type\n`text/html;charset=utf-8` is of type `text`, subtype `html`\nand has a single parameter `charset` with value `utf-8`.\n\nThe special value `'*'` can be used to accept any media type.\n\n// @todo Cowboy needs to ignore the boundary parameter for\n// multipart, as we never want to match against it. Or allow\n// ignoring specific parameters at the very least.\n\nCowboy will match the content-type request header against\nthe media types the server accepts and select the appropriate\ncallback. When that header is missing, or when the server does not\naccept this media type, the request fails and an error response\nis returned. Cowboy will execute the callback immediately otherwise.\n\n// @todo We should explain precisely how media types are picked.\n\nAn empty parameters list `[]` means that no parameters will be\naccepted. When any parameter is acceptable, the tuple form\nshould be used with parameters as the atom `'*'`.\n\nCowboy treats all parameters as case sensitive, except for the\n`charset` parameter, which is known to be case insensitive. You\nshould therefore always provide the charset as a lowercase\nbinary string.\n\n// @todo Maybe this should be in the user guide instead.\n//This function will be called for POST, PUT and PATCH requests.\n//It is entirely possible to define different callbacks for different\n//methods if the handling of the request differs. Simply verify\n//what the method is with `cowboy_req:method/1` and return a\n//different list for each methods.\n\n=== content_types_provided\n\n[source,erlang]\n----\ncontent_types_provided(Req, State) -> {Result, Req, State}\n\nResult     :: [{binary() | ParsedMime, ProvideCallback :: atom()}]\nParsedMime :: {Type :: binary(), SubType :: binary(), '*' | Params}\nParams     :: [{Key :: binary(), Value :: binary()}]\n\nDefault     - [{{ <<\"text\">>, <<\"html\">>, '*'}, to_html}]\n----\n\n// @todo Case sensitivity of parsed mime content?\n// @todo Space required for the time being: https://github.com/spf13/hugo/issues/2398\n\nReturn the list of media types the resource provides in\norder of preference.\n\nA media type is made of different parts. The media type\n`text/html;charset=utf-8` is of type `text`, subtype `html`\nand has a single parameter `charset` with value `utf-8`.\n\n// @todo Cowboy needs to ignore the boundary parameter for\n// multipart, as we never want to match against it. Or allow\n// ignoring specific parameters at the very least.\n\nDuring content negotiation Cowboy will pick the most appropriate\nmedia type for the client. The client advertises media types it\nprefers with the accept header. When that header is missing,\nthe content negotiation fails and an error response is returned.\n\nThe callback given for the selected media type will be called\nat the end of the execution of GET and HEAD requests when a\nrepresentation must be sent to the client.\n\n// @todo We should explain precisely how media types are picked.\n\nAn empty parameters list `[]` means that no parameters will be\naccepted. When any parameter is acceptable, the tuple form\nshould be used with parameters as the atom `'*'`.\n\nCowboy treats all parameters as case sensitive, except for the\n`charset` parameter, which is known to be case insensitive. You\nshould therefore always provide the charset as a lowercase\nbinary string.\n\nWhen a charset is given in the media type parameters in the\naccept header, Cowboy will do some additional checks to confirm\nthat it can use this charset. When the wildcard is used then Cowboy\nwill immediately call `charsets_provided` to confirm the charset\nis acceptable. If the callback is undefined Cowboy assumes any\ncharset is acceptable. When the wildcard is not used and the charset\ngiven in the accept header matches one of the configured media\ntypes Cowboy will use that charset and skip the `charsets_provided`\nstep entirely.\n\nCowboy will add the negotiated `media_type` to the Req object\nafter this step completes:\n\n[source,erlang]\n----\nreq() :: #{\n    media_type => ParsedMime\n}\n----\n\n// @todo Case sensitivity of parsed mime content?\n\nCowboy may also add the negotiated `charset` to the Req object\nafter this step completes:\n\n[source,erlang]\n----\nreq() :: #{\n    charset => binary()  %% lowercase; case insensitive\n}\n----\n\n=== delete_completed\n\n[source,erlang]\n----\ndelete_completed(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: true\n----\n\nReturn whether the resource has been fully deleted from the\nsystem, including from any internal cache.\n\nReturning `false` will result in a '202 Accepted' response\nbeing sent instead of a '200 OK' or '204 No Content'.\n\n=== delete_resource\n\n[source,erlang]\n----\ndelete_resource(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: false\n----\n\nDelete the resource.\n\nCowboy will send an error response when this function\nreturns `false`.\n\n=== expires\n\n[source,erlang]\n----\nexpires(Req, State) -> {Result, Req, State}\n\nResult  :: calendar:datetime() | binary() | undefined\nDefault :: undefined\n----\n\nReturn the resource's expiration date.\n\n=== forbidden\n\n[source,erlang]\n----\nforbidden(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: false\n----\n\nReturn whether access to the resource is forbidden.\n\nA '403 Forbidden' response will be sent if this\nfunction returns `true`. This status code means that\naccess is forbidden regardless of authentication,\nand that the request shouldn't be repeated.\n\n=== generate_etag\n\n[source,erlang]\n----\ngenerate_etag(Req, State) -> {Result, Req, State}\n\nResult  :: binary() | {weak | strong, binary()} | undefined\nDefault  - no etag value\n----\n\nReturn the entity tag of the resource.\n\nWhen a binary is returned, the value is automatically\nparsed to a tuple. The binary must be in the same\nformat as the etag header, including quotes.\n\nIt is possible to conditionally generate an etag.\nWhen no etag can be generated, `undefined` should\nbe returned.\n\n=== is_authorized\n\n[source,erlang]\n----\nis_authorized(Req, State) -> {Result, Req, State}\n\nResult  :: true | {false, AuthHeader :: iodata()}\nDefault  - true\n----\n\nReturn whether the user is authorized to perform the action.\n\nThis function should be used to perform any necessary\nauthentication of the user before attempting to perform\nany action on the resource.\n\nWhen authentication fails, the `AuthHeader` value will\nbe sent in the www-authenticate header for the\n'401 Unauthorized' response.\n\n=== is_conflict\n\n[source,erlang]\n----\nis_conflict(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: false\n----\n\nReturn whether the PUT request results in a conflict.\n\nA '409 Conflict' response is sent when `true`.\n\n=== known_methods\n\n[source,erlang]\n----\nknown_methods(Req, State) -> {Result, Req, State}\n\nResult  :: [binary()]  %% case sensitive\nDefault :: [<<\"GET\">>, <<\"HEAD\">>, <<\"POST\">>, <<\"PUT\">>,\n            <<\"PATCH\">>, <<\"DELETE\">>, <<\"OPTIONS\">>]\n----\n\nReturn the list of known methods.\n\nThe full list of methods known by the server should be\nreturned, regardless of their use in the resource.\n\nThe default value lists the methods Cowboy knows and\nimplement in `cowboy_rest`.\n\n=== languages_provided\n\n[source,erlang]\n----\nlanguages_provided(Req, State) -> {Result, Req, State}\n\nResult  :: [binary()]  %% lowercase; case insensitive\nDefault  - skip this step\n----\n\nReturn the list of languages the resource provides in order\nof preference.\n\nDuring content negotiation Cowboy will pick the most\nappropriate language for the client. The client advertises\nlanguages it prefers with the accept-language header. When\nthat header is missing, Cowboy picks the first language\nfrom the resource.\n\n// @todo We should explain precisely how languages are picked.\n\nCowboy will add the negotiated `language` to the Req object\nafter this step completes:\n\n[source,erlang]\n----\nreq() :: #{\n    language => binary()  %% lowercase; case insensitive\n}\n----\n\n=== last_modified\n\n[source,erlang]\n----\nlast_modified(Req, State) -> {Result, Req, State}\n\nResult  :: calendar:datetime() | undefined\nDefault  - no last modified value\n----\n\nReturn the resource's last modification date.\n\nThis date will be used to test against the if-modified-since\nand if-unmodified-since headers, and sent as the last-modified\nheader in the response to GET and HEAD requests.\n\nWhen `undefined` is returned, no last-modified header is\nadded to response. Can be useful if you save timestamp on store\naction in memory and lose it after restart.\n\n=== malformed_request\n\n[source,erlang]\n----\nmalformed_request(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: false\n----\n\nReturn whether the request is malformed.\n\nA request is malformed when a component required by the\nresource is invalid. This may include the query string\nor individual headers. They should be parsed and validated\nin this function. The body should not be read at this point.\n\n=== moved_permanently\n\n[source,erlang]\n----\nmoved_permanently(Req, State) -> {Result, Req, State}\n\nResult  :: {true, URI :: iodata()} | false\nDefault :: false\n----\n\nReturn whether the resource was permanently moved, and\nwhat its new location is.\n\n=== moved_temporarily\n\n[source,erlang]\n----\nmoved_temporarily(Req, State) -> {Result, Req, State}\n\nResult  :: {true, URI :: iodata()} | false\nDefault :: false\n----\n\nReturn whether the resource was temporarily moved, and\nwhat its new location is.\n\n=== multiple_choices\n\n[source,erlang]\n----\nmultiple_choices(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: false\n----\n\nReturn whether the client should engage in reactive\nnegotiation.\n\nReturn `true` when the server has multiple representations\nof a resource, each with their specific identifier, but is\nunable to determine which is best for the client. For\nexample an image might have different sizes and the server\nis unable to determine the capabilities of the client.\n\nWhen returning `true` the server should send a body with\nlinks to the different representations. If the server has\na preferred representation it can send its link inside a\nlocation header.\n\nNote that when replying manually in this callback you\nshould either call `cowboy_req:reply/4` or remove the\nresponse body that Cowboy sets to avoid surprises.\n\n=== options\n\n[source,erlang]\n----\noptions(Req, State) -> {ok, Req, State}\n----\n\nRespond to an OPTIONS request.\n\nThe response should inform the client the communication\noptions available for this resource. By default Cowboy\nwill send a '200 OK' response with the allow header set.\n\n=== previously_existed\n\n[source,erlang]\n----\npreviously_existed(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: false\n----\n\nReturn whether the resource existed previously.\n\n=== ProvideCallback\n\n[source,erlang]\n----\nProvideCallback(Req, State) -> {Result, Req, State}\n\nResult  :: cowboy_req:resp_body()\nDefault  - crash\n----\n\nReturn the response body.\n\nThe response body can be provided either as the actual data\nto be sent or a tuple indicating which file to send.\n\nThis function is called for both GET and HEAD requests. For\nthe latter the body is not sent: it is only used to calculate\nthe content length.\n\n// @todo Perhaps we can optimize HEAD requests and just\n// allow calculating the length instead of returning the\n// whole thing.\n\nIt is possible to stream the response body either by manually\nsending the response and returning a `stop` value; or by\nswitching to a different handler (for example a loop handler)\nand manually sending the response. All headers already set\nby Cowboy will also be included in the response.\n\n== RangeCallback\n\n[source,erlang]\n----\nRangeCallback(Req, State) -> {Result, Req, State}\n\nResult  :: [{Range, Body}]\nRange   :: {From, To, Total} | binary()\nFrom    :: non_neg_integer()\nTo      :: non_neg_integer()\nTotal   :: non_neg_integer() | '*'\nBody    :: cowboy_req:resp_body()\nDefault  - crash\n----\n\nReturn a list of ranges for the response body.\n\nThe range selected can be found in the key `range`\nin the Req object, as indicated in `range_satisfiable`.\n\nInstead of returning the full response body as would\nbe done in the `ProvideCallback`, a list of ranges\nmust be returned. There can be one or more range.\nWhen one range is returned, a normal ranged response\nis sent. When multiple ranges are returned, Cowboy\nwill automatically send a multipart/byteranges\nresponse.\n\nWhen the total is not known the atom `'*'` can be\nreturned.\n\n== ranges_provided\n\n[source,erlang]\n----\nranges_provided(Req, State) -> {Result, Req, State}\n\nResult :: [Range | Auto]\nRange  :: {\n    binary(),  %% lowercase; case insensitive\n    RangeCallback :: atom()\n}\nAuto   :: {<<\"bytes\">>, auto}\nDefault - skip this step\n----\n\nReturn the list of range units the resource provides.\n\nDuring content negotiation Cowboy will build an accept-ranges\nresponse header with the list of ranges provided. Cowboy\ndoes not choose a range at this time; ranges are choosen\nwhen it comes time to call the `ProvideCallback`.\n\nBy default ranged requests will be handled the same as normal\nrequests: the `ProvideCallback` will be called and the full\nresponse body will be sent.\n\nIt is possible to let Cowboy handle ranged responses\nautomatically when the range unit is bytes and the\natom returned is `auto` (instead of a callback name).\nIn that case Cowboy will call the `ProvideCallback`\nand split the response automatically, including by\nproducing a multipart/byteranges response if necessary.\n\n== range_satisfiable\n\n[source,erlang]\n----\nrange_satisfiable(Req, State) -> {Result, Req, State}\n\nResult  :: boolean() | {false, non_neg_integer() | iodata()}\nDefault :: true\n----\n\nWhether the range request is satisfiable.\n\nWhen the time comes to send the response body, and when\nranges have been provided via the `ranges_provided`\ncallback, Cowboy will process the if-range and the\nrange request headers and ensure it is satisfiable.\n\nThis callback allows making resource-specific checks\nbefore sending the ranged response. The default is\nto accept sending a ranged response.\n\nCowboy adds the requested `range` to the Req object\njust before calling this callback:\n\n[source,erlang]\n----\nreq() :: #{\n    range => {\n        binary(),  %% lowercase; case insensitive\n        Range\n    }\n}\n\nRange     :: ByteRange | binary()\n\nByteRange :: [{FirstByte, LastByte | infinity} | SuffixLen]\nFirstByte :: non_neg_integer()\nLastByte  :: non_neg_integer()\nSuffixLen :: neg_integer()\n----\n\nOnly byte ranges are parsed. Other ranges are provided\nas binary. Byte ranges may either be requested from first\nto last bytes (inclusive); from first bytes to the end\n(`infinity` is used to represent the last byte); or\nthe last bytes of the representation via a negative\ninteger (so -500 means the last 500 bytes).\n\nReturning `false` will result in a 416 Range Not Satisfiable\nresponse being sent. The content-range header will be\nset automatically in the response if a tuple is\nreturned. The integer value represents the total\nsize (in the choosen unit) of the resource. An\niodata value may also be returned and will be\nused as-is to build the content range header,\nprepended with the unit choosen.\n\n=== rate_limited\n\n[source,erlang]\n----\nrate_limited(Req, State) -> {Result, Req, State}\n\nResult     :: false | {true, RetryAfter}\nRetryAfter :: non_neg_integer() | calendar:datetime()\nDefault    :: false\n----\n\nReturn whether the user is rate limited.\n\nThis function can be used to temporarily restrict\naccess to a resource when the user has issued too\nmany requests.\n\nWhen the resource is rate limited the `RetryAfter`\nvalue will be sent in the retry-after header for the\n'429 Too Many Requests' response. It indicates when\nthe resource will become available again and can be\nspecified as a number of seconds in the future or a\nspecific date/time.\n\n=== resource_exists\n\n[source,erlang]\n----\nresource_exists(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: true\n----\n\nReturn whether the resource exists.\n\n=== service_available\n\n[source,erlang]\n----\nservice_available(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: true\n----\n\nReturn whether the service is available.\n\nA '503 Service Unavailable' response will be sent when this\nfunction returns `false`.\n\n=== uri_too_long\n\n[source,erlang]\n----\nuri_too_long(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: false\n----\n\nReturn whether the requested URI is too long.\n\nThis function can be used to further restrict the length\nof the URI for this specific resource.\n\n=== valid_content_headers\n\n[source,erlang]\n----\nvalid_content_headers(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: true\n----\n\nReturn whether the content headers are valid.\n\nThis callback can be used to reject requests that have\ninvalid content header values, for example an unsupported\ncontent-encoding.\n\n=== valid_entity_length\n\n[source,erlang]\n----\nvalid_entity_length(Req, State) -> {Result, Req, State}\n\nResult  :: boolean()\nDefault :: true\n----\n\nReturn whether the request body length is within acceptable boundaries.\n\nA '413 Request Entity Too Large' response will be sent if this\nfunction returns `false`.\n\n=== variances\n\n[source,erlang]\n----\nvariances(Req, State) -> {Result, Req, State}\n\nResult  :: [binary()]  %% case insensitive\nDefault :: []\n----\n\nReturn the list of request headers that affect the\nrepresentation of the resource.\n\nCowboy automatically adds the accept, accept-charset and\naccept-language headers when necessary. It's also useful\nto note that some standard headers also do not need to be\nlisted here, like the authorization header.\n\n== Changelog\n\n* *2.14*: The `last_modified` callback is now type correct\n          when returning `undefined` to avoid responding\n          a last-modified header.\n* *2.11*: The `ranges_provided`, `range_satisfiable` and\n          the `RangeCallback` callbacks have been added.\n* *2.11*: The `generate_etag` callback can now return\n          `undefined` to conditionally avoid generating\n          an etag.\n* *2.9*: An `AcceptCallback` can now return `{created, URI}` or\n         `{see_other, URI}`. The return value `{true, URI}`\n         is deprecated.\n* *2.7*: The media type wildcard in `content_types_accepted`\n         is now documented.\n* *2.6*: The callback `rate_limited` was added.\n* *2.1*: The `switch_handler` return value was added.\n* *1.0*: Behavior introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_handler(3)[cowboy_handler(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_router.asciidoc",
    "content": "= cowboy_router(3)\n\n== Name\n\ncowboy_router - Router middleware\n\n== Description\n\nThe `cowboy_router` middleware maps the requested host and\npath to the handler to be used for processing the request.\n\nThe router takes the `dispatch` rules as input from the\nmiddleware environment. Dispatch rules are generated by\ncalling the\nlink:man:cowboy_router:compile(3)[cowboy_router:compile(3)]\nfunction. The environment can contain the rules directly\nor a tuple `{persistent_term, Key}`, in which case Cowboy\nwill call `persistent_term:get(Key)` to retrieve the\ndispatch rules.\n\nWhen a route matches, the router sets the `handler` and\n`handler_opts` middleware environment values containing\nthe handler module and initial state, respectively.\n\nThe router will stop execution when no route matches.\nIt will send a 400 response if no host was found, and\na 404 response otherwise.\n\n== Exports\n\n* link:man:cowboy_router:compile(3)[cowboy_router:compile(3)] - Compile routes to the resources\n\n== Types\n\n=== bindings()\n\n[source,erlang]\n----\nbindings() :: #{atom() => any()}\n----\n\nBindings found during routing.\n\n=== dispatch_rules()\n\nOpaque type containing the compiled routes.\n\n=== routes()\n\n[source,erlang]\n----\nroutes() = [\n    {Host, PathList} |\n    {Host, Fields, PathList}\n]\n\nPathList :: [\n    {Path, Handler, InitialState} |\n    {Path, Fields, Handler, InitialState}\n]\n\nHost         :: '_' | iodata()\nPath         :: '_' | iodata()\nFields       :: cowboy:fields()\nHandler      :: module()\nInitialState :: any()\n----\n\nHuman readable list of routes to handlers.\n\nCowboy uses this list to map hosts and paths, optionally\naugmented with constraints applied to the bindings, to\nhandler modules.\n\nThe syntax for routes is currently defined in the user guide.\n\n// @todo The syntax should probably be in this module,\n// and the user guide show more practical examples.\n\n=== tokens()\n\n[source,erlang]\n----\ntokens() :: [binary()]\n----\n\nList of `host_info` and `path_info` tokens that were found\nusing the `...` syntax.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_req:binding(3)[cowboy_req:binding(3)],\nlink:man:cowboy_req:bindings(3)[cowboy_req:bindings(3)],\nlink:man:cowboy_req:host_info(3)[cowboy_req:host_info(3)],\nlink:man:cowboy_req:path_info(3)[cowboy_req:path_info(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_router.compile.asciidoc",
    "content": "= cowboy_router:compile(3)\n\n== Name\n\ncowboy_router:compile - Compile routes to the resources\n\n== Description\n\n[source,erlang]\n----\ncompile(cowboy_router:routes()) -> cowboy_router:dispatch_rules()\n----\n\nCompile routes to the resources.\n\nTakes a human readable list of routes and transforms it\ninto a form more efficient to process.\n\n== Arguments\n\nRoutes::\n\nHuman readable list of routes.\n\n== Return value\n\nAn opaque dispatch rules value is returned. This value\nmust be given to Cowboy as a middleware environment value.\n\n== Changelog\n\n* *1.0*: Function introduced.\n\n== Examples\n\n.Compile routes and start a listener\n[source,erlang]\n----\nDispatch = cowboy_router:compile([\n    {'_', [\n        {\"/\", toppage_h, []},\n        {\"/[...]\", cowboy_static, {priv_dir, my_example_app, \"\"}}\n    ]}\n]),\n\n{ok, _} = cowboy:start_clear(example, [{port, 8080}], #{\n    env => #{dispatch => Dispatch}\n}).\n----\n\n== See also\n\nlink:man:cowboy_router(3)[cowboy_router(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_static.asciidoc",
    "content": "= cowboy_static(3)\n\n== Name\n\ncowboy_static - Static file handler\n\n== Description\n\nThe module `cowboy_static` implements file serving capabilities\nusing the REST semantics provided by `cowboy_rest`.\n\nThe static file handler is a pre-written handler coming with\nCowboy. To serve files, use it in your routes.\n\n== Options\n\n[source,erlang]\n----\nopts() :: {priv_file, App, Path}\n        | {priv_file, App, Path, Extra}\n        | {file, Path}\n        | {file, Path, Extra}\n        | {priv_dir, App, Path}\n        | {priv_dir, App, Path, Extra}\n        | {dir, Path}\n        | {dir, Path, Extra}\n\nApp        :: atom()\nPath       :: binary() | string()\nExtra      :: [Charset | Etag | Mimetypes]\n\nCharset    :: {charset, module(), function()}\n            | {charset, binary()}\n\nEtag       :: {etag, module(), function()}\n            | {etag, false}\n\nMimetypes  :: {mimetypes, module(), function()}\n            | {mimetypes, binary() | ParsedMime}\n\nParsedMime :: {Type :: binary(), SubType :: binary(), Params}\nParams     :: [{Key :: binary(), Value :: binary()}]\n----\n\nStatic handler configuration.\n\npriv_file::\n\nSend a file.\n+\nThe path is relative to the given application's private\ndirectory.\n\nfile::\n\nSend a file.\n+\nThe path is either absolute or relative to the Erlang node's\ncurrent directory.\n\npriv_dir::\n\nRecursively serve files from a directory.\n+\nThe path is relative to the given application's private\ndirectory.\n\ndir::\n\nRecursively serve files from a directory.\n+\nThe path is either absolute or relative to the Erlang node's\ncurrent directory.\n\nThe extra options allow you to define how the etag should be\ncalculated and how the MIME type of files should be detected.\n\nBy default the static handler will not send a charset with\nthe response. You can provide a specific charset that will\nbe used for all files using the text media type, or provide\na module and function that will be called when needed:\n\n[source,erlang]\n----\ndetect_charset(Path :: binary()) -> Charset :: binary()\n----\n\nA charset must always be returned even if it doesn't make\nsense considering the media type of the file. A good default\nis `<<\"utf-8\">>`.\n\nBy default the static handler will generate an etag based\non the size and modification time of the file. You may disable\nthe etag entirely with `{etag, false}` or provide a module\nand function that will be called when needed:\n\n[source,erlang]\n----\ngenerate_etag(Path, Size, Mtime) -> {strong | weak, binary()}\n\nPath  :: binary()\nSize  :: non_neg_integer()\nMtime :: file:date_time()\n----\n\nBy default the static handler will detect Web-related MIME types\nby looking at the file extension. You can provide a specific\nMIME type that will always be used, or a module and function that\nwill be called when needed:\n\n[source,erlang]\n----\ndetect_mimetype(Path) -> ParsedMime\n\nPath       :: binary()\nParsedMime :: {Type :: binary(), SubType :: binary(), Params}\nParams     :: [{Key :: binary(), Value :: binary()}]\n----\n\n// @todo Case sensitivity of parsed mime content?\n\nCowboy comes with two such functions; the default function\n`cow_mimetypes:web/1`, and a second function generated from\nthe Apache 'mime.types' file, `cow_mimetypes:all/1`.\n\nThe MIME type function should return\n`{<<\"application\">>, <<\"octet-stream\">>, []}`\nwhen it fails to detect a file's MIME type.\n\n== Changelog\n\n* *2.11*: Support for range requests was added in 2.6 and\n          is now considered stable.\n* *2.6*: The `charset` extra option was added.\n* *1.0*: Handler introduced.\n\n== Examples\n\n.Custom etag function\n[source,erlang]\n----\ngenerate_etag(Path, Size, Mtime) ->\n    {strong, integer_to_binary(\n        erlang:phash2({Path, Size, Mtime}, 16#ffffffff))}.\n----\n\n.Custom MIME type function\n[source,erlang]\n----\nalways_octet_stream(_Path) ->\n    case filename:extension(Path) of\n        <<\".erl\">> -> {<<\"text\">>, <<\"plain\">>, []};\n        _ -> {<<\"application\">>, <<\"octet-stream\">>, []}\n    end.\n----\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_router(3)[cowboy_router(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_stream.asciidoc",
    "content": "= cowboy_stream(3)\n\n== Name\n\ncowboy_stream - Stream handlers\n\n== Description\n\nThe module `cowboy_stream` defines a callback interface\nand a protocol for handling HTTP streams.\n\nAn HTTP request and its associated response is called\na stream. A connection may have many streams. In HTTP/1.1\nthey are executed sequentially, while in HTTP/2 they are\nexecuted concurrently.\n\nCowboy calls the stream handler for nearly all events\nrelated to a stream. Exceptions vary depending on the\nprotocol.\n\nExtra care must be taken when implementing stream handlers\nto ensure compatibility. While some modification of the\nevents and commands is allowed, it is generally not a good\nidea to completely discard them.\n\n== Callbacks\n\nStream handlers must implement the following interface:\n\n[source,erlang]\n----\ninit(StreamID, Req, Opts) -> {Commands, State}\ndata(StreamID, IsFin, Data, State) -> {Commands, State}\ninfo(StreamID, Info, State) -> {Commands, State}\nterminate(StreamID, Reason, State) -> any()\nearly_error(StreamID, Reason, PartialReq, Resp, Opts) -> Resp\n\nStreamID   :: cowboy_stream:streamid()\nReq        :: cowboy_req:req()\nOpts       :: cowboy:opts()\nCommands   :: cowboy_stream:commands()\nState      :: any()\nIsFin      :: cowboy_stream:fin()\nData       :: binary()\nInfo       :: any()\nReason     :: cowboy_stream:reason()\nPartialReq  - cowboy_req:req(), except all fields are optional\nResp       :: cowboy_stream:resp_command()\n----\n\nHTTP/1.1 will initialize a stream only when the request-line\nand all headers have been received. When errors occur before\nthat point Cowboy will call the callback `early_error/5`\nwith a partial request, the error reason and the response\nCowboy intends to send. All other events go through the\nstream handler using the normal callbacks.\n\nHTTP/2 will initialize the stream when the `HEADERS` block has\nbeen fully received and decoded. Any protocol error occurring\nbefore that will not result in a response being sent and\nwill therefore not go through the stream handler. In addition\nCowboy may terminate streams without sending an HTTP response\nback.\n\nThe stream is initialized by calling `init/3`. All streams\nthat are initialized will eventually be terminated by calling\n`terminate/3`.\n\nWhen Cowboy receives data for the stream it will call `data/4`.\nThe data given is the request body after any transfer decoding\nhas been applied.\n\nWhen Cowboy receives a message addressed to a stream, or when\nCowboy needs to inform the stream handler that an internal\nevent has occurred, it will call `info/3`.\n\n[[commands]]\n== Commands\n\nStream handlers can return a list of commands to be executed\nfrom the `init/3`, `data/4` and `info/3` callbacks. In addition,\nthe `early_error/5` callback must return a response command.\n\n// @todo The logger option and the {log, Level, Format, Args}\n// options need to be documented and tested.\n\nThe order in which the commands are given matters. For example,\nwhen sending a response and at the same time creating a new child\nprocess, the first command should be the `spawn` and the second the\n`response`. The reason for that is that the sending of the response\nmay result in a socket error which leads to the termination of\nthe connection before the rest of the commands are executed.\n\nThe following commands are defined:\n\n[[inform_command]]\n=== inform\n\nSend an informational response to the client.\n\n[source,erlang]\n----\n{inform, cowboy:http_status(), cowboy:http_headers()}\n----\n\nAny number of informational responses may be sent,\nbut only until the final response is sent.\n\n[[response_command]]\n=== response\n\nSend a response to the client.\n\n[source,erlang]\n----\n{response, cowboy:http_status(), cowboy:http_headers(),\n    cowboy_req:resp_body()}\n----\n\nNo more data can be sent after this command.\n\nNote that in Cowboy it is the `cowboy_req` module that\nsets the date and server headers. When using the command\ndirectly those headers will not be added.\n\n[[headers_command]]\n=== headers\n\nInitiate a response to the client.\n\n[source,erlang]\n----\n{headers, cowboy:http_status(), cowboy:http_headers()}\n----\n\nThis initiates a response to the client. The stream\nwill end when a data command with the `fin` flag or\na trailer command is returned.\n\nNote that in Cowboy it is the `cowboy_req` module that\nsets the date and server headers. When using the command\ndirectly those headers will not be added.\n\n[[data_command]]\n=== data\n\nSend data to the client.\n\n[source,erlang]\n----\n{data, fin(), cowboy_req:resp_body()}\n----\n\n[[trailers_command]]\n=== trailers\n\nSend response trailers to the client.\n\n[source,erlang]\n----\n{trailers, cowboy:http_headers()}\n----\n\n[[push_command]]\n=== push\n\nPush a resource to the client.\n\n[source,erlang]\n----\n{push, Method, Scheme, Host, inet:port_number(),\n    Path, Qs, cowboy:http_headers()}\n\nMethod = Scheme = Host = Path = Qs = binary()\n----\n\nThe command will be ignored if the protocol does not provide\nany server push mechanism.\n\n=== flow\n\n[source,erlang]\n----\n{flow, pos_integer()}\n----\n\nRequest more data to be read from the request body. The\nexact behavior depends on the protocol.\n\n=== spawn\n\nInform Cowboy that a process was spawned and should be\nsupervised.\n\n[source,erlang]\n----\n{spawn, pid(), timeout()}\n----\n\n=== error_response\n\nSend an error response if no response was sent previously.\n\n[source,erlang]\n----\n{error_response, cowboy:http_status(), cowboy:http_headers(), iodata()}\n----\n\n[[switch_protocol_command]]\n=== switch_protocol\n\nSwitch to a different protocol.\n\n[source,erlang]\n----\n{switch_protocol, cowboy:http_headers(), module(), state()}\n----\n\nContains the headers that will be sent in the 101 response,\nalong with the module implementing the protocol we are\nswitching to and its initial state.\n\nNote that the 101 informational response will not be sent\nafter a final response.\n\n=== stop\n\nStop the stream.\n\n[source,erlang]\n----\nstop\n----\n\nWhile no more data can be sent after the `fin` flag was set,\nthe stream is still tracked by Cowboy until it is stopped by\nthe handler.\n\nThe behavior when stopping a stream for which no response\nhas been sent will vary depending on the protocol. The stream\nwill end successfully as far as the client is concerned.\n\nTo indicate that an error occurred, either use `error_response`\nbefore stopping, or use `internal_error`.\n\nNo other command can be executed after the `stop` command.\n\n=== internal_error\n\nStop the stream with an error.\n\n[source,erlang]\n----\n{internal_error, Reason, HumanReadable}\n\nReason        = any()\nHumanReadable = atom()\n----\n\nThis command should be used when the stream cannot continue\nbecause of an internal error. An `error_response` command\nmay be sent before that to advertise to the client why the\nstream is dropped.\n\n=== log\n\nLog a message.\n\n[source,erlang]\n----\n{log, logger:level(), io:format(), list()}\n----\n\nThis command can be used to log a message using the\nconfigured `logger` module.\n\n=== set_options\n\nSet protocol options.\n\n[source,erlang]\n----\n{set_options, map()}\n----\n\nThis can also be used to override stream handler\noptions. For example this is supported by\nlink:man:cowboy_compress_h(3)[cowboy_compress_h(3)].\n\nNot all options can be overridden. Please consult the\nrelevant option's documentation for details.\n\n== Predefined events\n\nCowboy will forward all messages sent to the stream to\nthe `info/3` callback. To send a message to a stream,\nthe function link:man:cowboy_req:cast(3)[cowboy_req:cast(3)]\ncan be used.\n\nCowboy will also forward the exit signals for the\nprocesses that the stream spawned.\n\nWhen Cowboy needs to send a response it will trigger\nan event that looks exactly like the corresponding\ncommand. This event must be returned to be processed\nby Cowboy (which is done automatically when using\nlink:man:cowboy_stream_h(3)[cowboy_stream_h(3)]).\n\nCowboy may trigger the following events on its own,\nregardless of the stream handlers configured:\nxref:inform_command[inform] (to send a 101\ninformational response when upgrading to HTTP/2 or\nWebsocket), xref:response_command[response],\nxref:headers_command[headers], xref:data_command[data]\nand xref:switch_protocol_command[switch_protocol].\n\n== Exports\n\nThe following function should be called by modules implementing\nstream handlers to execute the next stream handler in the list:\n\n* link:man:cowboy_stream:init(3)[cowboy_stream:init(3)] - Initialize a stream\n* link:man:cowboy_stream:data(3)[cowboy_stream:data(3)] - Handle data for a stream\n* link:man:cowboy_stream:info(3)[cowboy_stream:info(3)] - Handle a message for a stream\n* link:man:cowboy_stream:terminate(3)[cowboy_stream:terminate(3)] - Terminate a stream\n* link:man:cowboy_stream:early_error(3)[cowboy_stream:early_error(3)] - Handle an early error for a stream\n\n== Types\n\n=== commands()\n\n[source,erlang]\n----\ncommands() :: [Command]\n----\n\nSee the xref:commands[list of commands] for details.\n\n=== fin()\n\n[source,erlang]\n----\nfin() :: fin | nofin\n----\n\nUsed in commands and events to indicate that this is\nthe end of a direction of a stream.\n\n=== partial_req()\n\n[source,erlang]\n----\nreq() :: #{\n    method  => binary(),               %% case sensitive\n    version => cowboy:http_version() | atom(),\n    scheme  => binary(),               %% lowercase; case insensitive\n    host    => binary(),               %% lowercase; case insensitive\n    port    => inet:port_number(),\n    path    => binary(),               %% case sensitive\n    qs      => binary(),               %% case sensitive\n    headers => cowboy:http_headers(),\n    peer    => {inet:ip_address(), inet:port_number()}\n}\n----\n\nPartial request information received when an early error is\ndetected.\n\n=== reason()\n\n[source,erlang]\n----\nreason() :: normal | switch_protocol\n    | {internal_error, timeout | {error | exit | throw, any()}, HumanReadable}\n    | {socket_error, closed | atom(), HumanReadable}\n    | {stream_error, Error, HumanReadable}\n    | {connection_error, Error, HumanReadable}\n    | {stop, cow_http2:frame() | {exit, any()}, HumanReadable}\n\nError         = atom()\nHumanReadable = atom()\n----\n\nReason for the stream termination.\n\n=== resp_command()\n\n[source,erlang]\n----\nresp_command() :: {response, cowboy:http_status(),\n    cowboy:http_headers(), cowboy_req:resp_body()}\n----\n\nSee the xref:response_command[response command] for details.\n\n=== streamid()\n\n[source,erlang]\n----\nstreamid() :: any()\n----\n\nThe identifier for this stream.\n\nThe identifier is unique over the connection process.\nIt is possible to form a unique identifier node-wide and\ncluster-wide by wrapping it in a `{self(), StreamID}`\ntuple.\n\n== Changelog\n\n* *2.7*: The `log` and `set_options` commands were introduced.\n* *2.6*: The `data` command can now contain a sendfile tuple.\n* *2.6*: The `{stop, {exit, any()}, HumanReadable}` terminate reason was added.\n* *2.2*: The `trailers` command was introduced.\n* *2.0*: Module introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_http(3)[cowboy_http(3)],\nlink:man:cowboy_http2(3)[cowboy_http2(3)],\nlink:man:cowboy_req:cast(3)[cowboy_req:cast(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_stream.data.asciidoc",
    "content": "= cowboy_stream:data(3)\n\n== Name\n\ncowboy_stream:data - Handle data for a stream\n\n== Description\n\n[source,erlang]\n----\ndata(StreamID, IsFin, Data, State) -> {Commands, State}\n\nStreamID :: cowboy_stream:stream_id()\nIsFin    :: cowboy_stream:fin()\nData     :: binary()\nCommands :: cowboy_stream:commands()\nState    - opaque\n----\n\nHandle data for a stream.\n\nThis function should be called by all stream handlers. It will\npropagate data to the next configured stream handler. Handlers\ndo not have to propagate data that has been fully handled.\n\n== Arguments\n\nStreamID::\n\nThe stream ID.\n\nIsFin::\n\nWhether this is the end of the request body.\n\nData::\n\nThe data received.\n\nCommands::\n\nThe commands to be executed.\n\nState::\n\nThe state for the next stream handler.\n\n== Return value\n\nA list of commands and an opaque state is returned.\n\nThe list of commands returned should be included in the\ncommands returned from the current stream handler. It\ncan be modified if necessary.\n\nThe state should be stored in the current stream\nhandler's state and passed to `cowboy_stream` when\nnecessary. The state should be treated as opaque.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Propagate data to the next stream handler\n[source,erlang]\n----\ndata(StreamID, IsFin, Data, State=#state{next=Next0}) ->\n    MyCommands = my_commands(),\n    {Commands, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),\n    {MyCommands ++ Commands, #state{next=Next}}.\n----\n\n== See also\n\nlink:man:cowboy_stream(3)[cowboy_stream(3)],\nlink:man:cowboy_stream:init(3)[cowboy_stream:init(3)],\nlink:man:cowboy_stream:info(3)[cowboy_stream:info(3)],\nlink:man:cowboy_stream:terminate(3)[cowboy_stream:terminate(3)],\nlink:man:cowboy_stream:early_error(3)[cowboy_stream:early_error(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_stream.early_error.asciidoc",
    "content": "= cowboy_stream:early_error(3)\n\n== Name\n\ncowboy_stream:early_error - Handle an early error for a stream\n\n== Description\n\n[source,erlang]\n----\nearly_error(StreamID, Reason, PartialReq, Resp, Opts) -> Resp\n\nStreamID   :: cowboy_stream:stream_id()\nReason     :: cowboy_stream:reason()\nPartialReq :: cowboy_stream:partial_req()\nResp       :: cowboy_stream:resp_command()\nOpts       :: cowboy:opts()\n----\n\nHandle an early error for a stream.\n\nThis function should be called by all stream handlers. It will\npropagate the early error to the next configured stream handler.\n\n== Arguments\n\nStreamID::\n\nThe stream ID.\n\nReason::\n\nReason for termination.\n\nPartialReq::\n\nThe request data that has been received so far.\n\nResp::\n\nThe response that will be sent as a result of the early error.\n+\nIt may be modified by the stream handler before or after\nbeing propagated to the next handler.\n\nOpts::\n\nThe protocol options.\n\n== Return value\n\nThe response to be sent as a result of the early error.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Propagate the early error to the next stream handler\n[source,erlang]\n----\nearly_error(StreamID, Reason, PartialReq, Resp, Opts) ->\n    cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).\n----\n\n== See also\n\nlink:man:cowboy_stream(3)[cowboy_stream(3)],\nlink:man:cowboy_stream:init(3)[cowboy_stream:init(3)],\nlink:man:cowboy_stream:data(3)[cowboy_stream:data(3)],\nlink:man:cowboy_stream:info(3)[cowboy_stream:info(3)],\nlink:man:cowboy_stream:terminate(3)[cowboy_stream:terminate(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_stream.info.asciidoc",
    "content": "= cowboy_stream:info(3)\n\n== Name\n\ncowboy_stream:info - Handle a message for a stream\n\n== Description\n\n[source,erlang]\n----\ninfo(StreamID, Info, State) -> {Commands, State}\n\nStreamID :: cowboy_stream:stream_id()\nInfo     :: any()\nCommands :: cowboy_stream:commands()\nState    - opaque\n----\n\nHandle a message for a stream.\n\nThis function should be called by all stream handlers. It will\npropagate the event to the next configured stream handler.\nHandlers do not have to propagate events that have been\nfully handled.\n\n== Arguments\n\nStreamID::\n\nThe stream ID.\n\nInfo::\n\nThe event received.\n\nCommands::\n\nThe commands to be executed.\n\nState::\n\nThe state for the next stream handler.\n\n== Return value\n\nA list of commands and an opaque state is returned.\n\nThe list of commands returned should be included in the\ncommands returned from the current stream handler. It\ncan be modified if necessary.\n\nThe state should be stored in the current stream\nhandler's state and passed to `cowboy_stream` when\nnecessary. The state should be treated as opaque.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Propagate an event to the next stream handler\n[source,erlang]\n----\ninfo(StreamID, Info, State=#state{next=Next0}) ->\n    MyCommands = my_commands(),\n    {Commands, Next} = cowboy_stream:info(StreamID, Info, Next0),\n    {MyCommands ++ Commands, #state{next=Next}}.\n----\n\n== See also\n\nlink:man:cowboy_stream(3)[cowboy_stream(3)],\nlink:man:cowboy_stream:init(3)[cowboy_stream:init(3)],\nlink:man:cowboy_stream:data(3)[cowboy_stream:data(3)],\nlink:man:cowboy_stream:terminate(3)[cowboy_stream:terminate(3)],\nlink:man:cowboy_stream:early_error(3)[cowboy_stream:early_error(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_stream.init.asciidoc",
    "content": "= cowboy_stream:init(3)\n\n== Name\n\ncowboy_stream:init - Initialize a stream\n\n== Description\n\n[source,erlang]\n----\ninit(StreamID, Req, Opts) -> {Commands, State}\n\nStreamID :: cowboy_stream:stream_id()\nReq      :: cowboy_req:req()\nOpts     :: cowboy:opts()\nCommands :: cowboy_stream:commands()\nState    - opaque\n----\n\nInitialize a stream.\n\nThis function must be called by all stream handlers. It will\ninitialize the next configured stream handler.\n\n== Arguments\n\nStreamID::\n\nThe stream ID.\n\nReq::\n\nThe Req object.\n\nOpts::\n\nThe protocol options.\n\nCommands::\n\nThe commands to be executed.\n\nState::\n\nThe state for the next stream handler.\n\n== Return value\n\nA list of commands and an opaque state is returned.\n\nThe list of commands returned should be included in the\ncommands returned from the current stream handler. It\ncan be modified if necessary.\n\nThe state should be stored in the current stream\nhandler's state and passed to `cowboy_stream` when\nnecessary. The state should be treated as opaque.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Initialize the next stream handler\n[source,erlang]\n----\ninit(StreamID, Req, Opts) ->\n    MyCommands = my_commands(),\n    {Commands, Next} = cowboy_stream:init(StreamID, Req, Opts),\n    {MyCommands ++ Commands, #state{next=Next}}.\n----\n\n== See also\n\nlink:man:cowboy_stream(3)[cowboy_stream(3)],\nlink:man:cowboy_stream:data(3)[cowboy_stream:data(3)],\nlink:man:cowboy_stream:info(3)[cowboy_stream:info(3)],\nlink:man:cowboy_stream:terminate(3)[cowboy_stream:terminate(3)],\nlink:man:cowboy_stream:early_error(3)[cowboy_stream:early_error(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_stream.terminate.asciidoc",
    "content": "= cowboy_stream:terminate(3)\n\n== Name\n\ncowboy_stream:terminate - Terminate a stream\n\n== Description\n\n[source,erlang]\n----\nterminate(StreamID, Reason, State) -> ok\n\nStreamID :: cowboy_stream:stream_id()\nReason   :: cowboy_stream:reason()\nState    - opaque\n----\n\nTerminate a stream.\n\nThis function must be called by all stream handlers. It will\nterminate the next configured stream handler.\n\n== Arguments\n\nStreamID::\n\nThe stream ID.\n\nReason::\n\nReason for termination.\n\nState::\n\nThe state for the next stream handler.\n\n== Return value\n\nThe atom `ok` is always returned. It can be safely ignored.\n\n== Changelog\n\n* *2.0*: Function introduced.\n\n== Examples\n\n.Terminate the next stream handler\n[source,erlang]\n----\nterminate(StreamID, Reason, State=#state{next=Next0}) ->\n    my_termination(State),\n    cowboy_stream:terminate(StreamID, Reason, Next0).\n----\n\n== See also\n\nlink:man:cowboy_stream(3)[cowboy_stream(3)],\nlink:man:cowboy_stream:init(3)[cowboy_stream:init(3)],\nlink:man:cowboy_stream:data(3)[cowboy_stream:data(3)],\nlink:man:cowboy_stream:info(3)[cowboy_stream:info(3)],\nlink:man:cowboy_stream:early_error(3)[cowboy_stream:early_error(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_stream_h.asciidoc",
    "content": "= cowboy_stream_h(3)\n\n== Name\n\ncowboy_stream_h - Default stream handler\n\n== Description\n\nThe module `cowboy_stream_h` is Cowboy's default stream\nhandler and defines much of its behavior. It is responsible\nfor managing the request process, sending it the request\nbody and translating its messages into commands that\nCowboy understands.\n\n== Options\n\n[source,erlang]\n----\nopts() :: #{\n    env              => cowboy_middleware:env(),\n    middlewares      => [module()],\n    shutdown_timeout => timeout()\n}\n----\n\nConfiguration for the default stream handler.\n\nThe default value is given next to the option name:\n\nenv (#{})::\n\nMiddleware environment.\n\nmiddlewares ([cowboy_router, cowboy_handler])::\n\nMiddlewares to run for every request.\n\nshutdown_timeout (5000)::\n\nTime in ms Cowboy will wait for child processes to shut down before killing them.\n\n== Events\n\nThe default stream handler spawns the request process\nand receives its exit signal when it terminates. It\nwill stop the stream once its receives it.\n\nBecause this stream handler converts events from the\nrequest process into commands, other stream handlers\nmay not work properly if they are executed after the\ndefault stream handler. Always be mindful of in which\norder stream handlers will get executed.\n\n=== Request body\n\nThe default stream handler implements the `read_body`\nmechanism. In addition to reading the body, the handler\nwill automatically handle the `expect: 100-continue`\nheader and send a 100 Continue response.\n\nNormally one would use\nlink:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)]\nto read the request body. The default stream handler\nwill buffer data until the amount gets larger than the\nrequested length before sending it. Alternatively, it\nwill send whatever data it has when the period timeout\ntriggers. Depending on the protocol, the flow control\nwindow is updated to allow receiving data for the\nrequested length.\n\nThe default stream handler also comes with an automatic\nmode for reading the request body. This can be used by\nsending the event message `{read_body, Pid, Ref, auto, infinity}`\nusing link:man:cowboy_req:cast(3)[cowboy_req:cast(3)].\nThe default stream handler will then send data as soon\nas some becomes available using one of these two\nmessages depending on whether body reading was completed:\n\n* `{request_body, Ref, nofin, Data}`\n* `{request_body, Ref, fin, BodyLen, Data}`\n\nDepending on the protocol, Cowboy will update the flow\ncontrol window using the size of the data that was read.\n\nAuto mode automatically gets disabled after data has\nbeen sent to the handler. Therefore in order to continue\nreading data a `read_body` event message must be sent\nafter each `request_body` message.\n\n=== Response\n\nIn addition it returns a command for any event message\nlooking like one of the following commands: `inform`,\n`response`, `headers`, `data`, `trailers`, `push`,\n`switch_protocol`. This is what allows the request\nprocess to send a response.\n\n== Changelog\n\n* *2.11*: Introduce body reading using auto mode.\n* *2.0*: Module introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_stream(3)[cowboy_stream(3)],\nlink:man:cowboy_compress_h(3)[cowboy_compress_h(3)],\nlink:man:cowboy_decompress_h(3)[cowboy_decompress_h(3)],\nlink:man:cowboy_metrics_h(3)[cowboy_metrics_h(3)],\nlink:man:cowboy_tracer_h(3)[cowboy_tracer_h(3)],\nlink:man:cowboy_req:cast(3)[cowboy_req:cast(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_tracer_h.asciidoc",
    "content": "= cowboy_tracer_h(3)\n\n== Name\n\ncowboy_tracer_h - Tracer stream handler\n\n== Description\n\nThe module `cowboy_tracer_h` can be used to conditionally\ntrace streams based on information found in the request.\nTrace messages are given to the configured callback.\n\n== Options\n\n[source,erlang]\n----\nopts() :: #{\n    tracer_callback    => Callback,\n    tracer_flags       => [atom()],\n    tracer_match_specs => [MatchSpec]\n}\n\nCallback :: fun((init | terminate | tuple(), State) -> State)\n\nMatchSpec :: MatchPredicate\n           | {method, binary()}\n           | {host, binary()}\n           | {path, binary()}\n           | {path_start, binary()}\n           | {header, binary()}\n           | {header, binary(), binary()}\n           | {peer_ip, inet:ip_address()}\n\nMatchPredicate :: fun((cowboy_stream:streamid(),\n                       cowboy_req:req(),\n                       cowboy:opts()) -> boolean())\n}\n----\n\nConfiguration for the tracer stream handler.\n\nThis module will not set trace patterns. Those must be\nset by the user directly, either from the callback's\n`init` or, preferably, in advance.\n\ntracer_callback::\n\nThe function that will be called for each trace\nevents. It will also be called before any trace\nevent with an argument `init`, and when the\nstream is terminated with an argument `terminate`.\n+\nThis option is required for tracing to be enabled.\nThe tracer stream handler does nothing otherwise.\n\ntracer_flags::\n\nTrace flags to enable. See the documentation\nof `erlang:trace/3` for details. Note that all\ntrace flags are allowed except for the `tracer`\nflag.\n\ntracer_match_specs::\n\nA list of match conditions that must all be\nfulfilled for the stream to be traced. Cowboy\nwill compare these with the information found\nin the request and only enable tracing if all\nmatches succeed.\n+\nThis option is required for tracing to be enabled.\nThe tracer stream handler does nothing otherwise.\n\n== Events\n\nThe tracer stream handler does not produce any event.\n\n== Changelog\n\n* *2.7*: Module introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_stream(3)[cowboy_stream(3)],\nlink:man:cowboy_compress_h(3)[cowboy_compress_h(3)],\nlink:man:cowboy_decompress_h(3)[cowboy_decompress_h(3)],\nlink:man:cowboy_metrics_h(3)[cowboy_metrics_h(3)],\nlink:man:cowboy_stream_h(3)[cowboy_stream_h(3)]\n"
  },
  {
    "path": "doc/src/manual/cowboy_websocket.asciidoc",
    "content": "= cowboy_websocket(3)\n\n== Name\n\ncowboy_websocket - Websocket\n\n== Description\n\nThe module `cowboy_websocket` implements Websocket\nas a Ranch protocol. It also defines a callback interface\nfor handling Websocket connections.\n\n== Callbacks\n\nWebsocket handlers must implement the following callback\ninterface:\n\n[source,erlang]\n----\ninit(Req, State)\n    -> {cowboy_websocket, Req, State}\n     | {cowboy_websocket, Req, State, Opts}\n\nwebsocket_init(State)            -> CallResult  %% optional\nwebsocket_handle(InFrame, State) -> CallResult\nwebsocket_info(Info, State)      -> CallResult\n\nterminate(Reason, PartialReq, State) -> ok      %% optional\n\nReq        :: cowboy_req:req()\nPartialReq :: map()\nState      :: any()\nOpts       :: cowboy_websocket:opts()\nInFrame    :: ping | pong | {text | binary | ping | pong, binary()}\nInfo       :: any()\n\nCallResult :: {commands(), State}\n            | {commands(), State, hibernate}\n            | Deprecated\n\nDeprecated :: {ok, State}\n            | {ok, State, hibernate}\n            | {reply, OutFrame | [OutFrame], State}\n            | {reply, OutFrame | [OutFrame], State, hibernate}\n            | {stop, State}\nOutFrame   :: cow_ws:frame()                    %% see types below\n\nReason     :: normal | stop | timeout\n            | remote | {remote, cow_ws:close_code(), binary()}\n            | {error, badencoding | badframe | closed | atom()}\n            | {crash, error | exit | throw, any()}\n----\n\nThe `init/2` callback is common to all handlers. To upgrade\nthe connection to Websocket, it must return `cowboy_websocket`\nas the first element of the tuple.\n\nAny operation requiring the HTTP request must be done in the\n`init/2` function, as the Req object will not be available\nafter it returns. Websocket sub-protocol selection should\ntherefore be done in this function.\n\nThe optional `websocket_init/1` callback will be called once\nthe connection has been upgraded to Websocket. It can be used\nto perform any required initialization of the handler.\n\nNote that the `init/2` function does not run in the same\nprocess as the Websocket callbacks. Any Websocket-specific\ninitialization must be done in `websocket_init/1`.\n\nThe `websocket_handle/2` callback will be called for every\nframe received. The `websocket_info/2` callback will be\ncalled for every Erlang message received.\n\nAll three Websocket callbacks may send one or more frames\nback to the client, including close frames to terminate\nthe connection; enable/disable active mode; enable/disable\ncompression for subsequent frames; or change Websocket options.\n\nThe optional `terminate/3` callback will ultimately be called\nwith the reason for the termination of the connection. This\ncallback is common to all handlers. Note that Websocket will\nnot provide the full Req object by default, to save memory.\n\nCowboy will terminate the process right after closing the\nWebsocket connection. This means that there is no need to\nperform any cleanup in the `terminate/3` callback.\n\nThe following terminate reasons are defined for Websocket\nconnections:\n\nnormal::\n    The connection was closed normally before establishing a Websocket\n    connection. This typically happens if an `ok` tuple is returned\n    from the `init/2` callback.\n\nremote::\n    The remote endpoint closed the connection without giving any\n    further details.\n\n{remote, Code, Payload}::\n    The remote endpoint closed the connection with the given\n    `Code` and `Payload` as the reason.\n\nstop::\n    The handler requested to close the connection, either by returning\n    a `stop` tuple or by sending a `close` frame.\n\ntimeout::\n    The connection has been closed due to inactivity. The timeout\n    value can be configured from `init/2`.\n\n{crash, Class, Reason}::\n    A crash occurred in the handler. `Class` and `Reason` can be\n    used to obtain more information about the crash.\n\n{error, badencoding}::\n    A text frame was sent by the client with invalid encoding. All\n    text frames must be valid UTF-8.\n\n{error, badframe}::\n    A protocol error has been detected.\n\n{error, closed}::\n    The socket has been closed brutally without a close frame being\n    received first.\n\n{error, Reason}::\n    A socket error occurred.\n\n== Types\n\n=== commands()\n\n[source,erlang]\n----\ncommands() :: [Command]\n\nCommand :: {active, boolean()}\n         | {deflate, boolean()}\n         | {set_options, #{\n             idle_timeout => timeout(),\n             max_frame_size => non_neg_integer() | infinity}}\n         | {shutdown_reason, any()}\n         | Frame :: cow_ws:frame()\n----\n\nCommands that may be returned from Websocket callbacks.\n\nThe following commands are defined:\n\nactive::\n\nWhether to disable or enable reading from the socket. This\ncan be used to apply flow control to a Websocket connection.\n\ndeflate::\n\nWhether the subsequent frames should be compressed. Has no\neffect on connections that did not negotiate compression.\n\nset_options::\n\nSet Websocket options. Currently only the options `idle_timeout`\nand `max_frame_size` may be updated from a Websocket handler.\n\nshutdown_reason::\n\nChange the shutdown reason. The Websocket process will exit\nwith reason `normal` by default. This command can be used to\nexit with reason `{shutdown, ShutdownReason}` under normal\nconditions. This command has no effect when the Websocket\nprocess exits abnormally, for example following a crash in a\nhandler callback.\n\nFrame::\n\nSend the corresponding Websocket frame.\n\n=== cow_ws:frame()\n\n[source,erlang]\n----\nframe() :: {text, iodata()}\n    | {binary, iodata()}\n    | ping | {ping, iodata()}\n    | pong | {pong, iodata()}\n    | close | {close, iodata()} | {close, close_code(), iodata()}\n\nclose_code() :: 1000..1003 | 1006..1011 | 3000..4999\n----\n\nWebsocket frames that can be sent as a response.\n\nNote that there is no need to send pong frames back as\nCowboy does it automatically for you.\n\n=== opts()\n\n[source,erlang]\n----\nopts() :: #{\n    active_n           => pos_integer(),\n    compress           => boolean(),\n    data_delivery      => stream_handlers | relay,\n    data_delivery_flow => pos_integer(),\n    deflate_opts       => cow_ws:deflate_opts(),\n    dynamic_buffer     => false | {pos_integer(), pos_integer()},\n    idle_timeout       => timeout(),\n    max_frame_size     => non_neg_integer() | infinity,\n    req_filter         => fun((cowboy_req:req()) -> map()),\n    validate_utf8      => boolean()\n}\n----\n\nWebsocket handler options.\n\nThis configuration is passed to Cowboy from the `init/2`\nfunction:\n\n[source,erlang]\n----\ninit(Req, State) ->\n    Opts = #{compress => true},\n    {cowboy_websocket, Req, State, Opts}.\n----\n\nThe default value is given next to the option name:\n\nactive_n (1)::\n\nThe number of packets Cowboy will request from the socket at once.\nThis can be used to tweak the performance of the server. Higher\nvalues reduce the number of times Cowboy need to request more\npackets from the port driver at the expense of potentially\nhigher memory being used.\n+\nThis option does not apply to Websocket over HTTP/2.\n\ncompress (false)::\n\nWhether to enable the Websocket frame compression\nextension. Frames will only be compressed for the\nclients that support this extension.\n\ndata_delivery (stream_handlers)::\n\nHTTP/2+ only. Determines how data will be delivered\nto the Websocket session process. `stream_handlers`\nis the default and makes data go through stream\nhandlers. `relay` is a faster method introduced in\nCowboy 2.14 and sends data directly. `relay` is\nintended to become the default in Cowboy 3.0.\n\ndata_delivery_flow (pos_integer())::\n\nWhen the `relay` data delivery method is used,\nthis value may be used to decide how much the\nflow control window should be for the Websocket\nstream. Currently only applies to HTTP/2.\n\ndeflate_opts (#{})::\n\nConfiguration for the permessage-deflate Websocket\nextension. Allows configuring both the negotiated\noptions and the zlib compression options. The\ndefaults optimize the compression at the expense\nof some memory and CPU.\n\ndynamic_buffer ({1024, 131072})::\n\nCowboy will dynamically change the socket's `buffer` size\ndepending on the size of the data it receives from the socket.\nThis lets Cowboy use the optimal buffer size for the current\nworkload.\n+\nThe dynamic buffer size functionality can be disabled by\nsetting this option to `false`. Cowboy will also disable\nit by default when the `buffer` transport option is configured.\n\nidle_timeout (60000)::\n\nTime in milliseconds that Cowboy will keep the\nconnection open without receiving anything from\nthe client.\n+\nThis option can be updated at any time using the\n`set_options` command.\n\nmax_frame_size (infinity)::\n\nMaximum frame size in bytes allowed by this Websocket\nhandler. Cowboy will close the connection when\na client attempts to send a frame that goes over\nthis limit. For fragmented frames this applies\nto the size of the reconstituted frame.\n\nreq_filter::\n\nA function applied to the Req to compact it and\nonly keep required information. The Req is only\ngiven back in the `terminate/3` callback. By default\nit keeps the method, version, URI components and peer\ninformation.\n\nvalidate_utf8 (true)::\n\nWhether Cowboy should verify that the payload of\n`text` and `close` frames is valid UTF-8. This is\nrequired by the protocol specification but in some\ncases it may be more interesting to disable it in\norder to save resources.\n+\nNote that `binary` frames do not have this UTF-8\nrequirement and are what should be used under\nnormal circumstances if necessary.\n\n== Changelog\n\n* *2.14*: The `data_delivery` and `data_delivery_flow` options\n          were added. The `relay` data delivery mechanism\n          provides a better way of forwarding data to\n          HTTP/2+ Websocket session processes.\n* *2.13*: The `active_n` default value was changed to `1`.\n* *2.13*: The `dynamic_buffer` option was added.\n* *2.13*: The `max_frame_size` option can now be set dynamically.\n* *2.11*: Websocket over HTTP/2 is now considered stable.\n* *2.11*: HTTP/1.1 Websocket no longer traps exits by default.\n* *2.8*: The `active_n` option was added.\n* *2.7*: The commands based interface has been documented.\n         The old interface is now deprecated.\n* *2.7*: The command `shutdown_reason` was introduced.\n* *2.7*: The option `validate_utf8` has been added.\n* *2.6*: Deflate options can now be configured via `deflate_opts`.\n* *2.0*: The Req object is no longer passed to Websocket callbacks.\n* *2.0*: The callback `websocket_terminate/3` was removed in favor of `terminate/3`.\n* *1.0*: Protocol introduced.\n\n== See also\n\nlink:man:cowboy(7)[cowboy(7)],\nlink:man:cowboy_handler(3)[cowboy_handler(3)],\nlink:man:cowboy_http(3)[cowboy_http(3)],\nlink:man:cowboy_http2(3)[cowboy_http2(3)]\n"
  },
  {
    "path": "doc/src/manual/http_status_codes.asciidoc",
    "content": "= HTTP status codes(7)\n\n== Name\n\nHTTP status codes - status codes used by Cowboy\n\n== Description\n\nThis chapter aims to list all HTTP status codes that Cowboy\nmay return, with details on the reasons why. The list given\nhere only includes the replies that Cowboy sends, not user\nreplies.\n\n== 100 Continue\n\nWhen the client sends an `expect: 100-continue` header,\nCowboy automatically sends a this status code before\ntrying to read the request body. This behavior can be\ndisabled using the appropriate body option.\n\n== 101 Switching Protocols\n\nThis is the status code sent when switching to the\nWebsocket protocol.\n\n== 200 OK\n\nThis status code is sent by `cowboy_rest`.\n\n== 201 Created\n\nThis status code is sent by `cowboy_rest`.\n\n== 202 Accepted\n\nThis status code is sent by `cowboy_rest`.\n\n== 204 No Content\n\nThis status code is sent when the processing of a request\nends without any reply having been sent. It may also be\nsent by `cowboy_rest` under normal conditions.\n\n== 300 Multiple Choices\n\nThis status code is sent by `cowboy_rest`.\n\n== 301 Moved Permanently\n\nThis status code is sent by `cowboy_rest`.\n\n== 303 See Other\n\nThis status code is sent by `cowboy_rest`.\n\n== 304 Not Modified\n\nThis status code is sent by `cowboy_rest`.\n\n== 307 Temporary Redirect\n\nThis status code is sent by `cowboy_rest`.\n\n== 400 Bad Request\n\nCowboy will send this status code for any of the\nfollowing reasons:\n\n* Too many empty lines were sent before the request.\n* The request-line could not be parsed.\n* Too many headers were sent.\n* A header name was too long.\n* A header value was too long.\n* The host header was missing from an HTTP/1.1 request.\n* The host header could not be parsed.\n* The requested host was not found.\n* The requested path could not be parsed.\n* The accept header could not be parsed when using REST.\n* REST under normal conditions.\n* A Websocket upgrade failed.\n\n== 401 Unauthorized\n\nThis status code is sent by `cowboy_rest`.\n\n== 403 Forbidden\n\nThis status code is sent by `cowboy_rest`.\n\n== 404 Not Found\n\nThis status code is sent when the router successfully\nresolved the host but didn't find a matching path for\nthe request. It may also be sent by `cowboy_rest` under\nnormal conditions.\n\n== 405 Method Not Allowed\n\nThis status code is sent by `cowboy_rest`.\n\n== 406 Not Acceptable\n\nThis status code is sent by `cowboy_rest`.\n\n== 408 Request Timeout\n\nCowboy will send this status code to the client if the\nclient started to send a request, indicated by the\nrequest-line being received fully, but failed to send\nall headers in a reasonable time.\n\n== 409 Conflict\n\nThis status code is sent by `cowboy_rest`.\n\n== 410 Gone\n\nThis status code is sent by `cowboy_rest`.\n\n== 412 Precondition Failed\n\nThis status code is sent by `cowboy_rest`.\n\n== 413 Request Entity Too Large\n\nThis status code is sent by `cowboy_rest`.\n\n== 414 Request-URI Too Long\n\nCowboy will send this status code to the client if the\nrequest-line is too long. It may also be sent by\n`cowboy_rest` under normal conditions.\n\n== 415 Unsupported Media Type\n\nThis status code is sent by `cowboy_rest`.\n\n== 500 Internal Server Error\n\nThis status code is sent when a crash occurs in HTTP, loop\nor REST handlers, or when an invalid return value is\nreturned. It may also be sent by `cowboy_rest` under\nnormal conditions.\n\n== 501 Not Implemented\n\nThis status code is sent by `cowboy_rest`.\n\n== 503 Service Unavailable\n\nThis status code is sent by `cowboy_rest`.\n\n== 505 HTTP Version Not Supported\n\nCowboy only supports the versions 1.0 and 1.1 of HTTP.\nIn all other cases this status code is sent back to the\nclient and the connection is closed.\n"
  },
  {
    "path": "doc/src/specs/index.ezdoc",
    "content": "::: Cowboy Implementation Reference\n\nThe implementation reference documents the behavior of Cowboy\nwith regards to various standards and specifications.\n\n* ^\"RFC6585 status codes^rfc6585\n* ^\"RFC7230 HTTP/1.1 server^rfc7230_server\n"
  },
  {
    "path": "doc/src/specs/rfc6585.ezdoc",
    "content": "::: RFC6585\n\nThis document lists status codes that Cowboy implements as\ndefined in the RFC6585 specifications.\n\n:: Status codes\n\n: 428 Precondition Required (RFC6585 3)\n\nThe server requires the request to this resource to be conditional.\n\nThe response should explain how to resubmit the request successfully.\n\n: 429 Too Many Requests (RFC6585 4, RFC6585 7.2)\n\nThe user has sent too many requests in a given amount of time.\n\nThe response should detail the rates allowed.\n\nThe retry-after header can be used to indicate how long the\nuser has to wait before making a new request.\n\nWhen an attack is detected it is recommended to drop the\nconnection directly instead of sending this response.\n\n: 431 Request Header Fields Too Large (RFC6585 5, RFC6585 7.3)\n\nThe request's header fields are too large.\n\nWhen rejecting a single header, the response should detail\nwhich header was at fault.\n\nWhen an attack is detected it is recommended to drop the\nconnection directly instead of sending this response.\n\n: 511 Network Authentication Required (RFC6585 6)\n\nThe user needs to authenticate into the network to gain access.\n\nThis status code is meant to be used by proxies only, not by\norigin servers.\n\nThe response should contain a link to the resource allowing\nthe user to log in.\n"
  },
  {
    "path": "doc/src/specs/rfc7230_server.ezdoc",
    "content": "::: RFC7230 HTTP/1.1 server\n\nThis document lists the rules the Cowboy server follows based\non the RFC7230 HTTP specifications.\n\n:: Listener\n\nThe default port for \"http\" connections is 80. The connection\nuses plain TCP. (RFC7230 2.7.1)\n\nThe default port for \"https\" connections is 443. The connection\nuses TLS. (RFC7230 2.7.2)\n\nAny other port may be used for either of them.\n\n:: Before the request\n\nA configurable number of empty lines (CRLF) preceding the request\nmust be ignored. At least 1 empty line must be ignored. (RFC7230 3.5)\n\nWhen receiving a response instead of a request, identified by the\nstatus-line which starts with the HTTP version, the server must\nreject the message with a 501 status code and close the connection. (RFC7230 3.1)\n\n:: Request\n\nIt is only necessary to parse elements required to process the\nrequest. (RFC7230 2.5)\n\nParsed elements are subject to configurable limits. A server must\nbe able to parse elements at least as long as it generates. (RFC7230 2.5)\n\nThe request must be parsed as a sequence of octets in an encoding\nthat is a superset of US-ASCII. (RFC7230 2.5)\n\n```\nHTTP-request = request-line *( header-field CRLF ) CRLF [ message-body ]\n```\n\nThe general format of HTTP requests is strict. No empty line is\nallowed in-between components except for the empty line\nindicating the end of the list of headers.\n\nIt is not necessary to read the message-body before processing\nthe request as the message-body may be dropped depending on the\noutcome of the processing.\n\nThe time the request (request line and headers) takes to be\nreceived by the server must be limited and subject to configuration.\nA server must wait at least 5 seconds before dropping the connection.\nA 408 status code must be sent if the request line was received\nfully when the timeout is triggered.\n\nAn HTTP/1.1 server must understand any valid HTTP/1.0 request,\nand respond to those with an HTTP/1.1 message that only use\nfeatures understood or safely ignored by HTTP/1.0 clients. (RFC7230 A)\n\n:: Request line\n\nIt is recommended to limit the request-line length to a configurable\nlimit of at least 8000 octets. However, as the possible line length is\nhighly dependent on what form the request-target takes, it is preferrable\nto limit each individual components of the request-target. (RFC7230 3.1.1)\n\nA request line too long must be rejected with a 414 status code\nand the closing of the connection. (RFC7230 3.1.1)\n\n```\nmethod SP request-target SP version CRLF\n```\n\n:: Method\n\n```\nmethod = token ; case sensitive\ntoken = 1*tchar\ntchar = \"!\" / \"#\" / \"$\" / \"%\" / \"&\" / \"'\" / \"*\" / \"+\" / \"-\" / \".\" / \"^\" / \"_\" / \"`\" / \"|\" / \"~\" / DIGIT / ALPHA\n```\n\nThe request method is defined as 1+ token characters. An invalid\nor empty method must be rejected with a 400 status code and the\nclosing of the connection. (RFC7230 3.1.1, RFC7230 3.2.6)\n\nIn practice the only characters in use by registered methods are\nuppercase letters [A-Z] and the dash \"-\". (IANA HTTP Method Registry)\n\nThe length of the method must be subject to a configurable limit.\nA method too long must be rejected with a 501 status code and the\nclosing of the connection. (RFC7230 3.1.1)\n\nA good default for the method length limit is the longest method\nlength the server implements. (RFC7230 3.1.1)\n\n:: Between method and request-target\n\nA request that uses anything other than SP as separator between\nthe method and the request-target must be rejected with a 400\nstatus code and the closing of the connection. (RFC7230 3.1.1, RFC7230 3.5)\n\n:: Request target\n\nThere are four request-target forms. A server must be able to\nhandle at least origin-form and absolute-form. The other two\nforms are specific to the CONNECT and site-wide OPTIONS method,\nrespectively. (RFC7230 5.3.2)\n\nThe fragment part of the target URI is not sent. It must be\nignored by a server receiving it. (RFC7230 5.1)\n\n```\nrequest-target = origin-form / absolute-form / authority-form / asterisk-form\n```\n\nAny other form is invalid and must be rejected with a 400\nstatus code and the closing of the connection.\n\n: origin-form\n\norigin-form is used when the client does not connect to a proxy,\ndoes not use the CONNECT method and does not issue a site-wide\nOPTIONS request. (RFC7230 5.3.1)\n\n```\norigin-form = absolute-path [ \"?\" query ]\nabsolute-path = 1*( \"/\" segment )\nsegment = *pchar\nquery = *( pchar / \"/\" / \"?\" )\n\npchar = unreserved / pct-encoded / sub-delims / \":\" / \"@\"\nunreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\npct-encoded = \"%\" HEXDIG HEXDIG\nsub-delims = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n```\n\nThe scheme is either resolved from configuration or is \"https\"\nwhen on a TLS connection and \"http\" otherwise. (RFC7230 5.5)\n\nThe authority is sent in the host header. (RFC7230 5.3.1, RFC7230 5.5)\n\nThe absolute-path always starts with \"/\" and ends with either \"?\", \"#\"\nor the end of the URI. (RFC3986 3.3)\n\nThe query starts with \"?\" and ends with \"#\" or the end of the URI. (RFC3986 3.4)\n\nThe path and query must be subject to a configurable limit.\nThis limit must be at least as high as what the server generates.\nA good default would be 8000 characters. (RFC7230 2.5, RFC7230 3.1.1)\n\nA request with a too long origin-form must be rejected with\na 414 status code and the closing of the connection. (RFC7230 3.1.1)\n\n: absolute-form\n\nabsolute-form is used when the client connects to a proxy, though\nits usage is also allowed when connecting to the server directly. (RFC7230 5.3.2)\n\nIn practice the scheme will be \"http\" or \"https\".\n\nThe \"http\" and \"https\" schemes based URI take the following form. (RFC7230 2.7.1, RFC7230 2.7.2)\n\n```\nhttp-URI = \"http:\" \"//\" authority path-abempty [ \"?\" query ] [ \"#\" fragment ]\nhttps-URI = \"https:\" \"//\" authority path-abempty [ \"?\" query ] [ \"#\" fragment ]\n```\n\nThe target URI excludes the fragment component. (RFC7230 5.1)\n\nThis means that the absolute-form uses a subset of absolute-URI.\n\n```\nabsolute-form = ( \"http\" / \"https\" ) \"://\" authority path-abempty [ \"?\" query ]\nauthority = host [ \":\" port ]\npath-abempty = *( \"/\" segment )\nquery = *( pchar / \"/\" / \"?\" )\n\nhost = IP-literal / IPv4address / reg-name\nport = *DIGIT\n\nIP-literal = \"[\" ( IPv6address / IPvFuture  ) \"]\"\n\nIPv6address = 6( h16 \":\" ) ls32\n\t/ \"::\" 5( h16 \":\" ) ls32\n\t/ [ h16 ] \"::\" 4( h16 \":\" ) ls32\n\t/ [ *1( h16 \":\" ) h16 ] \"::\" 3( h16 \":\" ) ls32\n\t/ [ *2( h16 \":\" ) h16 ] \"::\" 2( h16 \":\" ) ls32\n\t/ [ *3( h16 \":\" ) h16 ] \"::\" h16 \":\" ls32\n\t/ [ *4( h16 \":\" ) h16 ] \"::\" ls32\n\t/ [ *5( h16 \":\" ) h16 ] \"::\" h16\n\t/ [ *6( h16 \":\" ) h16 ] \"::\"\n\nls32 = ( h16 \":\" h16 ) / IPv4address ; least-significant 32 bits of address\nh16 = 1*4HEXDIG ; 16 bits of address represented in hexadecimal\n\nIPv4address = dec-octet \".\" dec-octet \".\" dec-octet \".\" dec-octet\n\ndec-octet = DIGIT / %x31-39 DIGIT / \"1\" 2DIGIT / \"2\" %x30-34 DIGIT / \"25\" %x30-35\n\nIPvFuture = \"v\" 1*HEXDIG \".\" 1*( unreserved / sub-delims / \":\" )\n\nreg-name = *( unreserved / pct-encoded / sub-delims )\n\nsegment = *pchar\nsegment-nz = 1*pchar\n\npchar = unreserved / pct-encoded / sub-delims / \":\" / \"@\"\nunreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\npct-encoded = \"%\" HEXDIG HEXDIG\nsub-delims = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n```\n\nThe scheme and host are case insensitive and normally provided in lowercase.\nAll other components are case sensitive. (RFC7230 2.7.3)\n\nUnknown schemes must be rejected with a 400 status code and the\nclosing of the connection. Because only a fixed number of schemes\nare allowed, it is not necessary to limit its length.\n\nThe scheme provided with the request must be dropped. The effective\nscheme is either resolved from configuration or is \"https\" when on\na TLS connection and \"http\" otherwise. (RFC7230 5.5)\n\nAn authority component with a userinfo component (and its\n\"@\" delimiter) is invalid. The request must be rejected with\na 400 status code and the closing of the connection. (RFC7230 2.7.1)\n\nA URI with a missing host identifier is invalid. The request must\nbe rejected with a 400 status code and the closing of the connection. (RFC7230 2.7.1)\n\nThe maximum length for an IPv4address is 15 characters. No\nconfigurable limit is necessary.\n\nThe maximum length for an IPv6address is 47 characters. No\nconfigurable limit is necessary.\n\nThe maximum length for the reg-name component must be subject\nto a configurable limit. A good default is 255 characters. (RFC3986 3.2.2, RFC1034 3.1)\n\nIt is not possible to distinguish between an IPv4address and\na reg-name before reaching the end of the string, therefore\nthe length limit for IPv4address must be ignored until that\npoint.\n\nThe maximum length for the port component is 5. No configurable\nlimit is necessary.\n\nThe authority is sent both in the URI and in the host header.\nThe authority from the URI must be dropped, and the host header\nmust be used instead. (RFC7230 5.5)\n\nThe path always starts with \"/\" and ends with either \"?\", \"#\"\nor the end of the URI. (RFC3986 3.3)\n\nAn empty path component is equivalent to \"/\". (RFC7230 2.7.3)\n\nThe query starts with \"?\" and ends with \"#\" or the end of the URI. (RFC3986 3.4)\n\nThe path and query must be subject to a configurable limit.\nThis limit must be at least as high as what the server generates.\nA good default would be 8000 characters. (RFC7230 2.5, RFC7230 3.1.1)\n\nA request with a too long component of absolute-form must be rejected with\na 414 status code and the closing of the connection. (RFC7230 3.1.1)\n\n: authority-form\n\nWhen the method is CONNECT, authority-form must be used. This\nform does not apply to any other methods which must reject the\nrequest with a 400 status code and the closing of the connection. (RFC7230 5.3.3)\n\n```\nauthority-form = authority\nauthority = host [ \":\" port ]\nhost = IP-literal / IPv4address / reg-name\nport = *DIGIT\n\nIP-literal = \"[\" ( IPv6address / IPvFuture  ) \"]\"\n\nIPv6address = 6( h16 \":\" ) ls32\n\t/ \"::\" 5( h16 \":\" ) ls32\n\t/ [ h16 ] \"::\" 4( h16 \":\" ) ls32\n\t/ [ *1( h16 \":\" ) h16 ] \"::\" 3( h16 \":\" ) ls32\n\t/ [ *2( h16 \":\" ) h16 ] \"::\" 2( h16 \":\" ) ls32\n\t/ [ *3( h16 \":\" ) h16 ] \"::\" h16 \":\" ls32\n\t/ [ *4( h16 \":\" ) h16 ] \"::\" ls32\n\t/ [ *5( h16 \":\" ) h16 ] \"::\" h16\n\t/ [ *6( h16 \":\" ) h16 ] \"::\"\n\nls32 = ( h16 \":\" h16 ) / IPv4address ; least-significant 32 bits of address\nh16 = 1*4HEXDIG ; 16 bits of address represented in hexadecimal\n\nIPv4address = dec-octet \".\" dec-octet \".\" dec-octet \".\" dec-octet\n\ndec-octet = DIGIT / %x31-39 DIGIT / \"1\" 2DIGIT / \"2\" %x30-34 DIGIT / \"25\" %x30-35\n\nIPvFuture = \"v\" 1*HEXDIG \".\" 1*( unreserved / sub-delims / \":\" )\n\nreg-name = *( unreserved / pct-encoded / sub-delims )\n\nunreserved = ALPHA / DIGIT / \"-\" / \".\" / \"_\" / \"~\"\npct-encoded = \"%\" HEXDIG HEXDIG\nsub-delims = \"!\" / \"$\" / \"&\" / \"'\" / \"(\" / \")\" / \"*\" / \"+\" / \",\" / \";\" / \"=\"\n```\n\nAn authority component with a userinfo component (and its\n\"@\" delimiter) is invalid. The request must be rejected with\na 400 status code and the closing of the connection. (RFC7230 2.7.1)\n\nThe maximum length for an IPv4address is 15 characters. No\nconfigurable limit is necessary.\n\nThe maximum length for an IPv6address is 47 characters. No\nconfigurable limit is necessary.\n\nThe maximum length for the reg-name component must be subject\nto a configurable limit. A good default is 255 characters. (RFC3986 3.2.2, RFC1034 3.1)\n\nIt is not possible to distinguish between an IPv4address and\na reg-name before reaching the end of the string, therefore\nthe length limit for IPv4address must be ignored until that\npoint.\n\nThe maximum length for the port component is 5. No configurable\nlimit is necessary.\n\nA request with a too long component of authority-form must be rejected with\na 414 status code and the closing of the connection. (RFC7230 3.1.1)\n\nThe authority is either resolved from configuration or is taken\ndirectly from authority-form. (RFC7230 5.5)\n\nThe path and query are empty when using authority-form. (RFC7230 5.5)\n\n: asterisk-form\n\nasterisk-form is used for server-wide OPTIONS requests.\nIt is invalid with any other methods which must reject the\nrequest with a 400 status code and the closing of the connection. (RFC7230 5.3.4)\n\n```\nasterisk-form  = \"*\"\n```\n\nThe asterisk-form always has a length of 1. No configurable limit\nis necessary.\n\nThe authority is empty when using asterisk-form.\n\nThe path and query are empty when using asterisk-form. (RFC7230 5.5)\n\n:: Between request-target and version\n\nA request that uses anything other than SP as separator between\nthe request-target and the version must be rejected with a 400\nstatus code and the closing of the connection. (RFC7230 3.1.1, RFC7230 3.5)\n\n:: Request version\n\n```\nversion  = \"HTTP/1.0\" / \"HTTP/1.1\"\n```\n\nAny version number other than HTTP/1.0 or HTTP/1.1 must be\nrejected by a server or intermediary with a 505 status code. (RFC7230 2.6, RFC7230 A.2)\n\nA request that has any whitespace or characters different than\nCRLF following the version must be rejected with a 400 status\ncode and the closing of the connection. (RFC7230 3.1.1)\n\n:: Request headers\n\n```\nheaders = *( header-field CRLF ) CRLF\nheader-field = field-name \":\" OWS field-value OWS\n\nfield-name = token\nfield-value = *( SP / HTAB / %21-7E / %80-FF )\n\nOWS = *( SP / HTAB )\n```\n\nThe header field name is case insensitive. (RFC7230 3.2)\n\nHTTP/2 requires header field names to be lowercase. It is\nperfectly acceptable for a server supporting both to convert\nHTTP/1.1 header names to lowercase when they are received. (draft-ietf-httpbis-http2-15 8.1.2.1)\n\nMessages that contain whitespace before the header name must\nbe rejected with a 400 status code and the closing of the\nconnection. (RFC7230 3.2.4)\n\nMessages that contain whitespace between the header name and\ncolon must be rejected with a 400 status code and the closing\nof the connection. (RFC7230 3.2.4)\n\nThe header name must be subject to a configurable limit. A\ngood default is 50 characters, well above the longest registered\nheader. Such a request must be rejected with a 431 status code\nand the closing of the connection. (RFC7230 3.2.5, RFC6585 5, IANA Message Headers registry)\n\nThe header value and the optional whitespace around it must be\nsubject to a configurable limit. There is no recommendations\nfor the default. 4096 characters is known to work well. Such\na request must be rejected with a 431 status code and the closing\nof the connection. (RFC7230 3.2.5, RFC6585 5)\n\nOptional whitespace before and after the header value is not\npart of the value and must be dropped.\n\nThe order of header fields with differing names is not significant. (RFC7230 3.2.2)\n\nThe normal procedure for parsing headers is to read each header\nfield into a hash table by field name until the empty line. (RFC7230 3)\n\nRequests with duplicate content-length or host headers must be rejected\nwith a 400 status code and the closing of the connection. (RFC7230 3.3.2)\n\nOther duplicate header fields must be combined by inserting a comma\nbetween the values in the order they were received. (RFC7230 3.2.2)\n\nDuplicate header field names are only allowed when their value is\na comma-separated list. In practice there is no need to perform\na check while reading the headers as the value will become invalid\nand the error can be handled while parsing the header later on. (RFC7230 3.2.2)\n\nThe request must not be processed until all headers have arrived. (RFC7230 3.2.2)\n\nThe number of headers allowed in a request must be subject to\na configurable limit. There is no recommendations for the default.\n100 headers is known to work well. Such a request must be rejected\nwith a 431 status code and the closing of the connection. (RFC7230 3.2.5, RFC6585 5)\n\nWhen parsing header field values, the server must ignore empty\nlist elements, and not count those as the count of elements present. (RFC7230 7)\n\nThe information in the via header is largely unreliable. (RFC7230 5.7.1)\n\n:: Request body\n\n```\nmessage-body = *OCTET\n```\n\nThe message body is the octets after decoding any transfer\ncodings. (RFC7230 3.3)\n\nA request has a message body only if it includes a transfer-encoding\nheader or a non-zero content-length header. (RFC7230 3.3)\n\n```\nTransfer-Encoding = 1#transfer-coding\n\ntransfer-coding = \"chunked\" / \"compress\" / \"deflate\" / \"gzip\" / transfer-extension\ntransfer-extension = token *( OWS \";\" OWS transfer-parameter )\ntransfer-parameter = token BWS \"=\" BWS ( token / quoted-string )\n```\n\nThe transfer-coding is case insensitive. (RFC7230 4)\n\nThere are no known other transfer-extension with the exception of\ndeprecated aliases \"x-compress\" and \"x-gzip\". (IANA HTTP Transfer Coding Registry,\nRFC7230 4.2.1, RFC7230 4.2.3, RFC7230 8.4.2)\n\nA server must be able to handle at least chunked transfer-encoding.\nThis is also the only coding that sees widespread use. (RFC7230 3.3.1, RFC7230 4.1)\n\nMessages encoded more than once with chunked transfer-encoding\nmust be rejected with a 400 status code and the closing of the\nconnection. (RFC7230 3.3.1)\n\nMessages where chunked, when present, is not the last\ntransfer-encoding must be rejected with a 400 status code\nand the closing of the connection. (RFC7230 3.3.3)\n\nSome non-conformant implementations send the \"deflate\" compressed\ndata without the zlib wrapper. (RFC7230 4.2.2)\n\nMessages encoded with a transfer-encoding the server does not\nunderstand must be rejected with a 501 status code and the\nclosing of the connection. (RFC7230 3.3.1)\n\nA server can reject requests with a body and no content-length\nheader with a 411 status code. (RFC7230 3.3.3)\n\n```\nContent-Length = 1*DIGIT\n```\n\nA request with an invalid content-length header must be rejected\nwith a 400 status code and the closing of the connection. (RFC7230 3.3.3)\n\nThe content-length header ranges from 0 to infinity. Requests\nwith a message body too large must be rejected with a 413 status\ncode and the closing of the connection. (RFC7230 3.3.2)\n\nWhen a message includes both transfer-encoding and content-length\nheaders, the content-length header must be removed before processing\nthe request. (RFC7230 3.3.3)\n\nIf a socket error occurs while reading the body the server\nmust send a 400 status code response and close the connection. (RFC7230 3.3.3, RFC7230 3.4)\n\nIf a timeout occurs while reading the body the server must\nsend a 408 status code response and close the connection. (RFC7230 3.3.3, RFC7230 3.4)\n\n: Body length\n\nThe length of a message with a transfer-encoding header can\nonly be determined on decoding completion. (RFC7230 3.3.3)\n\nThe length of a message with a content-length header is\nthe numeric value in octets found in the header. (RFC7230 3.3.3)\n\nA message with no transfer-encoding or content-length header\nhas a body length of 0. (RFC7230 3.3.3)\n\n: Chunked transfer-encoding\n\n```\nchunked-body = *chunk last-chunk trailer-part CRLF\n\nchunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF\nchunk-size = 1*HEXDIG\nchunk-data = 1*OCTET ; a sequence of chunk-size octets\n\nlast-chunk = 1*(\"0\") [ chunk-ext ] CRLF\n```\n\nThe chunk-size field is a string of hex digits indicating the size of\nthe chunk-data in octets.\n\n```\nchunk-ext = *( \";\" chunk-ext-name [ \"=\" chunk-ext-val ] )\nchunk-ext-name = token\nchunk-ext-val = token / quoted-string\n```\n\nUnknown chunk extensions must be ignored. (RFC7230 4.1.1)\n\nThe chunk-size line length must be subject to configuration.\nThere are no recommended defaults, although 100 octets should work.\nRequests with a too long line must be rejected with a 400 status\ncode and the closing of the connection.\n\n```\ntrailer-part = *( header-field CRLF )\n```\n\nTrailing headers must not include transfer-encoding, content-length,\nhost, cache-control, expect, max-forwards, pragma, range, te,\nif-match, if-none-match, if-modified-since, if-unmodified-since,\nif-range, www-authenticate, authorization, proxy-authenticate,\nproxy-authorization, age, cache-control, expires, date, location,\nretry-after, vary, warning, content-encoding, content-type,\ncontent-range, or trailer. (RFC7230 4.1.2)\n\nTrailer headers can be ignored safely. (RFC7230 4.1.2)\n\nWhen trailer headers are processed, invalid headers must be ignored.\nValid headers must be added to the list of headers of the request. (RFC7230 4.1.2)\n\nThe number of trailer headers must be subject to configuration.\nThere is no known recommendations for the default. A value of 10\nshould cover most cases. Requests with too many trailer headers\nmust be rejected with a 431 status code and the closing of the\nconnection. (RFC6585 5)\n\nUpon completion of chunk decoding the server must add a content-length\nheader with the value set to the total length of data read. (RFC7230 4.1.3)\n\nUpon completion of chunk decoding the server must remove \"chunked\"\nfrom the transfer-encoding header. This header must be removed if\nit becomes empty following this removal. (RFC7230 4.1.3)\n\nUpon completion of chunk decoding the server must remove the trailer\nheader from the list of headers. (RFC7230 4.1.3)\n\n```\nTrailer = 1#field-name\n```\n\nThe trailer header can be used to list the headers found in the\ntrailer. A server must have the option of ignoring trailer headers\nthat were not listed in the trailer header. (RFC7230 4.4)\n\nThe trailer header must be listed in the connection header field.\nTrailers must be ignored otherwise.\n\n:: Connection management\n\nNever assume any two requests on a single connection come\nfrom the same user agent. (RFC7230 2.3)\n\n```\nConnection = 1#token ; case-insensitive\n```\n\nThe connection token is either case insensitive \"close\", \"keep-alive\"\nor a header field name.\n\nThere are no corresponding \"close\" or \"keep-alive\" headers. (RFC7230 8.1, RFC7230 A.2)\n\nThe connection header is valid only for the immediate connection,\nalongside any header field it lists. (RFC7230 6.1)\n\nThe server must determine if the connection is persistent for\nevery message received by looking at the connection header and\nHTTP version. (RFC7230 6.3)\n\nHTTP/1.1 requests with no \"close\" option and HTTP/1.0 with the\n\"keep-alive\" option indicate the connection will persist. (RFC7230 6.1, RFC7230 6.3)\n\nHTTP/1.1 requests with the \"close\" option and HTTP/1.0 with no\n\"keep-alive\" option indicate the connection will be closed\nupon reception of the response by the client. (RFC7230 6.1, RFC7230 6.3)\n\nThe maximum number of requests sent using a persistent connection\nmust be subject to configuration. The connection must be closed\nwhen the limit is reached. (RFC7230 6.3)\n\nA server that doesn't want to read the entire body of a message\nmust close the connection, if possible after sending the \"close\"\nconnection option in the response. (RFC7230 6.3)\n\nA server can receive more than one request before any response\nis sent. This is called pipelining. The requests can be processed\nin parallel if they all have safe methods. Responses must be sent\nin the same order as the requests. (RFC7230 6.3.2)\n\nThe server must reject abusive traffic by closing the connection.\nAbusive traffic can come from the form of too many requests in a\ngiven amount of time, or too many concurrent connections. Limits\nmust be subject to configuration. (RFC7230 6.4)\n\nThe server must close inactive connections. The timeout\nmust be subject to configuration. (RFC7230 6.5)\n\nThe server must monitor connections for the close signal\nand close the socket on its end accordingly. (RFC7230 6.5)\n\nA connection close may occur at any time. (RFC7230 6.5)\n\nThe server must not process any request after sending or\nreceiving the \"close\" connection option. (RFC7230 6.6)\n\nThe server must close the connection in stages to avoid the\nTCP reset problem. The server starts by closing the write\nside of the socket. The server then reads until it detects\nthe socket has been closed, until it can be certain its\nlast response has been received by the client, or until\na close or timeout occurs. The server then fully close the\nconnection. (6.6)\n\n:: Routing\n\n```\nHost = authority ; same as authority-form\n```\n\nAn HTTP/1.1 request that lack a host header must be rejected with\na 400 status code and the closing of the connection. (RFC7230 5.4)\n\nAn HTTP/1.0 request that lack a host header is valid. Behavior\nfor these requests is configuration dependent. (RFC7230 5.5)\n\nA request with an invalid host header must be rejected with a\n400 status code and the closing of the connection. (RFC7230 5.4)\n\nAn authority component with a userinfo component (and its\n\"@\" delimiter) is invalid. The request must be rejected with\na 400 status code and the closing of the connection. (RFC7230 2.7.1)\n\nWhen using absolute-form the URI authority component must be\nidentical to the host header. Invalid requests must be rejected\nwith a 400 status code and the closing of the connection. (RFC7230 5.4)\n\nWhen using authority-form the URI authority component must be\nidentical to the host header. Invalid requests must be rejected\nwith a 400 status code and the closing of the connection.\n\nThe host header is empty when the authority component is undefined. (RFC7230 5.4)\n\nThe effective request URI can be rebuilt by concatenating scheme,\n\"://\", authority, path and query components. (RFC7230 5.5)\n\nResources with identical URI except for the scheme component\nmust be treated as different. (RFC7230 2.7.2)\n\n:: Response\n\nA server can send more than one response per request only when a\n1xx response is sent preceding the final response. (RFC7230 5.6)\n\nA server that does parallel pipelining must send responses in the\nsame order as the requests came in. (RFC7230 5.6)\n\n```\nHTTP-response = status-line *( header-field CRLF ) CRLF [ message-body ]\n```\n\nThe response format must be followed strictly.\n\n```\nstatus-line   = HTTP-version SP status-code SP reason-phrase CRLF\nstatus-code   = 3DIGIT\nreason-phrase = *( HTAB / SP / VCHAR / obs-text )\n```\n\nA server must send its own version. (RFC7230 2.6)\n\nAn HTTP/1.1 server may send an HTTP/1.0 version for compatibility purposes. (RFC7230 2.6)\n\nRFC6585 defines additional status code a server can use to reject\nmessages. (RFC7230 9.3, RFC6585)\n\n:: Response headers\n\nIn responses, OWS must be generated as SP or not generated\nat all. RWS must be generated as SP. BWS must not be\ngenerated. (RFC7230 3.2.3)\n\n```\nheader-field = field-name \":\" SP field-value\n\nfield-name = token ; case-insensitive\nfield-value = *( SP / %21-7E / %80-FF )\n```\n\nIn quoted-string found in field-value, quoted-pair must only be\nused for DQUOTE and backslash. (RFC7230 3.2.6)\n\nThe server must not generate comments in header values.\n\nHTTP header values must use US-ASCII encoding and must only send\nprintable characters or SP. (RFC7230 3.2.4, RFC7230 9.4)\n\nThe server must not generate empty list elements in headers. (RFC7230 7)\n\nWhen encoding an URI as part of a response, only characters that\nare reserved need to be percent-encoded. (RFC7230 2.7.3)\n\nThe set-cookie header must be handled as a special case. There\nmust be exactly one set-cookie header field per cookie. (RFC7230 3.2.2)\n\nThe server must list headers for or about the immediate connection\nin the connection header field. (RFC7230 6.1)\n\nA server that does not support persistent connections must\nsend \"close\" in every non-1xx response. (RFC7230 6.1)\n\nA server must not send a \"close\" connection option\nin 1xx responses. (RFC7230 6.1)\n\nThe \"close\" connection must be sent in a message when the\nsender knows it will close the connection after fully sending\nthe response. (RFC7230 6.6)\n\nA server must close the connection after sending or\nreceiving a \"close\" once the response has been sent. (RFC7230 6.6)\n\nA server must send a \"close\" in a response to a request\ncontaining a \"close\". (RFC7230 6.6)\n\n:: Response body\n\nResponses to HEAD requests never include a message body. (RFC7230 3.3)\n\n2xx responses to CONNECT requests never include a message\nbody. (RFC7230 3.3)\n\n1xx, 204 and 304 responses never include a message body. (RFC7230 3.3)\n\nResponses to HEAD requests and 304 responses can include a\ncontent-length or transfer-encoding header. Their value must\nbe the same as if the request was an unconditional GET. (RFC7230 3.3, RFC7230 3.3.1, RFC7230 3.3.2)\n\n1xx, 204 responses and 2xx responses to CONNECT requests must\nnot include a content-length or transfer-encoding header. (RFC7230 3.3.1, RFC7230 3.3.2)\n\n```\nmessage-body = *OCTET\n```\n\nThe message body is the octets after decoding any transfer\ncodings. (RFC7230 3.3)\n\nWhen the length is known in advance, the server must send a\ncontent-length header, including if the length is 0. (RFC7230 3.3.2, RFC7230 3.3.3)\n\nWhen the length is not known in advance, the chunked transfer-encoding\nmust be used. (RFC7230 3.3.2, RFC7230 3.3.3)\n\nFor compatibility purposes a server can send no content-length or\ntransfer-encoding header. In this case the connection must be\nclosed after the response has been sent fully. (RFC7230 3.3.2, RFC7230 3.3.3)\n\nThe content-length header must not be sent when a transfer-encoding\nheader already exists. (RFC7230 3.3.2)\n\nThe server must not apply the chunked transfer-encoding more than\nonce. (RFC7230 3.3.1)\n\nThe server must apply the chunked transfer-encoding last. (RFC7230 3.3.1)\n\nThe transfer-encoding header must not be sent in responses to\nHTTP/1.0 requests, or in responses that use the HTTP/1.0 version.\nNo transfer codings must be applied in these cases. (RFC7230 3.3.1)\n\n```\nTE = #t-codings\n\nt-codings = \"trailers\" / ( transfer-coding [ t-ranking ] )\nt-ranking = OWS \";\" OWS \"q=\" rank\nrank = ( \"0\" [ \".\" 0*3DIGIT ] ) / ( \"1\" [ \".\" 0*3(\"0\") ] )\n```\n\nTrailers can only be sent if the request includes a TE header\ncontaining \"trailers\". (RFC7230 4.1.2)\n\nThe presence of \"chunked\" in a TE header must be ignored as it\nis always acceptable with HTTP/1.1. (RFC7230 4.3)\n\nA qvalue of 0 in the TE header means \"not acceptable\". (RFC7230 4.3)\n\nThe lack of a TE header or an empty TE header means only \"chunked\"\n(with no trailers) or no transfer-encoding is acceptable. (RFC7230 4.3)\n\nThe TE header must be listed in the connection header field,\nor must be ignored otherwise.\n\nTrailer headers must be listed in the trailer header field value. (RFC7230 4.4)\n\nWhen defined, the trailer header must also be listed in the connection header. (RFC7230 4.4)\n\n:: Upgrade\n\n```\nUpgrade = 1#protocol\n\nprotocol = protocol-name [\"/\" protocol-version]\nprotocol-name = token\nprotocol-version = token\n```\n\nThe upgrade header contains the list of protocols the\nclient wishes to upgrade to, in order of preference. (RFC7230 6.7)\n\nThe upgrade header can be safely ignored. (RFC7230 6.7)\n\nThe upgrade header must be listed under the connection header,\nor must be ignored otherwise. (RFC7230 6.7)\n\nA server accepting an upgrade request must send a 101 status\ncode with a upgrade header listing the protocol(s) it upgrades\nto, in layer-ascending order. In addition the upgrade header\nmust be listed in the connection header. (RFC7230 6.7)\n\nA server must not switch to a protocol not listed in the\nrequest's upgrade header. (RFC7230 6.7)\n\nA server that sends a 426 status code must include a upgrade\nheader listing acceptable protocols in order of preference. (RFC7230 6.7)\n\nA server can send a upgrade header to any response to advertise\nits support for other protocols listed in order of preference. (RFC7230 6.7)\n\nImmediately after a server responds with a 101 status code\nit must respond to the original request using the new protocol. (RFC7230 6.7)\n\nA server must not switch protocols unless the original message's\nsemantics can be honored by the new protocol. OPTIONS requests\ncan be honored by any protocol. (RFC7230 6.7)\n\nA server must ignore an upgrade header received by an HTTP/1.0\nrequest. (RFC7230 6.7)\n\nA server receiving both an upgrade header and an expect header\ncontaining \"100-continue\" must send a 100 response before the\n101 response. (RFC7230 6.7)\n\nThe upgrade header field cannot be used for switching the\nconnection protocol (e.g. TCP) or switching connections. (RFC7230 6.7)\n\n:: Compatibility\n\nA server can choose to be non-conformant to the specifications\nfor the sake of compatibility. Such behavior can be enabled\nthrough configuration and/or software identification. (RFC7230 2.5)\n"
  },
  {
    "path": "ebin/cowboy.app",
    "content": "{application, 'cowboy', [\n\t{description, \"Small, fast, modern HTTP server.\"},\n\t{vsn, \"2.14.2\"},\n\t{modules, ['cowboy','cowboy_app','cowboy_bstr','cowboy_children','cowboy_clear','cowboy_clock','cowboy_compress_h','cowboy_constraints','cowboy_decompress_h','cowboy_handler','cowboy_http','cowboy_http2','cowboy_http3','cowboy_loop','cowboy_metrics_h','cowboy_middleware','cowboy_quicer','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','cowboy_webtransport']},\n\t{registered, [cowboy_sup,cowboy_clock]},\n\t{applications, [kernel,stdlib,crypto,cowlib,ranch]},\n\t{optional_applications, []},\n\t{mod, {cowboy_app, []}},\n\t{env, []}\n]}."
  },
  {
    "path": "erlang.mk",
    "content": "# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>\n#\n# Permission to use, copy, modify, and/or distribute this software for any\n# purpose with or without fee is hereby granted, provided that the above\n# copyright notice and this permission notice appear in all copies.\n#\n# THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n.PHONY: all app apps deps search rel relup docs install-docs check tests clean distclean help erlang-mk\n\nERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST)))\nexport ERLANG_MK_FILENAME\n\nERLANG_MK_VERSION = 2022.05.31-142-gba4dcff-dirty\nERLANG_MK_WITHOUT = \n\n# Make 3.81 and 3.82 are deprecated.\n\nifeq ($(MAKELEVEL)$(MAKE_VERSION),03.81)\n$(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html)\nendif\n\nifeq ($(MAKELEVEL)$(MAKE_VERSION),03.82)\n$(warning Please upgrade to GNU Make 4 or later: https://erlang.mk/guide/installation.html)\nendif\n\n# Core configuration.\n\nPROJECT ?= $(notdir $(CURDIR))\nPROJECT := $(strip $(PROJECT))\n\nPROJECT_VERSION ?= rolling\nPROJECT_MOD ?=\nPROJECT_ENV ?= []\n\n# Verbosity.\n\nV ?= 0\n\nverbose_0 = @\nverbose_2 = set -x;\nverbose = $(verbose_$(V))\n\nifeq ($V,3)\nSHELL := $(SHELL) -x\nendif\n\ngen_verbose_0 = @echo \" GEN   \" $@;\ngen_verbose_2 = set -x;\ngen_verbose = $(gen_verbose_$(V))\n\ngen_verbose_esc_0 = @echo \" GEN   \" $$@;\ngen_verbose_esc_2 = set -x;\ngen_verbose_esc = $(gen_verbose_esc_$(V))\n\n# Temporary files directory.\n\nERLANG_MK_TMP ?= $(CURDIR)/.erlang.mk\nexport ERLANG_MK_TMP\n\n# \"erl\" command.\n\nERL = erl -noinput -boot no_dot_erlang -kernel start_distribution false +P 1024 +Q 1024\n\n# Platform detection.\n\nifeq ($(PLATFORM),)\nUNAME_S := $(shell uname -s)\n\nifeq ($(UNAME_S),Linux)\nPLATFORM = linux\nelse ifeq ($(UNAME_S),Darwin)\nPLATFORM = darwin\nelse ifeq ($(UNAME_S),SunOS)\nPLATFORM = solaris\nelse ifeq ($(UNAME_S),GNU)\nPLATFORM = gnu\nelse ifeq ($(UNAME_S),FreeBSD)\nPLATFORM = freebsd\nelse ifeq ($(UNAME_S),NetBSD)\nPLATFORM = netbsd\nelse ifeq ($(UNAME_S),OpenBSD)\nPLATFORM = openbsd\nelse ifeq ($(UNAME_S),DragonFly)\nPLATFORM = dragonfly\nelse ifeq ($(shell uname -o),Msys)\nPLATFORM = msys2\nelse\n$(error Unable to detect platform. Please open a ticket with the output of uname -a.)\nendif\n\nexport PLATFORM\nendif\n\n# Core targets.\n\nall:: deps app rel\n\n# Noop to avoid a Make warning when there's nothing to do.\nrel::\n\t$(verbose) :\n\nrelup:: deps app\n\ncheck:: tests\n\nclean:: clean-crashdump\n\nclean-crashdump:\nifneq ($(wildcard erl_crash.dump),)\n\t$(gen_verbose) rm -f erl_crash.dump\nendif\n\ndistclean:: clean distclean-tmp\n\n$(ERLANG_MK_TMP):\n\t$(verbose) mkdir -p $(ERLANG_MK_TMP)\n\ndistclean-tmp:\n\t$(gen_verbose) rm -rf $(ERLANG_MK_TMP)\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \\\n\t\t\"erlang.mk (version $(ERLANG_MK_VERSION)) is distributed under the terms of the ISC License.\" \\\n\t\t\"Copyright (c) 2013-2016 Loïc Hoguin <essen@ninenines.eu>\" \\\n\t\t\"\" \\\n\t\t\"Usage: [V=1] $(MAKE) [target]...\" \\\n\t\t\"\" \\\n\t\t\"Core targets:\" \\\n\t\t\"  all           Run deps, app and rel targets in that order\" \\\n\t\t\"  app           Compile the project\" \\\n\t\t\"  deps          Fetch dependencies (if needed) and compile them\" \\\n\t\t\"  fetch-deps    Fetch dependencies recursively (if needed) without compiling them\" \\\n\t\t\"  list-deps     List dependencies recursively on stdout\" \\\n\t\t\"  search q=...  Search for a package in the built-in index\" \\\n\t\t\"  rel           Build a release for this project, if applicable\" \\\n\t\t\"  docs          Build the documentation for this project\" \\\n\t\t\"  install-docs  Install the man pages for this project\" \\\n\t\t\"  check         Compile and run all tests and analysis for this project\" \\\n\t\t\"  tests         Run the tests for this project\" \\\n\t\t\"  clean         Delete temporary and output files from most targets\" \\\n\t\t\"  distclean     Delete all temporary and output files\" \\\n\t\t\"  help          Display this help and exit\" \\\n\t\t\"  erlang-mk     Update erlang.mk to the latest version\"\n\n# Core functions.\n\nempty :=\nspace := $(empty) $(empty)\ntab := $(empty)\t$(empty)\ncomma := ,\n\ndefine newline\n\n\nendef\n\ndefine comma_list\n$(subst $(space),$(comma),$(strip $1))\nendef\n\ndefine escape_dquotes\n$(subst \",\\\",$1)\nendef\n\n# Adding erlang.mk to make Erlang scripts who call init:get_plain_arguments() happy.\ndefine erlang\n$(ERL) $2 -pz $(ERLANG_MK_TMP)/rebar3/_build/prod/lib/*/ebin/ -eval \"$(subst $(newline),,$(call escape_dquotes,$1))\" -- erlang.mk\nendef\n\nifeq ($(PLATFORM),msys2)\ncore_native_path = $(shell cygpath -m $1)\nelse\ncore_native_path = $1\nendif\n\ncore_http_get = curl -Lf$(if $(filter-out 0,$V),,s)o $(call core_native_path,$1) $2\n\ncore_eq = $(and $(findstring $1,$2),$(findstring $2,$1))\n\n# We skip files that contain spaces because they end up causing issues.\n# Files that begin with a dot are already ignored by the wildcard function.\ncore_find = $(foreach f,$(wildcard $(1:%/=%)/*),$(if $(wildcard $f/.),$(call core_find,$f,$2),$(if $(filter $(subst *,%,$2),$f),$(if $(wildcard $f),$f))))\n\ncore_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))))))))))))))))))))))))))\n\ncore_ls = $(filter-out $1,$(shell echo $1))\n\n# @todo Use a solution that does not require using perl.\ncore_relpath = $(shell perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . \"\\n\"' $1 $2)\n\ndefine core_render\n\tprintf -- '$(subst $(newline),\\n,$(subst %,%%,$(subst ','\\'',$(subst $(tab),$(WS),$(call $1)))))\\n' > $2\nendef\n\n# Automated update.\n\nERLANG_MK_REPO ?= https://github.com/ninenines/erlang.mk\nERLANG_MK_COMMIT ?=\nERLANG_MK_BUILD_CONFIG ?= build.config\nERLANG_MK_BUILD_DIR ?= .erlang.mk.build\n\nerlang-mk: WITHOUT ?= $(ERLANG_MK_WITHOUT)\nerlang-mk:\nifdef ERLANG_MK_COMMIT\n\t$(verbose) git clone $(ERLANG_MK_REPO) $(ERLANG_MK_BUILD_DIR)\n\t$(verbose) cd $(ERLANG_MK_BUILD_DIR) && git checkout $(ERLANG_MK_COMMIT)\nelse\n\t$(verbose) git clone --depth 1 $(ERLANG_MK_REPO) $(ERLANG_MK_BUILD_DIR)\nendif\n\t$(verbose) if [ -f $(ERLANG_MK_BUILD_CONFIG) ]; then cp $(ERLANG_MK_BUILD_CONFIG) $(ERLANG_MK_BUILD_DIR)/build.config; fi\n\t$(gen_verbose) $(MAKE) --no-print-directory -C $(ERLANG_MK_BUILD_DIR) WITHOUT='$(strip $(WITHOUT))' UPGRADE=1\n\t$(verbose) cp $(ERLANG_MK_BUILD_DIR)/erlang.mk ./erlang.mk\n\t$(verbose) rm -rf $(ERLANG_MK_BUILD_DIR)\n\t$(verbose) rm -rf $(ERLANG_MK_TMP)\n\n# The erlang.mk package index is bundled in the default erlang.mk build.\n# Search for the string \"copyright\" to skip to the rest of the code.\n\n# Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: distclean-kerl\n\nKERL_INSTALL_DIR ?= $(HOME)/erlang\n\nifeq ($(strip $(KERL)),)\nKERL := $(ERLANG_MK_TMP)/kerl/kerl\nendif\n\nKERL_DIR = $(ERLANG_MK_TMP)/kerl\n\nexport KERL\n\nKERL_GIT ?= https://github.com/kerl/kerl\nKERL_COMMIT ?= master\n\nKERL_MAKEFLAGS ?=\n\nOTP_GIT ?= https://github.com/erlang/otp\n\ndefine kerl_otp_target\n$(KERL_INSTALL_DIR)/$1: $(KERL)\n\t$(verbose) if [ ! -d $$@ ]; then \\\n\t\tMAKEFLAGS=\"$(KERL_MAKEFLAGS)\" $(KERL) build git $(OTP_GIT) $1 $1; \\\n\t\t$(KERL) install $1 $(KERL_INSTALL_DIR)/$1; \\\n\tfi\nendef\n\n$(KERL): $(KERL_DIR)\n\n$(KERL_DIR): | $(ERLANG_MK_TMP)\n\t$(gen_verbose) git clone --depth 1 $(KERL_GIT) $(ERLANG_MK_TMP)/kerl\n\t$(verbose) cd $(ERLANG_MK_TMP)/kerl && git checkout $(KERL_COMMIT)\n\t$(verbose) chmod +x $(KERL)\n\ndistclean:: distclean-kerl\n\ndistclean-kerl:\n\t$(gen_verbose) rm -rf $(KERL_DIR)\n\n# Allow users to select which version of Erlang/OTP to use for a project.\n\nifneq ($(strip $(LATEST_ERLANG_OTP)),)\n# In some environments it is necessary to filter out master.\nERLANG_OTP := $(notdir $(lastword $(sort\\\n\t$(filter-out $(KERL_INSTALL_DIR)/master $(KERL_INSTALL_DIR)/OTP_R%,\\\n\t$(filter-out %-rc1 %-rc2 %-rc3,$(wildcard $(KERL_INSTALL_DIR)/*[^-native]))))))\nendif\n\nERLANG_OTP ?=\n\n# Use kerl to enforce a specific Erlang/OTP version for a project.\nifneq ($(strip $(ERLANG_OTP)),)\n\nexport PATH := $(KERL_INSTALL_DIR)/$(ERLANG_OTP)/bin:$(PATH)\nSHELL := env PATH=$(PATH) $(SHELL)\n$(eval $(call kerl_otp_target,$(ERLANG_OTP)))\n\n# Build Erlang/OTP only if it doesn't already exist.\nifeq ($(wildcard $(KERL_INSTALL_DIR)/$(ERLANG_OTP))$(BUILD_ERLANG_OTP),)\n$(info Building Erlang/OTP $(ERLANG_OTP)... Please wait...)\n$(shell $(MAKE) $(KERL_INSTALL_DIR)/$(ERLANG_OTP) ERLANG_OTP=$(ERLANG_OTP) BUILD_ERLANG_OTP=1 >&2)\nendif\n\nendif\n\nPACKAGES += asciideck\npkg_asciideck_name = asciideck\npkg_asciideck_description = Asciidoc for Erlang.\npkg_asciideck_homepage = https://ninenines.eu\npkg_asciideck_fetch = git\npkg_asciideck_repo = https://github.com/ninenines/asciideck\npkg_asciideck_commit = master\n\nPACKAGES += cowboy\npkg_cowboy_name = cowboy\npkg_cowboy_description = Small, fast and modular HTTP server.\npkg_cowboy_homepage = http://ninenines.eu\npkg_cowboy_fetch = git\npkg_cowboy_repo = https://github.com/ninenines/cowboy\npkg_cowboy_commit = master\n\nPACKAGES += cowlib\npkg_cowlib_name = cowlib\npkg_cowlib_description = Support library for manipulating Web protocols.\npkg_cowlib_homepage = http://ninenines.eu\npkg_cowlib_fetch = git\npkg_cowlib_repo = https://github.com/ninenines/cowlib\npkg_cowlib_commit = master\n\nPACKAGES += elixir\npkg_elixir_name = elixir\npkg_elixir_description = Elixir is a dynamic, functional language for building scalable and maintainable applications.\npkg_elixir_homepage = https://elixir-lang.org\npkg_elixir_fetch = git\npkg_elixir_repo = https://github.com/elixir-lang/elixir\npkg_elixir_commit = main\n\nPACKAGES += erlydtl\npkg_erlydtl_name = erlydtl\npkg_erlydtl_description = Django Template Language for Erlang.\npkg_erlydtl_homepage = https://github.com/erlydtl/erlydtl\npkg_erlydtl_fetch = git\npkg_erlydtl_repo = https://github.com/erlydtl/erlydtl\npkg_erlydtl_commit = master\n\nPACKAGES += gpb\npkg_gpb_name = gpb\npkg_gpb_description = A Google Protobuf implementation for Erlang\npkg_gpb_homepage = https://github.com/tomas-abrahamsson/gpb\npkg_gpb_fetch = git\npkg_gpb_repo = https://github.com/tomas-abrahamsson/gpb\npkg_gpb_commit = master\n\nPACKAGES += gun\npkg_gun_name = gun\npkg_gun_description = Asynchronous SPDY, HTTP and Websocket client written in Erlang.\npkg_gun_homepage = http//ninenines.eu\npkg_gun_fetch = git\npkg_gun_repo = https://github.com/ninenines/gun\npkg_gun_commit = master\n\nPACKAGES += hex_core\npkg_hex_core_name = hex_core\npkg_hex_core_description = Reference implementation of Hex specifications\npkg_hex_core_homepage = https://github.com/hexpm/hex_core\npkg_hex_core_fetch = git\nHEX_CORE_GIT ?= https://github.com/hexpm/hex_core\npkg_hex_core_repo = $(HEX_CORE_GIT)\npkg_hex_core_commit = e57b4fb15cde710b3ae09b1d18f148f6999a63cc\n\nPACKAGES += proper\npkg_proper_name = proper\npkg_proper_description = PropEr: a QuickCheck-inspired property-based testing tool for Erlang.\npkg_proper_homepage = http://proper.softlab.ntua.gr\npkg_proper_fetch = git\npkg_proper_repo = https://github.com/manopapad/proper\npkg_proper_commit = master\n\nPACKAGES += ranch\npkg_ranch_name = ranch\npkg_ranch_description = Socket acceptor pool for TCP protocols.\npkg_ranch_homepage = http://ninenines.eu\npkg_ranch_fetch = git\npkg_ranch_repo = https://github.com/ninenines/ranch\npkg_ranch_commit = master\n\nPACKAGES += relx\npkg_relx_name = relx\npkg_relx_description = Sane, simple release creation for Erlang\npkg_relx_homepage = https://github.com/erlware/relx\npkg_relx_fetch = git\npkg_relx_repo = https://github.com/erlware/relx\npkg_relx_commit = main\n\nPACKAGES += triq\npkg_triq_name = triq\npkg_triq_description = Trifork QuickCheck\npkg_triq_homepage = https://triq.gitlab.io\npkg_triq_fetch = git\npkg_triq_repo = https://gitlab.com/triq/triq.git\npkg_triq_commit = master\n\n# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: search\n\ndefine pkg_print\n\t$(verbose) printf \"%s\\n\" \\\n\t\t$(if $(call core_eq,$1,$(pkg_$(1)_name)),,\"Pkg name:    $1\") \\\n\t\t\"App name:    $(pkg_$(1)_name)\" \\\n\t\t\"Description: $(pkg_$(1)_description)\" \\\n\t\t\"Home page:   $(pkg_$(1)_homepage)\" \\\n\t\t\"Fetch with:  $(pkg_$(1)_fetch)\" \\\n\t\t\"Repository:  $(pkg_$(1)_repo)\" \\\n\t\t\"Commit:      $(pkg_$(1)_commit)\" \\\n\t\t\"\"\n\nendef\n\nsearch:\nifdef q\n\t$(foreach p,$(PACKAGES), \\\n\t\t$(if $(findstring $(call core_lc,$q),$(call core_lc,$(pkg_$(p)_name) $(pkg_$(p)_description))), \\\n\t\t\t$(call pkg_print,$p)))\nelse\n\t$(foreach p,$(PACKAGES),$(call pkg_print,$p))\nendif\n\n# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: distclean-deps clean-tmp-deps.log\n\n# Configuration.\n\nifdef OTP_DEPS\n$(warning The variable OTP_DEPS is deprecated in favor of LOCAL_DEPS.)\nendif\n\nIGNORE_DEPS ?=\nexport IGNORE_DEPS\n\nAPPS_DIR ?= $(CURDIR)/apps\nexport APPS_DIR\n\nDEPS_DIR ?= $(CURDIR)/deps\nexport DEPS_DIR\n\nREBAR_DEPS_DIR = $(DEPS_DIR)\nexport REBAR_DEPS_DIR\n\n# When testing Erlang.mk and updating these, make sure\n# to delete test/test_rebar_git before running tests again.\nREBAR3_GIT ?= https://github.com/erlang/rebar3\nREBAR3_COMMIT ?= bde4b54248d16280b2c70a244aca3bb7566e2033 # 3.23.0\n\nCACHE_DEPS ?= 0\n\nCACHE_DIR ?= $(if $(XDG_CACHE_HOME),$(XDG_CACHE_HOME),$(HOME)/.cache)/erlang.mk\nexport CACHE_DIR\n\nHEX_CONFIG ?=\n\ndefine hex_config.erl\n\tbegin\n\t\tConfig0 = hex_core:default_config(),\n\t\tConfig0$(HEX_CONFIG)\n\tend\nendef\n\n# External \"early\" plugins (see core/plugins.mk for regular plugins).\n# They both use the core_dep_plugin macro.\n\ndefine core_dep_plugin\nifeq ($2,$(PROJECT))\n-include $$(patsubst $(PROJECT)/%,%,$1)\nelse\n-include $(DEPS_DIR)/$1\n\n$(DEPS_DIR)/$1: $(DEPS_DIR)/$2 ;\nendif\nendef\n\nDEP_EARLY_PLUGINS ?=\n\n$(foreach p,$(DEP_EARLY_PLUGINS),\\\n\t$(eval $(if $(findstring /,$p),\\\n\t\t$(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\\\n\t\t$(call core_dep_plugin,$p/early-plugins.mk,$p))))\n\n# Query functions.\n\nquery_fetch_method = $(if $(dep_$(1)),$(call _qfm_dep,$(word 1,$(dep_$(1)))),$(call _qfm_pkg,$1))\n_qfm_dep = $(if $(dep_fetch_$(1)),$1,fail)\n_qfm_pkg = $(if $(pkg_$(1)_fetch),$(pkg_$(1)_fetch),fail)\n\nquery_name = $(if $(dep_$(1)),$1,$(if $(pkg_$(1)_name),$(pkg_$(1)_name),$1))\n\nquery_repo = $(call _qr,$1,$(call query_fetch_method,$1))\n_qr = $(if $(query_repo_$(2)),$(call query_repo_$(2),$1),$(call query_repo_git,$1))\n\nquery_repo_default = $(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_repo))\nquery_repo_git = $(patsubst git://github.com/%,https://github.com/%,$(call query_repo_default,$1))\nquery_repo_git-subfolder = $(call query_repo_git,$1)\nquery_repo_git-submodule = -\nquery_repo_hg = $(call query_repo_default,$1)\nquery_repo_svn = $(call query_repo_default,$1)\nquery_repo_cp = $(call query_repo_default,$1)\nquery_repo_ln = $(call query_repo_default,$1)\nquery_repo_hex = https://hex.pm/packages/$(if $(word 3,$(dep_$(1))),$(word 3,$(dep_$(1))),$1)\nquery_repo_fail = -\n\nquery_version = $(call _qv,$1,$(call query_fetch_method,$1))\n_qv = $(if $(query_version_$(2)),$(call query_version_$(2),$1),$(call query_version_default,$1))\n\nquery_version_default = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 3,$(dep_$(1))),$(pkg_$(1)_commit)))\nquery_version_git = $(call query_version_default,$1)\nquery_version_git-subfolder = $(call query_version_default,$1)\nquery_version_git-submodule = -\nquery_version_hg = $(call query_version_default,$1)\nquery_version_svn = -\nquery_version_cp = -\nquery_version_ln = -\nquery_version_hex = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_commit)))\nquery_version_fail = -\n\nquery_extra = $(call _qe,$1,$(call query_fetch_method,$1))\n_qe = $(if $(query_extra_$(2)),$(call query_extra_$(2),$1),-)\n\nquery_extra_git = -\nquery_extra_git-subfolder = $(if $(dep_$(1)),subfolder=$(word 4,$(dep_$(1))),-)\nquery_extra_git-submodule = -\nquery_extra_hg = -\nquery_extra_svn = -\nquery_extra_cp = -\nquery_extra_ln = -\nquery_extra_hex = $(if $(dep_$(1)),package-name=$(word 3,$(dep_$(1))),-)\nquery_extra_fail = -\n\nquery_absolute_path = $(addprefix $(DEPS_DIR)/,$(call query_name,$1))\n\n# Deprecated legacy query function. Used by RabbitMQ and its third party plugins.\n# Can be removed once RabbitMQ has been updated and enough time has passed.\ndep_name = $(call query_name,$(1))\n\n# Application directories.\n\nLOCAL_DEPS_DIRS = $(foreach a,$(LOCAL_DEPS),$(if $(wildcard $(APPS_DIR)/$a),$(APPS_DIR)/$a))\n# Elixir is handled specially as it must be built before all other deps\n# when Mix autopatching is necessary.\nALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(BUILD_DEPS) $(DEPS)),$(call query_name,$(dep))))\n\n# When we are calling an app directly we don't want to include it here\n# otherwise it'll be treated both as an apps and a top-level project.\nALL_APPS_DIRS = $(if $(wildcard $(APPS_DIR)/),$(filter-out $(APPS_DIR),$(shell find $(APPS_DIR) -maxdepth 1 -type d)))\nifdef ROOT_DIR\nifndef IS_APP\nALL_APPS_DIRS := $(filter-out $(APPS_DIR)/$(notdir $(CURDIR)),$(ALL_APPS_DIRS))\nendif\nendif\n\nifeq ($(filter $(APPS_DIR) $(DEPS_DIR),$(subst :, ,$(ERL_LIBS))),)\nifeq ($(ERL_LIBS),)\n\tERL_LIBS = $(APPS_DIR):$(DEPS_DIR)\nelse\n\tERL_LIBS := $(ERL_LIBS):$(APPS_DIR):$(DEPS_DIR)\nendif\nendif\nexport ERL_LIBS\n\nexport NO_AUTOPATCH\n\n# Elixir.\n\n# Elixir is automatically enabled in all cases except when\n# an Erlang project uses an Elixir dependency. In that case\n# $(ELIXIR) must be set explicitly.\nELIXIR ?= $(if $(filter elixir,$(BUILD_DEPS) $(DEPS)),dep,$(if $(EX_FILES),system,disable))\nexport ELIXIR\n\n# Verbosity.\n\ndep_verbose_0 = @echo \" DEP    $1 ($(call query_version,$1))\";\ndep_verbose_2 = set -x;\ndep_verbose = $(dep_verbose_$(V))\n\n# Optimization: don't recompile deps unless truly necessary.\n\nifndef IS_DEP\nifneq ($(MAKELEVEL),0)\n$(shell rm -f ebin/dep_built)\nendif\nendif\n\n# Core targets.\n\nALL_APPS_DIRS_TO_BUILD = $(if $(LOCAL_DEPS_DIRS)$(IS_APP),$(LOCAL_DEPS_DIRS),$(ALL_APPS_DIRS))\n\napps:: $(ALL_APPS_DIRS) clean-tmp-deps.log | $(ERLANG_MK_TMP)\n# Create ebin directory for all apps to make sure Erlang recognizes them\n# as proper OTP applications when using -include_lib. This is a temporary\n# fix, a proper fix would be to compile apps/* in the right order.\nifndef IS_APP\nifneq ($(ALL_APPS_DIRS),)\n\t$(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \\\n\t\tmkdir -p $$dep/ebin; \\\n\tdone\nendif\nendif\n# At the toplevel: if LOCAL_DEPS is defined with at least one local app, only\n# compile that list of apps. Otherwise, compile everything.\n# Within an app: compile all LOCAL_DEPS that are (uncompiled) local apps.\nifneq ($(ALL_APPS_DIRS_TO_BUILD),)\n\t$(verbose) set -e; for dep in $(ALL_APPS_DIRS_TO_BUILD); do \\\n\t\tif grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/apps.log; then \\\n\t\t\t:; \\\n\t\telse \\\n\t\t\techo $$dep >> $(ERLANG_MK_TMP)/apps.log; \\\n\t\t\t$(MAKE) -C $$dep $(if $(IS_TEST),test-build-app) IS_APP=1; \\\n\t\tfi \\\n\tdone\nendif\n\nclean-tmp-deps.log:\nifeq ($(IS_APP)$(IS_DEP),)\n\t$(verbose) rm -f $(ERLANG_MK_TMP)/apps.log $(ERLANG_MK_TMP)/deps.log\nendif\n\n# Erlang.mk does not rebuild dependencies after they were compiled\n# once. If a developer is working on the top-level project and some\n# dependencies at the same time, he may want to change this behavior.\n# There are two solutions:\n#     1. Set `FULL=1` so that all dependencies are visited and\n#        recursively recompiled if necessary.\n#     2. Set `FORCE_REBUILD=` to the specific list of dependencies that\n#        should be recompiled (instead of the whole set).\n\nFORCE_REBUILD ?=\n\nifeq ($(origin FULL),undefined)\nifneq ($(strip $(force_rebuild_dep)$(FORCE_REBUILD)),)\ndefine force_rebuild_dep\necho \"$(FORCE_REBUILD)\" | grep -qw \"$$(basename \"$1\")\"\nendef\nendif\nendif\n\nifneq ($(SKIP_DEPS),)\ndeps::\nelse\nALL_DEPS_DIRS_TO_BUILD = $(if $(filter-out $(DEPS_DIR)/elixir,$(ALL_DEPS_DIRS)),$(filter-out $(DEPS_DIR)/elixir,$(ALL_DEPS_DIRS)),$(ALL_DEPS_DIRS))\n\ndeps:: $(ALL_DEPS_DIRS_TO_BUILD) apps clean-tmp-deps.log | $(ERLANG_MK_TMP)\nifneq ($(ALL_DEPS_DIRS_TO_BUILD),)\n\t$(verbose) set -e; for dep in $(ALL_DEPS_DIRS_TO_BUILD); do \\\n\t\tif grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/deps.log; then \\\n\t\t\t:; \\\n\t\telse \\\n\t\t\techo $$dep >> $(ERLANG_MK_TMP)/deps.log; \\\n\t\t\tif [ -z \"$(strip $(FULL))\" ] $(if $(force_rebuild_dep),&& ! ($(call force_rebuild_dep,$$dep)),) && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \\\n\t\t\t\t:; \\\n\t\t\telif [ \"$$dep\" = \"$(DEPS_DIR)/hut\" -a \"$(HUT_PATCH)\" ]; then \\\n\t\t\t\t$(MAKE) -C $$dep app IS_DEP=1; \\\n\t\t\t\tif [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \\\n\t\t\telif [ -f $$dep/GNUmakefile ] || [ -f $$dep/makefile ] || [ -f $$dep/Makefile ]; then \\\n\t\t\t\t$(MAKE) -C $$dep IS_DEP=1; \\\n\t\t\t\tif [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \\\n\t\t\telse \\\n\t\t\t\techo \"Error: No Makefile to build dependency $$dep.\" >&2; \\\n\t\t\t\texit 2; \\\n\t\t\tfi \\\n\t\tfi \\\n\tdone\nendif\nendif\n\n# Deps related targets.\n\nautopatch_verbose_0 = @echo \" PATCH \" $(subst autopatch-,,$@) \"(method: $(AUTOPATCH_METHOD))\";\nautopatch_verbose_2 = set -x;\nautopatch_verbose = $(autopatch_verbose_$(V))\n\ndefine dep_autopatch_detect\n\tif [ -f $(DEPS_DIR)/$1/erlang.mk ]; then \\\n\t\techo erlang.mk; \\\n\telif [ -f $(DEPS_DIR)/$1/mix.exs -a -d $(DEPS_DIR)/$1/lib ]; then \\\n\t\tif [ \"$(ELIXIR)\" != \"disable\" ]; then \\\n\t\t\techo mix; \\\n\t\telif [ -f $(DEPS_DIR)/$1/rebar.lock -o -f $(DEPS_DIR)/$1/rebar.config ]; then \\\n\t\t\techo rebar3; \\\n\t\telif [ -f $(DEPS_DIR)/$1/Makefile ]; then \\\n\t\t\techo noop; \\\n\t\telse \\\n\t\t\texit 99; \\\n\t\tfi \\\n\telif [ -f $(DEPS_DIR)/$1/Makefile ]; then \\\n\t\tif [ -f $(DEPS_DIR)/$1/rebar.lock ]; then \\\n\t\t\techo rebar3; \\\n\t\telif [ 0 != \\`grep -c \"include ../\\w*\\.mk\" $(DEPS_DIR)/$1/Makefile\\` ]; then \\\n\t\t\techo rebar3; \\\n\t\telif [ 0 != \\`grep -ci \"^[^#].*rebar\" $(DEPS_DIR)/$1/Makefile\\` ]; then \\\n\t\t\techo rebar3; \\\n\t\telif [ -n \"\\`find $(DEPS_DIR)/$1/ -type f -name \\*.mk -not -name erlang.mk -exec grep -i \"^[^#].*rebar\" '{}' \\;\\`\" ]; then \\\n\t\t\techo rebar3; \\\n\t\telse \\\n\t\t\techo noop; \\\n\t\tfi \\\n\telif [ ! -d $(DEPS_DIR)/$1/src/ ]; then \\\n\t\techo noop; \\\n\telse \\\n\t\techo rebar3; \\\n\tfi\nendef\n\ndefine dep_autopatch_for_erlang.mk\n\trm -rf $(DEPS_DIR)/$1/ebin/; \\\n\t$(call erlang,$(call dep_autopatch_appsrc.erl,$1)); \\\n\t$(call dep_autopatch_erlang_mk,$1)\nendef\n\ndefine dep_autopatch_for_rebar3\n\t! test -f $(DEPS_DIR)/$1/ebin/$1.app || \\\n\tmv -n $(DEPS_DIR)/$1/ebin/$1.app $(DEPS_DIR)/$1/src/$1.app.src; \\\n\trm -f $(DEPS_DIR)/$1/ebin/$1.app; \\\n\tif [ -f $(DEPS_DIR)/$1/src/$1.app.src.script ]; then \\\n\t\t$(call erlang,$(call dep_autopatch_appsrc_script.erl,$1)); \\\n\tfi; \\\n\t$(call erlang,$(call dep_autopatch_appsrc.erl,$1)); \\\n\tif [ -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 \\\n\t\t$(call dep_autopatch_fetch_rebar); \\\n\t\t$(call dep_autopatch_rebar,$1); \\\n\telse \\\n\t\t$(call dep_autopatch_gen,$1); \\\n\tfi\nendef\n\ndefine dep_autopatch_for_mix\n\t$(call dep_autopatch_mix,$1)\nendef\n\ndefine dep_autopatch_for_noop\n\ttest -f $(DEPS_DIR)/$1/Makefile || printf \"noop:\\n\" > $(DEPS_DIR)/$1/Makefile\nendef\n\ndefine maybe_flock\n\tif command -v flock >/dev/null; then \\\n\t\tflock $1 sh -c \"$2\"; \\\n\telif command -v lockf >/dev/null; then \\\n\t\tlockf $1 sh -c \"$2\"; \\\n\telse \\\n\t\t$2; \\\n\tfi\nendef\n\n# Replace \"include erlang.mk\" with a line that will load the parent Erlang.mk\n# if given. Do it for all 3 possible Makefile file names.\nifeq ($(NO_AUTOPATCH_ERLANG_MK),)\ndefine dep_autopatch_erlang_mk\n\tfor f in Makefile makefile GNUmakefile; do \\\n\t\tif [ -f $(DEPS_DIR)/$1/$$f ]; then \\\n\t\t\tsed -i.bak s/'include *erlang.mk'/'include $$(if $$(ERLANG_MK_FILENAME),$$(ERLANG_MK_FILENAME),erlang.mk)'/ $(DEPS_DIR)/$1/$$f; \\\n\t\tfi \\\n\tdone\nendef\nelse\ndefine dep_autopatch_erlang_mk\n\t:\nendef\nendif\n\ndefine dep_autopatch_gen\n\tprintf \"%s\\n\" \\\n\t\t\"ERLC_OPTS = +debug_info\" \\\n\t\t\"include ../../erlang.mk\" > $(DEPS_DIR)/$1/Makefile\nendef\n\n# We use flock/lockf when available to avoid concurrency issues.\ndefine dep_autopatch_fetch_rebar\n\t$(call maybe_flock,$(ERLANG_MK_TMP)/rebar.lock,$(call dep_autopatch_fetch_rebar2))\nendef\n\ndefine dep_autopatch_fetch_rebar2\n\tif [ ! -d $(ERLANG_MK_TMP)/rebar3 ]; then \\\n\t\tgit clone -q -n -- $(REBAR3_GIT) $(ERLANG_MK_TMP)/rebar3; \\\n\t\tcd $(ERLANG_MK_TMP)/rebar3; \\\n\t\tgit checkout -q $(REBAR3_COMMIT); \\\n\t\t./bootstrap; \\\n\t\tcd -; \\\n\tfi\nendef\n\ndefine dep_autopatch_rebar\n\tif [ -f $(DEPS_DIR)/$1/Makefile ]; then \\\n\t\tmv $(DEPS_DIR)/$1/Makefile $(DEPS_DIR)/$1/Makefile.orig.mk; \\\n\tfi; \\\n\t$(call erlang,$(call dep_autopatch_rebar.erl,$1)); \\\n\trm -f $(DEPS_DIR)/$1/ebin/$1.app\nendef\n\ndefine dep_autopatch_rebar.erl\n\tapplication:load(rebar),\n\tapplication:set_env(rebar, log_level, debug),\n\t{module, rebar3} = c:l(rebar3),\n\tConf1 = case file:consult(\"$(call core_native_path,$(DEPS_DIR)/$1/rebar.config)\") of\n\t\t{ok, Conf0} -> Conf0;\n\t\t_ -> []\n\tend,\n\t{Conf, OsEnv} = fun() ->\n\t\tcase filelib:is_file(\"$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)\") of\n\t\t\tfalse -> {Conf1, []};\n\t\t\ttrue ->\n\t\t\t\tBindings0 = erl_eval:new_bindings(),\n\t\t\t\tBindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0),\n\t\t\t\tBindings = erl_eval:add_binding('SCRIPT', \"$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)\", Bindings1),\n\t\t\t\tBefore = os:getenv(),\n\t\t\t\t{ok, Conf2} = file:script(\"$(call core_native_path,$(DEPS_DIR)/$1/rebar.config.script)\", Bindings),\n\t\t\t\t{Conf2, lists:foldl(fun(E, Acc) -> lists:delete(E, Acc) end, os:getenv(), Before)}\n\t\tend\n\tend(),\n\tWrite = fun (Text) ->\n\t\tfile:write_file(\"$(call core_native_path,$(DEPS_DIR)/$1/Makefile)\", Text, [append])\n\tend,\n\tEscape = fun (Text) ->\n\t\tre:replace(Text, \"\\\\\\\\$$\", \"\\$$$$\", [global, {return, list}])\n\tend,\n\tWrite(\"IGNORE_DEPS += edown eper eunit_formatters meck node_package \"\n\t\t\"rebar_lock_deps_plugin rebar_vsn_plugin reltool_util\\n\"),\n\tWrite(\"C_SRC_DIR = /path/do/not/exist\\n\"),\n\tWrite(\"C_SRC_TYPE = rebar\\n\"),\n\tWrite(\"DRV_CFLAGS = -fPIC\\nexport DRV_CFLAGS\\n\"),\n\tWrite([\"ERLANG_ARCH = \", rebar_utils:wordsize(), \"\\nexport ERLANG_ARCH\\n\"]),\n\tToList = fun\n\t\t(V) when is_atom(V) -> atom_to_list(V);\n\t\t(V) when is_list(V) -> \"'\\\\\"\" ++ V ++ \"\\\\\"'\"\n\tend,\n\tfun() ->\n\t\tWrite(\"ERLC_OPTS = +debug_info\\n\"),\n\t\tcase lists:keyfind(erl_opts, 1, Conf) of\n\t\t\tfalse -> ok;\n\t\t\t{_, ErlOpts} ->\n\t\t\t\tlists:foreach(fun\n\t\t\t\t\t({d, D}) ->\n\t\t\t\t\t\tWrite(\"ERLC_OPTS += -D\" ++ ToList(D) ++ \"=1\\n\");\n\t\t\t\t\t({d, DKey, DVal}) ->\n\t\t\t\t\t\tWrite(\"ERLC_OPTS += -D\" ++ ToList(DKey) ++ \"=\" ++ ToList(DVal) ++ \"\\n\");\n\t\t\t\t\t({i, I}) ->\n\t\t\t\t\t\tWrite([\"ERLC_OPTS += -I \", I, \"\\n\"]);\n\t\t\t\t\t({platform_define, Regex, D}) ->\n\t\t\t\t\t\tcase rebar_utils:is_arch(Regex) of\n\t\t\t\t\t\t\ttrue -> Write(\"ERLC_OPTS += -D\" ++ ToList(D) ++ \"=1\\n\");\n\t\t\t\t\t\t\tfalse -> ok\n\t\t\t\t\t\tend;\n\t\t\t\t\t({parse_transform, PT}) ->\n\t\t\t\t\t\tWrite(\"ERLC_OPTS += +'{parse_transform, \" ++ ToList(PT) ++ \"}'\\n\");\n\t\t\t\t\t(_) -> ok\n\t\t\t\tend, ErlOpts)\n\t\tend,\n\t\tWrite(\"\\n\")\n\tend(),\n\tGetHexVsn2 = fun(N, NP) ->\n\t\tcase file:consult(\"$(call core_native_path,$(DEPS_DIR)/$1/rebar.lock)\") of\n\t\t\t{ok, Lock} ->\n\t\t\t\tLockPkgs = case lists:keyfind(\"1.2.0\", 1, Lock) of\n\t\t\t\t\t{_, LP} ->\n\t\t\t\t\t\tLP;\n\t\t\t\t\t_ ->\n\t\t\t\t\t\tcase lists:keyfind(\"1.1.0\", 1, Lock) of\n\t\t\t\t\t\t\t{_, LP} ->\n\t\t\t\t\t\t\t\tLP;\n\t\t\t\t\t\t\t_ ->\n\t\t\t\t\t\t\t\tfalse\n\t\t\t\t\t\tend\n\t\t\t\tend,\n\t\t\t\tif\n\t\t\t\t\tis_list(LockPkgs) ->\n\t\t\t\t\t\tcase lists:keyfind(atom_to_binary(N, latin1), 1, LockPkgs) of\n\t\t\t\t\t\t\t{_, {pkg, _, Vsn}, _} ->\n\t\t\t\t\t\t\t\t{N, {hex, NP, binary_to_list(Vsn)}};\n\t\t\t\t\t\t\t_ ->\n\t\t\t\t\t\t\t\tfalse\n\t\t\t\t\t\tend;\n\t\t\t\t\ttrue ->\n\t\t\t\t\t\tfalse\n\t\t\t\tend;\n\t\t\t_ ->\n\t\t\t\tfalse\n\t\tend\n\tend,\n\tGetHexVsn3Common = fun(N, NP, S0) ->\n\t\tcase GetHexVsn2(N, NP) of\n\t\t\tfalse ->\n\t\t\t\tS2 = case S0 of\n\t\t\t\t\t\" \" ++ S1 -> S1;\n\t\t\t\t\t_ -> S0\n\t\t\t\tend,\n\t\t\t\tS = case length([ok || $$. <- S2]) of\n\t\t\t\t\t0 -> S2 ++ \".0.0\";\n\t\t\t\t\t1 -> S2 ++ \".0\";\n\t\t\t\t\t_ -> S2\n\t\t\t\tend,\n\t\t\t\t{N, {hex, NP, S}};\n\t\t\tNameSource ->\n\t\t\t\tNameSource\n\t\tend\n\tend,\n\tGetHexVsn3 = fun\n\t\t(N, NP, \"~>\" ++ S0) ->\n\t\t\tGetHexVsn3Common(N, NP, S0);\n\t\t(N, NP, \">=\" ++ S0) ->\n\t\t\tGetHexVsn3Common(N, NP, S0);\n\t\t(N, NP, S) -> {N, {hex, NP, S}}\n\tend,\n\tConvertCommit = fun\n\t\t({branch, C}) -> C;\n\t\t({ref, C}) -> C;\n\t\t({tag, C}) -> C;\n\t\t(C) -> C\n\tend,\n\tfun() ->\n\t\tFile = case lists:keyfind(deps, 1, Conf) of\n\t\t\tfalse -> [];\n\t\t\t{_, Deps} ->\n\t\t\t\t[begin case case Dep of\n\t\t\t\t\t\t\tN when is_atom(N) -> GetHexVsn2(N, N);\n\t\t\t\t\t\t\t{N, S} when is_atom(N), is_list(S) -> GetHexVsn3(N, N, S);\n\t\t\t\t\t\t\t{N, {pkg, NP}} when is_atom(N) -> GetHexVsn2(N, NP);\n\t\t\t\t\t\t\t{N, S, {pkg, NP}} -> GetHexVsn3(N, NP, S);\n\t\t\t\t\t\t\t{N, S} when is_tuple(S) -> {N, S};\n\t\t\t\t\t\t\t{N, _, S} -> {N, S};\n\t\t\t\t\t\t\t{N, _, S, _} -> {N, S};\n\t\t\t\t\t\t\t_ -> false\n\t\t\t\t\t\tend of\n\t\t\t\t\tfalse -> ok;\n\t\t\t\t\t{Name, {git_subdir, Repo, Commit, SubDir}} ->\n\t\t\t\t\t\tWrite(io_lib:format(\"DEPS += ~s\\ndep_~s = git-subfolder ~s ~s ~s~n\", [Name, Name, Repo, ConvertCommit(Commit), SubDir]));\n\t\t\t\t\t{Name, Source} ->\n\t\t\t\t\t\t{Method, Repo, Commit} = case Source of\n\t\t\t\t\t\t\t{hex, NPV, V} -> {hex, V, NPV};\n\t\t\t\t\t\t\t{git, R} -> {git, R, master};\n\t\t\t\t\t\t\t{M, R, C} -> {M, R, C}\n\t\t\t\t\t\tend,\n\t\t\t\t\t\tWrite(io_lib:format(\"DEPS += ~s\\ndep_~s = ~s ~s ~s~n\", [Name, Name, Method, Repo, ConvertCommit(Commit)]))\n\t\t\t\tend end || Dep <- Deps]\n\t\tend\n\tend(),\n\tfun() ->\n\t\tcase lists:keyfind(erl_first_files, 1, Conf) of\n\t\t\tfalse -> ok;\n\t\t\t{_, Files0} ->\n\t\t\t\tFiles = [begin\n\t\t\t\t\thd(filelib:wildcard(\"$(call core_native_path,$(DEPS_DIR)/$1/src/)**/\" ++ filename:rootname(F) ++ \".*rl\"))\n\t\t\t\tend || \"src/\" ++ F <- Files0],\n\t\t\t\tNames = [[\" \", case lists:reverse(F) of\n\t\t\t\t\t\"lre.\" ++ Elif -> lists:reverse(Elif);\n\t\t\t\t\t\"lrx.\" ++ Elif -> lists:reverse(Elif);\n\t\t\t\t\t\"lry.\" ++ Elif -> lists:reverse(Elif);\n\t\t\t\t\tElif -> lists:reverse(Elif)\n\t\t\t\tend] || \"$(call core_native_path,$(DEPS_DIR)/$1/src/)\" ++ F <- Files],\n\t\t\t\tWrite(io_lib:format(\"COMPILE_FIRST +=~s\\n\", [Names]))\n\t\tend\n\tend(),\n\tWrite(\"\\n\\nrebar_dep: preprocess pre-deps deps pre-app app post-app\\n\"),\n\tWrite(\"\\npreprocess::\\n\"),\n\tWrite(\"\\npre-deps::\\n\"),\n\tWrite(\"\\npre-app::\\n\"),\n\tWrite(\"\\npost-app::\\n\"),\n\tPatchHook = fun(Cmd) ->\n\t\tCmd2 = re:replace(Cmd, \"^([g]?make)(.*)( -C.*)\", \"\\\\\\\\1\\\\\\\\3\\\\\\\\2\", [{return, list}]),\n\t\tcase Cmd2 of\n\t\t\t\"make -C\" ++ Cmd1 -> \"$$\\(MAKE) -C\" ++ Escape(Cmd1);\n\t\t\t\"gmake -C\" ++ Cmd1 -> \"$$\\(MAKE) -C\" ++ Escape(Cmd1);\n\t\t\t\"make \" ++ Cmd1 -> \"$$\\(MAKE) -f Makefile.orig.mk \" ++ Escape(Cmd1);\n\t\t\t\"gmake \" ++ Cmd1 -> \"$$\\(MAKE) -f Makefile.orig.mk \" ++ Escape(Cmd1);\n\t\t\t_ -> Escape(Cmd)\n\t\tend\n\tend,\n\tfun() ->\n\t\tcase lists:keyfind(pre_hooks, 1, Conf) of\n\t\t\tfalse -> ok;\n\t\t\t{_, Hooks} ->\n\t\t\t\t[case H of\n\t\t\t\t\t{'get-deps', Cmd} ->\n\t\t\t\t\t\tWrite(\"\\npre-deps::\\n\\t\" ++ PatchHook(Cmd) ++ \"\\n\");\n\t\t\t\t\t{compile, Cmd} ->\n\t\t\t\t\t\tWrite(\"\\npre-app::\\n\\tCC=$$\\(CC) \" ++ PatchHook(Cmd) ++ \"\\n\");\n\t\t\t\t\t{{pc, compile}, Cmd} ->\n\t\t\t\t\t\tWrite(\"\\npre-app::\\n\\tCC=$$\\(CC) \" ++ PatchHook(Cmd) ++ \"\\n\");\n\t\t\t\t\t{Regex, compile, Cmd} ->\n\t\t\t\t\t\tcase rebar_utils:is_arch(Regex) of\n\t\t\t\t\t\t\ttrue -> Write(\"\\npre-app::\\n\\tCC=$$\\(CC) \" ++ PatchHook(Cmd) ++ \"\\n\");\n\t\t\t\t\t\t\tfalse -> ok\n\t\t\t\t\t\tend;\n\t\t\t\t\t_ -> ok\n\t\t\t\tend || H <- Hooks]\n\t\tend\n\tend(),\n\tfun() ->\n\t\tcase lists:keyfind(post_hooks, 1, Conf) of\n\t\t\tfalse -> ok;\n\t\t\t{_, Hooks} ->\n\t\t\t\t[case H of\n\t\t\t\t\t{compile, Cmd} ->\n\t\t\t\t\t\tWrite(\"\\npost-app::\\n\\tCC=$$\\(CC) \" ++ PatchHook(Cmd) ++ \"\\n\");\n\t\t\t\t\t{{pc, compile}, Cmd} ->\n\t\t\t\t\t\tWrite(\"\\npost-app::\\n\\tCC=$$\\(CC) \" ++ PatchHook(Cmd) ++ \"\\n\");\n\t\t\t\t\t{Regex, compile, Cmd} ->\n\t\t\t\t\t\tcase rebar_utils:is_arch(Regex) of\n\t\t\t\t\t\t\ttrue -> Write(\"\\npost-app::\\n\\tCC=$$\\(CC) \" ++ PatchHook(Cmd) ++ \"\\n\");\n\t\t\t\t\t\t\tfalse -> ok\n\t\t\t\t\t\tend;\n\t\t\t\t\t_ -> ok\n\t\t\t\tend || H <- Hooks]\n\t\tend\n\tend(),\n\tShellToMk = fun(V0) ->\n\t\tV1 = re:replace(V0, \"[$$][(]\", \"$$\\(shell \", [global]),\n\t\tV = re:replace(V1, \"([$$])(?![(])(\\\\\\\\w*)\", \"\\\\\\\\1(\\\\\\\\2)\", [global]),\n\t\tre:replace(V, \"-Werror\\\\\\\\b\", \"\", [{return, list}, global])\n\tend,\n\tPortSpecs = fun() ->\n\t\tcase lists:keyfind(port_specs, 1, Conf) of\n\t\t\tfalse ->\n\t\t\t\tcase filelib:is_dir(\"$(call core_native_path,$(DEPS_DIR)/$1/c_src)\") of\n\t\t\t\t\tfalse -> [];\n\t\t\t\t\ttrue ->\n\t\t\t\t\t\t[{\"priv/\" ++ proplists:get_value(so_name, Conf, \"$(1)_drv.so\"),\n\t\t\t\t\t\t\tproplists:get_value(port_sources, Conf, [\"c_src/*.c\"]), []}]\n\t\t\t\tend;\n\t\t\t{_, Specs} ->\n\t\t\t\tlists:flatten([case S of\n\t\t\t\t\t{Output, Input} -> {ShellToMk(Output), Input, []};\n\t\t\t\t\t{Regex, Output, Input} ->\n\t\t\t\t\t\tcase rebar_utils:is_arch(Regex) of\n\t\t\t\t\t\t\ttrue -> {ShellToMk(Output), Input, []};\n\t\t\t\t\t\t\tfalse -> []\n\t\t\t\t\t\tend;\n\t\t\t\t\t{Regex, Output, Input, [{env, Env}]} ->\n\t\t\t\t\t\tcase rebar_utils:is_arch(Regex) of\n\t\t\t\t\t\t\ttrue -> {ShellToMk(Output), Input, Env};\n\t\t\t\t\t\t\tfalse -> []\n\t\t\t\t\t\tend\n\t\t\t\tend || S <- Specs])\n\t\tend\n\tend(),\n\tPortSpecWrite = fun (Text) ->\n\t\tfile:write_file(\"$(call core_native_path,$(DEPS_DIR)/$1/c_src/Makefile.erlang.mk)\", Text, [append])\n\tend,\n\tcase PortSpecs of\n\t\t[] -> ok;\n\t\t_ ->\n\t\t\tWrite(\"\\npre-app::\\n\\t@$$\\(MAKE) --no-print-directory -f c_src/Makefile.erlang.mk\\n\"),\n\t\t\tPortSpecWrite(io_lib:format(\"ERL_CFLAGS ?= -finline-functions -Wall -fPIC -I \\\\\"~s/erts-~s/include\\\\\" -I \\\\\"~s\\\\\"\\n\",\n\t\t\t\t[code:root_dir(), erlang:system_info(version), code:lib_dir(erl_interface, include)])),\n\t\t\tPortSpecWrite(io_lib:format(\"ERL_LDFLAGS ?= -L \\\\\"~s\\\\\" -lei\\n\",\n\t\t\t\t[code:lib_dir(erl_interface, lib)])),\n\t\t\t[PortSpecWrite([\"\\n\", E, \"\\n\"]) || E <- OsEnv],\n\t\t\tFilterEnv = fun(Env) ->\n\t\t\t\tlists:flatten([case E of\n\t\t\t\t\t{_, _} -> E;\n\t\t\t\t\t{Regex, K, V} ->\n\t\t\t\t\t\tcase rebar_utils:is_arch(Regex) of\n\t\t\t\t\t\t\ttrue -> {K, V};\n\t\t\t\t\t\t\tfalse -> []\n\t\t\t\t\t\tend\n\t\t\t\tend || E <- Env])\n\t\t\tend,\n\t\t\tMergeEnv = fun(Env) ->\n\t\t\t\tlists:foldl(fun ({K, V}, Acc) ->\n\t\t\t\t\tcase lists:keyfind(K, 1, Acc) of\n\t\t\t\t\t\tfalse -> [{K, rebar_utils:expand_env_variable(V, K, \"\")}|Acc];\n\t\t\t\t\t\t{_, V0} -> [{K, rebar_utils:expand_env_variable(V, K, V0)}|Acc]\n\t\t\t\t\tend\n\t\t\t\tend, [], Env)\n\t\t\tend,\n\t\t\tPortEnv = case lists:keyfind(port_env, 1, Conf) of\n\t\t\t\tfalse -> [];\n\t\t\t\t{_, PortEnv0} -> FilterEnv(PortEnv0)\n\t\t\tend,\n\t\t\tPortSpec = fun ({Output, Input0, Env}) ->\n\t\t\t\tfilelib:ensure_dir(\"$(call core_native_path,$(DEPS_DIR)/$1/)\" ++ Output),\n\t\t\t\tInput = [[\" \", I] || I <- Input0],\n\t\t\t\tPortSpecWrite([\n\t\t\t\t\t[[\"\\n\", K, \" = \", ShellToMk(V)] || {K, V} <- lists:reverse(MergeEnv(PortEnv))],\n\t\t\t\t\tcase $(PLATFORM) of\n\t\t\t\t\t\tdarwin -> \"\\n\\nLDFLAGS += -flat_namespace -undefined suppress\";\n\t\t\t\t\t\t_ -> \"\"\n\t\t\t\t\tend,\n\t\t\t\t\t\"\\n\\nall:: \", Output, \"\\n\\t@:\\n\\n\",\n\t\t\t\t\t\"%.o: %.c\\n\\t$$\\(CC) -c -o $$\\@ $$\\< $$\\(CFLAGS) $$\\(ERL_CFLAGS) $$\\(DRV_CFLAGS) $$\\(EXE_CFLAGS)\\n\\n\",\n\t\t\t\t\t\"%.o: %.C\\n\\t$$\\(CXX) -c -o $$\\@ $$\\< $$\\(CXXFLAGS) $$\\(ERL_CFLAGS) $$\\(DRV_CFLAGS) $$\\(EXE_CFLAGS)\\n\\n\",\n\t\t\t\t\t\"%.o: %.cc\\n\\t$$\\(CXX) -c -o $$\\@ $$\\< $$\\(CXXFLAGS) $$\\(ERL_CFLAGS) $$\\(DRV_CFLAGS) $$\\(EXE_CFLAGS)\\n\\n\",\n\t\t\t\t\t\"%.o: %.cpp\\n\\t$$\\(CXX) -c -o $$\\@ $$\\< $$\\(CXXFLAGS) $$\\(ERL_CFLAGS) $$\\(DRV_CFLAGS) $$\\(EXE_CFLAGS)\\n\\n\",\n\t\t\t\t\t[[Output, \": \", K, \" += \", ShellToMk(V), \"\\n\"] || {K, V} <- lists:reverse(MergeEnv(FilterEnv(Env)))],\n\t\t\t\t\tOutput, \": $$\\(foreach ext,.c .C .cc .cpp,\",\n\t\t\t\t\t\t\"$$\\(patsubst %$$\\(ext),%.o,$$\\(filter %$$\\(ext),$$\\(wildcard\", Input, \"))))\\n\",\n\t\t\t\t\t\"\\t$$\\(CC) -o $$\\@ $$\\? $$\\(LDFLAGS) $$\\(ERL_LDFLAGS) $$\\(DRV_LDFLAGS) $$\\(LDLIBS) $$\\(EXE_LDFLAGS)\",\n\t\t\t\t\tcase {filename:extension(Output), $(PLATFORM)} of\n\t\t\t\t\t    {[], _} -> \"\\n\";\n\t\t\t\t\t    {\".so\", darwin} -> \" -shared\\n\";\n\t\t\t\t\t    {\".dylib\", darwin} -> \" -shared\\n\";\n\t\t\t\t\t    {_, darwin} -> \"\\n\";\n\t\t\t\t\t    _ -> \" -shared\\n\"\n\t\t\t\t\tend])\n\t\t\tend,\n\t\t\t[PortSpec(S) || S <- PortSpecs]\n\tend,\n\tfun() ->\n\t\tcase lists:keyfind(plugins, 1, Conf) of\n\t\t\tfalse -> ok;\n\t\t\t{_, Plugins0} ->\n\t\t\t\tPlugins = [P || P <- Plugins0, is_tuple(P)],\n\t\t\t\tcase lists:keyfind('lfe-compile', 1, Plugins) of\n\t\t\t\t\tfalse -> ok;\n\t\t\t\t\t_ -> Write(\"\\nBUILD_DEPS = lfe lfe.mk\\ndep_lfe.mk = git https://github.com/ninenines/lfe.mk master\\nDEP_PLUGINS = lfe.mk\\n\")\n\t\t\t\tend\n\t\tend\n\tend(),\n\tWrite(\"\\ninclude $$\\(if $$\\(ERLANG_MK_FILENAME),$$\\(ERLANG_MK_FILENAME),erlang.mk)\"),\n\tRunPlugin = fun(Plugin, Step) ->\n\t\tcase erlang:function_exported(Plugin, Step, 2) of\n\t\t\tfalse -> ok;\n\t\t\ttrue ->\n\t\t\t\tc:cd(\"$(call core_native_path,$(DEPS_DIR)/$1/)\"),\n\t\t\t\tRet = Plugin:Step({config, \"\", Conf, dict:new(), dict:new(), dict:new(),\n\t\t\t\t\tdict:store(base_dir, \"\", dict:new())}, undefined),\n\t\t\t\tio:format(\"rebar plugin ~p step ~p ret ~p~n\", [Plugin, Step, Ret])\n\t\tend\n\tend,\n\tfun() ->\n\t\tcase lists:keyfind(plugins, 1, Conf) of\n\t\t\tfalse -> ok;\n\t\t\t{_, Plugins0} ->\n\t\t\t\tPlugins = [P || P <- Plugins0, is_atom(P)],\n\t\t\t\t[begin\n\t\t\t\t\tcase lists:keyfind(deps, 1, Conf) of\n\t\t\t\t\t\tfalse -> ok;\n\t\t\t\t\t\t{_, Deps} ->\n\t\t\t\t\t\t\tcase lists:keyfind(P, 1, Deps) of\n\t\t\t\t\t\t\t\tfalse -> ok;\n\t\t\t\t\t\t\t\t_ ->\n\t\t\t\t\t\t\t\t\tPath = \"$(call core_native_path,$(DEPS_DIR)/)\" ++ atom_to_list(P),\n\t\t\t\t\t\t\t\t\tio:format(\"~s\", [os:cmd(\"$(MAKE) -C $(call core_native_path,$(DEPS_DIR)/$1) \" ++ Path)]),\n\t\t\t\t\t\t\t\t\tio:format(\"~s\", [os:cmd(\"$(MAKE) -C \" ++ Path ++ \" IS_DEP=1\")]),\n\t\t\t\t\t\t\t\t\tcode:add_patha(Path ++ \"/ebin\")\n\t\t\t\t\t\t\tend\n\t\t\t\t\tend\n\t\t\t\tend || P <- Plugins],\n\t\t\t\t[case code:load_file(P) of\n\t\t\t\t\t{module, P} -> ok;\n\t\t\t\t\t_ ->\n\t\t\t\t\t\tcase lists:keyfind(plugin_dir, 1, Conf) of\n\t\t\t\t\t\t\tfalse -> ok;\n\t\t\t\t\t\t\t{_, PluginsDir} ->\n\t\t\t\t\t\t\t\tErlFile = \"$(call core_native_path,$(DEPS_DIR)/$1/)\" ++ PluginsDir ++ \"/\" ++ atom_to_list(P) ++ \".erl\",\n\t\t\t\t\t\t\t\t{ok, P, Bin} = compile:file(ErlFile, [binary]),\n\t\t\t\t\t\t\t\t{module, P} = code:load_binary(P, ErlFile, Bin)\n\t\t\t\t\t\tend\n\t\t\t\tend || P <- Plugins],\n\t\t\t\t[RunPlugin(P, preprocess) || P <- Plugins],\n\t\t\t\t[RunPlugin(P, pre_compile) || P <- Plugins],\n\t\t\t\t[RunPlugin(P, compile) || P <- Plugins]\n\t\tend\n\tend(),\n\thalt()\nendef\n\ndefine dep_autopatch_appsrc_script.erl\n\tAppSrc = \"$(call core_native_path,$(DEPS_DIR)/$1/src/$1.app.src)\",\n\tAppSrcScript = AppSrc ++ \".script\",\n\tConf1 = case file:consult(AppSrc) of\n\t\t{ok, Conf0} -> Conf0;\n\t\t{error, enoent} -> []\n\tend,\n\tBindings0 = erl_eval:new_bindings(),\n\tBindings1 = erl_eval:add_binding('CONFIG', Conf1, Bindings0),\n\tBindings = erl_eval:add_binding('SCRIPT', AppSrcScript, Bindings1),\n\tConf = case file:script(AppSrcScript, Bindings) of\n\t\t{ok, [C]} -> C;\n\t\t{ok, C} -> C\n\tend,\n\tok = file:write_file(AppSrc, io_lib:format(\"~p.~n\", [Conf])),\n\thalt()\nendef\n\ndefine dep_autopatch_appsrc.erl\n\tAppSrcOut = \"$(call core_native_path,$(DEPS_DIR)/$1/src/$1.app.src)\",\n\tAppSrcIn = case filelib:is_regular(AppSrcOut) of false -> \"$(call core_native_path,$(DEPS_DIR)/$1/ebin/$1.app)\"; true -> AppSrcOut end,\n\tcase filelib:is_regular(AppSrcIn) of\n\t\tfalse -> ok;\n\t\ttrue ->\n\t\t\t{ok, [{application, $1, L0}]} = file:consult(AppSrcIn),\n\t\t\tL1 = lists:keystore(modules, 1, L0, {modules, []}),\n\t\t\tL2 = case lists:keyfind(vsn, 1, L1) of\n\t\t\t\t{_, git} -> lists:keyreplace(vsn, 1, L1, {vsn, lists:droplast(os:cmd(\"git -C $(DEPS_DIR)/$1 describe --dirty --tags --always\"))});\n\t\t\t\t{_, {cmd, _}} -> lists:keyreplace(vsn, 1, L1, {vsn, \"cmd\"});\n\t\t\t\t_ -> L1\n\t\t\tend,\n\t\t\tL3 = case lists:keyfind(registered, 1, L2) of false -> [{registered, []}|L2]; _ -> L2 end,\n\t\t\tok = file:write_file(AppSrcOut, io_lib:format(\"~p.~n\", [{application, $1, L3}])),\n\t\t\tcase AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end\n\tend,\n\thalt()\nendef\n\nifeq ($(CACHE_DEPS),1)\n\ndefine dep_cache_fetch_git\n\tmkdir -p $(CACHE_DIR)/git; \\\n\tif test -d \"$(join $(CACHE_DIR)/git/,$(call query_name,$1))\"; then \\\n\t\tcd $(join $(CACHE_DIR)/git/,$(call query_name,$1)); \\\n\t\tif ! git checkout -q $(call query_version,$1); then \\\n\t\t\tgit remote set-url origin $(call query_repo_git,$1) && \\\n\t\t\tgit pull --all && \\\n\t\t\tgit cat-file -e $(call query_version_git,$1) 2>/dev/null; \\\n\t\tfi; \\\n\telse \\\n\t\tgit clone -q -n -- $(call query_repo_git,$1) $(join $(CACHE_DIR)/git/,$(call query_name,$1)); \\\n\tfi; \\\n\tgit clone -q --single-branch -- $(join $(CACHE_DIR)/git/,$(call query_name,$1)) $2; \\\n\tcd $2 && git checkout -q $(call query_version_git,$1)\nendef\n\ndefine dep_fetch_git\n\t$(call dep_cache_fetch_git,$1,$(DEPS_DIR)/$(call query_name,$1));\nendef\n\ndefine dep_fetch_git-subfolder\n\tmkdir -p $(ERLANG_MK_TMP)/git-subfolder; \\\n\t$(call dep_cache_fetch_git,$1,$(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1)); \\\n\tln -s $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1)/$(word 4,$(dep_$1)) \\\n\t\t$(DEPS_DIR)/$(call query_name,$1);\nendef\n\nelse\n\ndefine dep_fetch_git\n\tgit clone -q -n -- $(call query_repo_git,$1) $(DEPS_DIR)/$(call query_name,$1); \\\n\tcd $(DEPS_DIR)/$(call query_name,$1) && git checkout -q $(call query_version_git,$1);\nendef\n\ndefine dep_fetch_git-subfolder\n\tmkdir -p $(ERLANG_MK_TMP)/git-subfolder; \\\n\tgit clone -q -n -- $(call query_repo_git-subfolder,$1) \\\n\t\t$(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1); \\\n\tcd $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1) \\\n\t\t&& git checkout -q $(call query_version_git-subfolder,$1); \\\n\tln -s $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1)/$(word 4,$(dep_$1)) \\\n\t\t$(DEPS_DIR)/$(call query_name,$1);\nendef\n\nendif\n\ndefine dep_fetch_git-submodule\n\tgit submodule update --init -- $(DEPS_DIR)/$1;\nendef\n\ndefine dep_fetch_hg\n\thg clone -q -U $(call query_repo_hg,$1) $(DEPS_DIR)/$(call query_name,$1); \\\n\tcd $(DEPS_DIR)/$(call query_name,$1) && hg update -q $(call query_version_hg,$1);\nendef\n\ndefine dep_fetch_svn\n\tsvn checkout -q $(call query_repo_svn,$1) $(DEPS_DIR)/$(call query_name,$1);\nendef\n\ndefine dep_fetch_cp\n\tcp -R $(call query_repo_cp,$1) $(DEPS_DIR)/$(call query_name,$1);\nendef\n\ndefine dep_fetch_ln\n\tln -s $(call query_repo_ln,$1) $(DEPS_DIR)/$(call query_name,$1);\nendef\n\ndefine hex_get_tarball.erl\n\t{ok, _} = application:ensure_all_started(ssl),\n\t{ok, _} = application:ensure_all_started(inets),\n\tConfig = $(hex_config.erl),\n\tcase hex_repo:get_tarball(Config, <<\"$1\">>, <<\"$(strip $2)\">>) of\n\t\t{ok, {200, _, Tarball}} ->\n\t\t\tok = file:write_file(\"$(call core_native_path,$3)\", Tarball),\n\t\t\thalt(0);\n\t\t{ok, {Status, _, Errors}} ->\n\t\t\tio:format(\"Error ~b: ~0p~n\", [Status, Errors]),\n\t\t\thalt(79)\n\tend\nendef\n\nifeq ($(CACHE_DEPS),1)\n\n# Hex only has a package version. No need to look in the Erlang.mk packages.\ndefine dep_fetch_hex\n\tmkdir -p $(CACHE_DIR)/hex $(DEPS_DIR)/$1; \\\n\t$(eval hex_pkg_name := $(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1)) \\\n\t$(eval hex_tar_name := $(hex_pkg_name)-$(strip $(word 2,$(dep_$1))).tar) \\\n\t$(if $(wildcard $(CACHE_DIR)/hex/$(hex_tar_name)),,\\\n\t\t$(call erlang,$(call hex_get_tarball.erl,$(hex_pkg_name),$(word 2,$(dep_$1)),$(CACHE_DIR)/hex/$(hex_tar_name)));) \\\n\ttar -xOf $(CACHE_DIR)/hex/$(hex_tar_name) contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -;\nendef\n\nelse\n\n# Hex only has a package version. No need to look in the Erlang.mk packages.\ndefine dep_fetch_hex\n\tmkdir -p $(ERLANG_MK_TMP)/hex $(DEPS_DIR)/$1; \\\n\t$(call erlang,$(call hex_get_tarball.erl,$(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1),$(word 2,$(dep_$1)),$(ERLANG_MK_TMP)/hex/$1.tar)); \\\n\ttar -xOf $(ERLANG_MK_TMP)/hex/$1.tar contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -;\nendef\n\nendif\n\ndefine dep_fetch_fail\n\techo \"Error: Unknown or invalid dependency: $1.\" >&2; \\\n\texit 78;\nendef\n\ndefine dep_target\n$(DEPS_DIR)/$(call query_name,$1): $(if $(filter elixir,$(BUILD_DEPS) $(DEPS)),$(if $(filter-out elixir,$1),$(DEPS_DIR)/elixir/ebin/dep_built)) $(if $(filter hex,$(call query_fetch_method,$1)),$(if $(wildcard $(DEPS_DIR)/$(call query_name,$1)),,$(DEPS_DIR)/hex_core/ebin/dep_built)) | $(ERLANG_MK_TMP)\n\t$(eval DEP_NAME := $(call query_name,$1))\n\t$(eval DEP_STR := $(if $(filter $1,$(DEP_NAME)),$1,\"$1 ($(DEP_NAME))\"))\n\t$(verbose) if test -d $(APPS_DIR)/$(DEP_NAME); then \\\n\t\techo \"Error: Dependency\" $(DEP_STR) \"conflicts with application found in $(APPS_DIR)/$(DEP_NAME).\" >&2; \\\n\t\texit 17; \\\n\tfi\n\t$(verbose) mkdir -p $(DEPS_DIR)\n\t$(dep_verbose) $(call dep_fetch_$(strip $(call query_fetch_method,$1)),$1)\n\t$(verbose) if [ -f $(DEPS_DIR)/$1/configure.ac -o -f $(DEPS_DIR)/$1/configure.in ] \\\n\t\t\t&& [ ! -f $(DEPS_DIR)/$1/configure ]; then \\\n\t\techo \" AUTO  \" $(DEP_STR); \\\n\t\tcd $(DEPS_DIR)/$1 && autoreconf -Wall -vif -I m4; \\\n\tfi\n\t- $(verbose) if [ -f $(DEPS_DIR)/$(DEP_NAME)/configure ]; then \\\n\t\techo \" CONF  \" $(DEP_STR); \\\n\t\tcd $(DEPS_DIR)/$(DEP_NAME) && ./configure; \\\n\tfi\nifeq ($(filter $1,$(NO_AUTOPATCH)),)\n\t$(verbose) AUTOPATCH_METHOD=`$(call dep_autopatch_detect,$1)`; \\\n\tif [ $$$$? -eq 99 ]; then \\\n\t\techo \"Elixir is currently disabled. Please set 'ELIXIR = system' in the Makefile to enable\"; \\\n\t\texit 99; \\\n\tfi; \\\n\t$$(MAKE) --no-print-directory autopatch-$(DEP_NAME) AUTOPATCH_METHOD=$$$$AUTOPATCH_METHOD\nendif\n\n.PHONY: autopatch-$(call query_name,$1)\n\nifeq ($1,elixir)\nautopatch-elixir::\n\t$$(verbose) ln -s lib/elixir/ebin $(DEPS_DIR)/elixir/\nelse\nautopatch-$(call query_name,$1)::\n\t$$(autopatch_verbose) $$(call dep_autopatch_for_$(AUTOPATCH_METHOD),$(call query_name,$1))\nendif\nendef\n\n# We automatically depend on hex_core when the project isn't already.\n$(if $(filter hex_core,$(DEPS) $(BUILD_DEPS) $(DOC_DEPS) $(REL_DEPS) $(TEST_DEPS)),,\\\n\t$(eval $(call dep_target,hex_core)))\n\n$(DEPS_DIR)/hex_core/ebin/dep_built: | $(ERLANG_MK_TMP)\n\t$(verbose) $(call maybe_flock,$(ERLANG_MK_TMP)/hex_core.lock,\\\n\t\tif [ ! -e $(DEPS_DIR)/hex_core/ebin/dep_built ]; then \\\n\t\t\t$(MAKE) $(DEPS_DIR)/hex_core; \\\n\t\t\t$(MAKE) -C $(DEPS_DIR)/hex_core IS_DEP=1; \\\n\t\t\ttouch $(DEPS_DIR)/hex_core/ebin/dep_built; \\\n\t\tfi)\n\n$(DEPS_DIR)/elixir/ebin/dep_built: | $(ERLANG_MK_TMP)\n\t$(verbose) $(call maybe_flock,$(ERLANG_MK_TMP)/elixir.lock,\\\n\t\tif [ ! -e $(DEPS_DIR)/elixir/ebin/dep_built ]; then \\\n\t\t\t$(MAKE) $(DEPS_DIR)/elixir; \\\n\t\t\t$(MAKE) -C $(DEPS_DIR)/elixir; \\\n\t\t\ttouch $(DEPS_DIR)/elixir/ebin/dep_built; \\\n\t\tfi)\n\n$(foreach dep,$(BUILD_DEPS) $(DEPS),$(eval $(call dep_target,$(dep))))\n\nifndef IS_APP\nclean:: clean-apps\n\nclean-apps:\n\t$(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \\\n\t\t$(MAKE) -C $$dep clean IS_APP=1; \\\n\tdone\n\ndistclean:: distclean-apps\n\ndistclean-apps:\n\t$(verbose) set -e; for dep in $(ALL_APPS_DIRS) ; do \\\n\t\t$(MAKE) -C $$dep distclean IS_APP=1; \\\n\tdone\nendif\n\nifndef SKIP_DEPS\ndistclean:: distclean-deps\n\ndistclean-deps:\n\t$(gen_verbose) rm -rf $(DEPS_DIR)\nendif\n\nifeq ($(CACHE_DEPS),1)\ncacheclean:: cacheclean-git cacheclean-hex\n\ncacheclean-git:\n\t$(gen_verbose) rm -rf $(CACHE_DIR)/git\n\ncacheclean-hex:\n\t$(gen_verbose) rm -rf $(CACHE_DIR)/hex\nendif\n\n# Forward-declare variables used in core/deps-tools.mk. This is required\n# in case plugins use them.\n\nERLANG_MK_RECURSIVE_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-deps-list.log\nERLANG_MK_RECURSIVE_DOC_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-doc-deps-list.log\nERLANG_MK_RECURSIVE_REL_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-rel-deps-list.log\nERLANG_MK_RECURSIVE_TEST_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-test-deps-list.log\nERLANG_MK_RECURSIVE_SHELL_DEPS_LIST = $(ERLANG_MK_TMP)/recursive-shell-deps-list.log\n\nERLANG_MK_QUERY_DEPS_FILE = $(ERLANG_MK_TMP)/query-deps.log\nERLANG_MK_QUERY_DOC_DEPS_FILE = $(ERLANG_MK_TMP)/query-doc-deps.log\nERLANG_MK_QUERY_REL_DEPS_FILE = $(ERLANG_MK_TMP)/query-rel-deps.log\nERLANG_MK_QUERY_TEST_DEPS_FILE = $(ERLANG_MK_TMP)/query-test-deps.log\nERLANG_MK_QUERY_SHELL_DEPS_FILE = $(ERLANG_MK_TMP)/query-shell-deps.log\n\n# Copyright (c) 2024, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: beam-cache-restore-app beam-cache-restore-test clean-beam-cache distclean-beam-cache\n\nBEAM_CACHE_DIR ?= $(ERLANG_MK_TMP)/beam-cache\nPROJECT_BEAM_CACHE_DIR = $(BEAM_CACHE_DIR)/$(PROJECT)\n\nclean:: clean-beam-cache\n\nclean-beam-cache:\n\t$(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR)\n\ndistclean:: distclean-beam-cache\n\n$(PROJECT_BEAM_CACHE_DIR):\n\t$(verbose) mkdir -p $(PROJECT_BEAM_CACHE_DIR)\n\ndistclean-beam-cache:\n\t$(gen_verbose) rm -rf $(BEAM_CACHE_DIR)\n\nbeam-cache-restore-app: | $(PROJECT_BEAM_CACHE_DIR)\n\t$(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR)/ebin-test\nifneq ($(wildcard ebin/),)\n\t$(verbose) mv ebin/ $(PROJECT_BEAM_CACHE_DIR)/ebin-test\nendif\nifneq ($(wildcard $(PROJECT_BEAM_CACHE_DIR)/ebin-app),)\n\t$(gen_verbose) mv $(PROJECT_BEAM_CACHE_DIR)/ebin-app ebin/\nelse\n\t$(verbose) $(MAKE) --no-print-directory clean-app\nendif\n\nbeam-cache-restore-test: | $(PROJECT_BEAM_CACHE_DIR)\n\t$(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR)/ebin-app\nifneq ($(wildcard ebin/),)\n\t$(verbose) mv ebin/ $(PROJECT_BEAM_CACHE_DIR)/ebin-app\nendif\nifneq ($(wildcard $(PROJECT_BEAM_CACHE_DIR)/ebin-test),)\n\t$(gen_verbose) mv $(PROJECT_BEAM_CACHE_DIR)/ebin-test ebin/\nelse\n\t$(verbose) $(MAKE) --no-print-directory clean-app\nendif\n\n# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: clean-app\n\n# Configuration.\n\nERLC_OPTS ?= -Werror +debug_info +warn_export_vars +warn_shadow_vars \\\n\t+warn_obsolete_guard # +bin_opt_info +warn_export_all +warn_missing_spec\nCOMPILE_FIRST ?=\nCOMPILE_FIRST_PATHS = $(addprefix src/,$(addsuffix .erl,$(COMPILE_FIRST)))\nERLC_EXCLUDE ?=\nERLC_EXCLUDE_PATHS = $(addprefix src/,$(addsuffix .erl,$(ERLC_EXCLUDE)))\n\nERLC_ASN1_OPTS ?=\n\nERLC_MIB_OPTS ?=\nCOMPILE_MIB_FIRST ?=\nCOMPILE_MIB_FIRST_PATHS = $(addprefix mibs/,$(addsuffix .mib,$(COMPILE_MIB_FIRST)))\n\n# Verbosity.\n\napp_verbose_0 = @echo \" APP   \" $(PROJECT);\napp_verbose_2 = set -x;\napp_verbose = $(app_verbose_$(V))\n\nappsrc_verbose_0 = @echo \" APP   \" $(PROJECT).app.src;\nappsrc_verbose_2 = set -x;\nappsrc_verbose = $(appsrc_verbose_$(V))\n\nmakedep_verbose_0 = @echo \" DEPEND\" $(PROJECT).d;\nmakedep_verbose_2 = set -x;\nmakedep_verbose = $(makedep_verbose_$(V))\n\nerlc_verbose_0 = @echo \" ERLC  \" $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\\\n\t$(filter %.erl %.core,$(?F)));\nerlc_verbose_2 = set -x;\nerlc_verbose = $(erlc_verbose_$(V))\n\nxyrl_verbose_0 = @echo \" XYRL  \" $(filter %.xrl %.yrl,$(?F));\nxyrl_verbose_2 = set -x;\nxyrl_verbose = $(xyrl_verbose_$(V))\n\nasn1_verbose_0 = @echo \" ASN1  \" $(filter %.asn1,$(?F));\nasn1_verbose_2 = set -x;\nasn1_verbose = $(asn1_verbose_$(V))\n\nmib_verbose_0 = @echo \" MIB   \" $(filter %.bin %.mib,$(?F));\nmib_verbose_2 = set -x;\nmib_verbose = $(mib_verbose_$(V))\n\nifneq ($(wildcard src/)$(wildcard lib/),)\n\n# Targets.\n\napp:: $(if $(wildcard ebin/test),beam-cache-restore-app) deps\n\t$(verbose) $(MAKE) --no-print-directory $(PROJECT).d\n\t$(verbose) $(MAKE) --no-print-directory app-build\n\nPROJECT_MOD := $(if $(PROJECT_MOD),$(PROJECT_MOD),$(if $(wildcard src/$(PROJECT)_app.erl),$(PROJECT)_app))\n\ndefine app_file\n{application, '$(PROJECT)', [\n\t{description, \"$(PROJECT_DESCRIPTION)\"},\n\t{vsn, \"$(PROJECT_VERSION)\"},$(if $(IS_DEP),\n\t{id$(comma)$(space)\"$1\"}$(comma))\n\t{modules, [$(call comma_list,$2)]},\n\t{registered, [$(if $(PROJECT_MOD),$(call comma_list,$(if $(filter $(PROJECT_MOD),$(PROJECT)_app),$(PROJECT)_sup) $(PROJECT_REGISTERED)))]},\n\t{applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(OPTIONAL_DEPS) $(foreach dep,$(DEPS),$(call query_name,$(dep))))]},\n\t{optional_applications, [$(call comma_list,$(OPTIONAL_DEPS))]},$(if $(PROJECT_MOD),\n\t{mod$(comma)$(space){$(patsubst %,'%',$(PROJECT_MOD))$(comma)$(space)[]}}$(comma))\n\t{env, $(subst \\,\\\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \\,\\\\,$(PROJECT_APP_EXTRA_KEYS)),)\n]}.\nendef\n\napp-build: ebin/$(PROJECT).app\n\t$(verbose) :\n\n# Source files.\n\nALL_SRC_FILES := $(sort $(call core_find,src/,*))\n\nERL_FILES := $(filter %.erl,$(ALL_SRC_FILES))\nCORE_FILES := $(filter %.core,$(ALL_SRC_FILES))\n\nALL_LIB_FILES := $(sort $(call core_find,lib/,*))\nEX_FILES := $(filter-out lib/mix/%,$(filter %.ex,$(ALL_SRC_FILES) $(ALL_LIB_FILES)))\n\n# ASN.1 files.\n\nifneq ($(wildcard asn1/),)\nASN1_FILES = $(sort $(call core_find,asn1/,*.asn1))\nERL_FILES += $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES))))\n\ndefine compile_asn1\n\t$(verbose) mkdir -p include/\n\t$(asn1_verbose) erlc -v -I include/ -o asn1/ +noobj $(ERLC_ASN1_OPTS) $1\n\t$(verbose) mv asn1/*.erl src/\n\t-$(verbose) mv asn1/*.hrl include/\n\t$(verbose) mv asn1/*.asn1db include/\nendef\n\n$(PROJECT).d:: $(ASN1_FILES)\n\t$(if $(strip $?),$(call compile_asn1,$?))\nendif\n\n# SNMP MIB files.\n\nifneq ($(wildcard mibs/),)\nMIB_FILES = $(sort $(call core_find,mibs/,*.mib))\n\n$(PROJECT).d:: $(COMPILE_MIB_FIRST_PATHS) $(MIB_FILES)\n\t$(verbose) mkdir -p include/ priv/mibs/\n\t$(mib_verbose) erlc -v $(ERLC_MIB_OPTS) -o priv/mibs/ -I priv/mibs/ $?\n\t$(mib_verbose) erlc -o include/ -- $(addprefix priv/mibs/,$(patsubst %.mib,%.bin,$(notdir $?)))\nendif\n\n# Leex and Yecc files.\n\nXRL_FILES := $(filter %.xrl,$(ALL_SRC_FILES))\nXRL_ERL_FILES = $(addprefix src/,$(patsubst %.xrl,%.erl,$(notdir $(XRL_FILES))))\nERL_FILES += $(XRL_ERL_FILES)\n\nYRL_FILES := $(filter %.yrl,$(ALL_SRC_FILES))\nYRL_ERL_FILES = $(addprefix src/,$(patsubst %.yrl,%.erl,$(notdir $(YRL_FILES))))\nERL_FILES += $(YRL_ERL_FILES)\n\n$(PROJECT).d:: $(XRL_FILES) $(YRL_FILES)\n\t$(if $(strip $?),$(xyrl_verbose) erlc -v -o src/ $(YRL_ERLC_OPTS) $?)\n\n# Erlang and Core Erlang files.\n\ndefine makedep.erl\n\tE = ets:new(makedep, [bag]),\n\tG = digraph:new([acyclic]),\n\tErlFiles = lists:usort(string:tokens(\"$(ERL_FILES)\", \" \")),\n\tDepsDir = \"$(call core_native_path,$(DEPS_DIR))\",\n\tAppsDir = \"$(call core_native_path,$(APPS_DIR))\",\n\tDepsDirsSrc = \"$(if $(wildcard $(DEPS_DIR)/*/src), $(call core_native_path,$(wildcard $(DEPS_DIR)/*/src)))\",\n\tDepsDirsInc = \"$(if $(wildcard $(DEPS_DIR)/*/include), $(call core_native_path,$(wildcard $(DEPS_DIR)/*/include)))\",\n\tAppsDirsSrc = \"$(if $(wildcard $(APPS_DIR)/*/src), $(call core_native_path,$(wildcard $(APPS_DIR)/*/src)))\",\n\tAppsDirsInc = \"$(if $(wildcard $(APPS_DIR)/*/include), $(call core_native_path,$(wildcard $(APPS_DIR)/*/include)))\",\n\tDepsDirs = lists:usort(string:tokens(DepsDirsSrc++DepsDirsInc, \" \")),\n\tAppsDirs = lists:usort(string:tokens(AppsDirsSrc++AppsDirsInc, \" \")),\n\tModules = [{list_to_atom(filename:basename(F, \".erl\")), F} || F <- ErlFiles],\n\tAdd = fun (Mod, Dep) ->\n\t\tcase lists:keyfind(Dep, 1, Modules) of\n\t\t\tfalse -> ok;\n\t\t\t{_, DepFile} ->\n\t\t\t\t{_, ModFile} = lists:keyfind(Mod, 1, Modules),\n\t\t\t\tets:insert(E, {ModFile, DepFile}),\n\t\t\t\tdigraph:add_vertex(G, Mod),\n\t\t\t\tdigraph:add_vertex(G, Dep),\n\t\t\t\tdigraph:add_edge(G, Mod, Dep)\n\t\tend\n\tend,\n\tAddHd = fun (F, Mod, DepFile) ->\n\t\tcase file:open(DepFile, [read]) of\n\t\t\t{error, enoent} ->\n\t\t\t\tok;\n\t\t\t{ok, Fd} ->\n\t\t\t\t{_, ModFile} = lists:keyfind(Mod, 1, Modules),\n\t\t\t\tcase ets:match(E, {ModFile, DepFile}) of\n\t\t\t\t\t[] ->\n\t\t\t\t\t\tets:insert(E, {ModFile, DepFile}),\n\t\t\t\t\t\tF(F, Fd, Mod,0);\n\t\t\t\t\t_ -> ok\n\t\t\t\tend\n\t\tend\n\tend,\n\tSearchHrl = fun\n\t\tF(_Hrl, []) -> {error,enoent};\n\t\tF(Hrl, [Dir|Dirs]) ->\n\t\t\tHrlF = filename:join([Dir,Hrl]),\n\t\t\tcase filelib:is_file(HrlF) of\n\t\t\t\ttrue  ->\n\t\t\t\t{ok, HrlF};\n\t\t\t\tfalse -> F(Hrl,Dirs)\n\t\t\tend\n\tend,\n\tAttr = fun\n\t\t(_F, Mod, behavior, Dep) ->\n\t\t\tAdd(Mod, Dep);\n\t\t(_F, Mod, behaviour, Dep) ->\n\t\t\tAdd(Mod, Dep);\n\t\t(_F, Mod, compile, {parse_transform, Dep}) ->\n\t\t\tAdd(Mod, Dep);\n\t\t(_F, Mod, compile, Opts) when is_list(Opts) ->\n\t\t\tcase proplists:get_value(parse_transform, Opts) of\n\t\t\t\tundefined -> ok;\n\t\t\t\tDep -> Add(Mod, Dep)\n\t\t\tend;\n\t\t(F, Mod, include, Hrl) ->\n\t\t\tcase SearchHrl(Hrl, [\"src\", \"include\",AppsDir,DepsDir]++AppsDirs++DepsDirs) of\n\t\t\t\t{ok, FoundHrl} -> AddHd(F, Mod, FoundHrl);\n\t\t\t\t{error, _} -> false\n\t\t\tend;\n\t\t(F, Mod, include_lib, Hrl) ->\n\t\t\tcase SearchHrl(Hrl, [\"src\", \"include\",AppsDir,DepsDir]++AppsDirs++DepsDirs) of\n\t\t\t\t{ok, FoundHrl} -> AddHd(F, Mod, FoundHrl);\n\t\t\t\t{error, _} -> false\n\t\t\tend;\n\t\t(F, Mod, import, {Imp, _}) ->\n\t\t\tIsFile =\n\t\t\t\tcase lists:keyfind(Imp, 1, Modules) of\n\t\t\t\t\tfalse -> false;\n\t\t\t\t\t{_, FilePath} -> filelib:is_file(FilePath)\n\t\t\t\tend,\n\t\t\tcase IsFile of\n\t\t\t\tfalse -> ok;\n\t\t\t\ttrue -> Add(Mod, Imp)\n\t\t\tend;\n\t\t(_, _, _, _) -> ok\n\tend,\n\tMakeDepend = fun\n\t\t(F, Fd, Mod, StartLocation) ->\n\t\t\tcase io:parse_erl_form(Fd, undefined, StartLocation) of\n\t\t\t\t{ok, AbsData, EndLocation} ->\n\t\t\t\t\tcase AbsData of\n\t\t\t\t\t\t{attribute, _, Key, Value} ->\n\t\t\t\t\t\t\tAttr(F, Mod, Key, Value),\n\t\t\t\t\t\t\tF(F, Fd, Mod, EndLocation);\n\t\t\t\t\t\t_ -> F(F, Fd, Mod, EndLocation)\n\t\t\t\t\tend;\n\t\t\t\t{eof, _ } -> file:close(Fd);\n\t\t\t\t{error, ErrorDescription } ->\n\t\t\t\t\tfile:close(Fd);\n\t\t\t\t{error, ErrorInfo, ErrorLocation} ->\n\t\t\t\t\tF(F, Fd, Mod, ErrorLocation)\n\t\t\tend,\n\t\t\tok\n\tend,\n\t[begin\n\t\tMod = list_to_atom(filename:basename(F, \".erl\")),\n\t\tcase file:open(F, [read]) of\n\t\t\t{ok, Fd} -> MakeDepend(MakeDepend, Fd, Mod,0);\n\t\t\t{error, enoent} -> ok\n\t\tend\n\tend || F <- ErlFiles],\n\tDepend = sofs:to_external(sofs:relation_to_family(sofs:relation(ets:tab2list(E)))),\n\tCompileFirst = [X || X <- lists:reverse(digraph_utils:topsort(G)), [] =/= digraph:in_neighbours(G, X)],\n\tTargetPath = fun(Target) ->\n\t\tcase lists:keyfind(Target, 1, Modules) of\n\t\t\tfalse -> \"\";\n\t\t\t{_, DepFile} ->\n\t\t\t\tDirSubname = tl(string:tokens(filename:dirname(DepFile), \"/\")),\n\t\t\t\tstring:join(DirSubname ++ [atom_to_list(Target)], \"/\")\n\t\tend\n\tend,\n\tOutput0 = [\n\t\t\"# Generated by Erlang.mk. Edit at your own risk!\\n\\n\",\n\t\t[[F, \"::\", [[\" \", D] || D <- Deps], \"; @touch \\$$@\\n\"] || {F, Deps} <- Depend],\n\t\t\"\\nCOMPILE_FIRST +=\", [[\" \", TargetPath(CF)] || CF <- CompileFirst], \"\\n\"\n\t],\n\tOutput = case \"é\" of\n\t\t[233] -> unicode:characters_to_binary(Output0);\n\t\t_ -> Output0\n\tend,\n\tok = file:write_file(\"$1\", Output),\n\thalt()\nendef\n\nifeq ($(if $(NO_MAKEDEP),$(wildcard $(PROJECT).d),),)\n$(PROJECT).d:: $(ERL_FILES) $(EX_FILES) $(call core_find,include/,*.hrl) $(MAKEFILE_LIST)\n# Rebuild everything when the .d file does not exist.\n# We touch $@ to make sure the command doesn't fail in empty projects.\n# The file will be generated with content immediately after.\n\t$(verbose) if ! test -e $@; then \\\n\t\ttouch $@ $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES); \\\n\tfi\n\t$(makedep_verbose) $(call erlang,$(call makedep.erl,$@))\nendif\n\nifeq ($(IS_APP)$(IS_DEP),)\nifneq ($(words $(ERL_FILES) $(EX_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES) $(EX_FILES)),0)\n# Rebuild everything when the Makefile changes.\n$(ERLANG_MK_TMP)/last-makefile-change: $(MAKEFILE_LIST) | $(ERLANG_MK_TMP)\n\t$(verbose) if test -f $@; then \\\n\t\ttouch $(ERL_FILES) $(EX_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES) $(EX_FILES); \\\n\t\ttouch -c $(PROJECT).d; \\\n\tfi\n\t$(verbose) touch $@\n\n$(ERL_FILES) $(EX_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(ERLANG_MK_TMP)/last-makefile-change\nebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change\nendif\nendif\n\n$(PROJECT).d::\n\t$(verbose) :\n\ninclude $(wildcard $(PROJECT).d)\n\nebin/$(PROJECT).app:: ebin/\n\nebin/:\n\t$(verbose) mkdir -p ebin/\n\ndefine compile_erl\n\t$(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \\\n\t\t-pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),$(COMPILE_FIRST_PATHS) $1)\nendef\n\ndefine validate_app_file\n\tcase file:consult(\"ebin/$(PROJECT).app\") of\n\t\t{ok, _} -> halt();\n\t\t_ -> halt(1)\n\tend\nendef\n\nebin/$(PROJECT).app:: $(ERL_FILES) $(CORE_FILES) $(wildcard src/$(PROJECT).app.src) $(EX_FILES)\n\t$(eval FILES_TO_COMPILE := $(filter-out $(EX_FILES) src/$(PROJECT).app.src,$?))\n\t$(if $(strip $(FILES_TO_COMPILE)),$(call compile_erl,$(FILES_TO_COMPILE)))\n\t$(if $(filter $(ELIXIR),disable),,$(if $(filter $?,$(EX_FILES)),$(elixirc_verbose) $(eval MODULES := $(shell $(call erlang,$(call compile_ex.erl,$(EX_FILES)))))))\n\t$(eval ELIXIR_COMP_FAILED := $(if $(filter _ERROR_,$(firstword $(MODULES))),true,false))\n# Older git versions do not have the --first-parent flag. Do without in that case.\n\t$(verbose) if $(ELIXIR_COMP_FAILED); then exit 1; fi\n\t$(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null \\\n\t\t|| git describe --dirty --abbrev=7 --tags --always 2>/dev/null || true))\n\t$(eval MODULES := $(MODULES) $(patsubst %,'%',$(sort $(notdir $(basename \\\n\t\t$(filter-out $(ERLC_EXCLUDE_PATHS),$(ERL_FILES) $(CORE_FILES) $(BEAM_FILES)))))))\nifeq ($(wildcard src/$(PROJECT).app.src),)\n\t$(app_verbose) printf '$(subst %,%%,$(subst $(newline),\\n,$(subst ','\\'',$(call app_file,$(GITDESCRIBE),$(MODULES)))))' \\\n\t\t> ebin/$(PROJECT).app\n\t$(verbose) if ! $(call erlang,$(call validate_app_file)); then \\\n\t\techo \"The .app file produced is invalid. Please verify the value of PROJECT_ENV.\" >&2; \\\n\t\texit 1; \\\n\tfi\nelse\n\t$(verbose) if [ -z \"$$(grep -e '^[^%]*{\\s*modules\\s*,' src/$(PROJECT).app.src)\" ]; then \\\n\t\techo \"Empty modules entry not found in $(PROJECT).app.src. Please consult the erlang.mk documentation for instructions.\" >&2; \\\n\t\texit 1; \\\n\tfi\n\t$(appsrc_verbose) cat src/$(PROJECT).app.src \\\n\t\t| sed \"s/{[[:space:]]*modules[[:space:]]*,[[:space:]]*\\[\\]}/{modules, \\[$(call comma_list,$(MODULES))\\]}/\" \\\n\t\t| sed \"s/{id,[[:space:]]*\\\"git\\\"}/{id, \\\"$(subst /,\\/,$(GITDESCRIBE))\\\"}/\" \\\n\t\t> ebin/$(PROJECT).app\nendif\nifneq ($(wildcard src/$(PROJECT).appup),)\n\t$(verbose) cp src/$(PROJECT).appup ebin/\nendif\n\nclean:: clean-app\n\nclean-app:\n\t$(gen_verbose) rm -rf $(PROJECT).d ebin/ priv/mibs/ $(XRL_ERL_FILES) $(YRL_ERL_FILES) \\\n\t\t$(addprefix include/,$(patsubst %.mib,%.hrl,$(notdir $(MIB_FILES)))) \\\n\t\t$(addprefix include/,$(patsubst %.asn1,%.hrl,$(notdir $(ASN1_FILES)))) \\\n\t\t$(addprefix include/,$(patsubst %.asn1,%.asn1db,$(notdir $(ASN1_FILES)))) \\\n\t\t$(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES))))\n\nendif\n\n# Copyright (c) 2024, Tyler Hughes <tyler@tylerhughes.dev>\n# Copyright (c) 2024, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\nifeq ($(ELIXIR),system)\n# We expect 'elixir' to be on the path.\nELIXIR_BIN ?= $(shell readlink -f `which elixir`)\nELIXIR_LIBS ?= $(abspath $(dir $(ELIXIR_BIN))/../lib)\n# Fallback in case 'elixir' is a shim.\nifeq ($(wildcard $(ELIXIR_LIBS)/elixir/),)\nELIXIR_LIBS = $(abspath $(shell elixir -e 'IO.puts(:code.lib_dir(:elixir))')/../)\nendif\nELIXIR_LIBS := $(ELIXIR_LIBS)\nexport ELIXIR_LIBS\nERL_LIBS := $(ERL_LIBS):$(ELIXIR_LIBS)\nelse\nifeq ($(ELIXIR),dep)\nERL_LIBS := $(ERL_LIBS):$(DEPS_DIR)/elixir/lib/\nendif\nendif\n\nelixirc_verbose_0 = @echo \" EXC    $(words $(EX_FILES)) files\";\nelixirc_verbose_2 = set -x;\nelixirc_verbose = $(elixirc_verbose_$(V))\n\n# Unfortunately this currently requires Elixir.\n# https://github.com/jelly-beam/verl is a good choice\n# for an Erlang implementation, but we already have to\n# pull hex_core and Rebar3 so adding yet another pull\n# is annoying, especially one that would be necessary\n# every time we autopatch Rebar projects. Wait and see.\ndefine hex_version_resolver.erl\n\tHexVersionResolve = fun(Name, Req) ->\n\t\tapplication:ensure_all_started(ssl),\n\t\tapplication:ensure_all_started(inets),\n\t\tConfig = $(hex_config.erl),\n\t\tcase hex_repo:get_package(Config, atom_to_binary(Name)) of\n\t\t\t{ok, {200, _RespHeaders, Package}} ->\n\t\t\t\t#{releases := List} = Package,\n\t\t\t\t{value, #{version := Version}} = lists:search(fun(#{version := Vsn}) ->\n\t\t\t\t\tM = list_to_atom(\"Elixir.Version\"),\n\t\t\t\t\tF = list_to_atom(\"match?\"),\n\t\t\t\t\tM:F(Vsn, Req)\n\t\t\t\tend, List),\n\t\t\t\t{ok, Version};\n\t\t\t{ok, {Status, _, Errors}} ->\n\t\t\t\t{error, Status, Errors}\n\t\tend\n\tend,\n\tHexVersionResolveAndPrint = fun(Name, Req) ->\n\t\tcase HexVersionResolve(Name, Req) of\n\t\t\t{ok, Version} ->\n\t\t\t\tio:format(\"~s\", [Version]),\n\t\t\t\thalt(0);\n\t\t\t{error, Status, Errors} ->\n\t\t\t\tio:format(\"Error ~b: ~0p~n\", [Status, Errors]),\n\t\t\t\thalt(77)\n\t\tend\n\tend\nendef\n\ndefine dep_autopatch_mix.erl\n\t$(call hex_version_resolver.erl),\n\t{ok, _} = application:ensure_all_started(elixir),\n\t{ok, _} = application:ensure_all_started(mix),\n\tMixFile = <<\"$(call core_native_path,$(DEPS_DIR)/$1/mix.exs)\">>,\n\t{Mod, Bin} =\n\t\tcase elixir_compiler:file(MixFile, fun(_File, _LexerPid) -> ok end) of\n\t\t\t[{T = {_, _}, _CheckerPid}] -> T;\n\t\t\t[T = {_, _}] -> T\n\t\tend,\n\t{module, Mod} = code:load_binary(Mod, binary_to_list(MixFile), Bin),\n\tProject = Mod:project(),\n\tApplication = try Mod:application() catch error:undef -> [] end,\n\tStartMod = case lists:keyfind(mod, 1, Application) of\n\t\t{mod, {StartMod0, _StartArgs}} ->\n\t\t\tatom_to_list(StartMod0);\n\t\t_ ->\n\t\t\t\"\"\n\tend,\n\tWrite = fun (Text) ->\n\t\tfile:write_file(\"$(call core_native_path,$(DEPS_DIR)/$1/Makefile)\", Text, [append])\n\tend,\n\tWrite([\n\t\t\"PROJECT = \", atom_to_list(proplists:get_value(app, Project)), \"\\n\"\n\t\t\"PROJECT_DESCRIPTION = \", proplists:get_value(description, Project, \"\"), \"\\n\"\n\t\t\"PROJECT_VERSION = \", proplists:get_value(version, Project, \"\"), \"\\n\"\n\t\t\"PROJECT_MOD = \", StartMod, \"\\n\"\n\t\t\"define PROJECT_ENV\\n\",\n\t\tio_lib:format(\"~p\", [proplists:get_value(env, Application, [])]), \"\\n\"\n\t\t\"endef\\n\\n\"]),\n\tExtraApps = lists:usort([eex, elixir, logger, mix] ++ proplists:get_value(extra_applications, Application, [])),\n\tWrite([\"LOCAL_DEPS += \", lists:join(\" \", [atom_to_list(App) || App <- ExtraApps]), \"\\n\\n\"]),\n\tDeps = proplists:get_value(deps, Project, []) -- [elixir_make],\n\tIsRequiredProdDep = fun(Opts) ->\n\t\t(proplists:get_value(optional, Opts) =/= true)\n\t\tandalso\n\t\tcase proplists:get_value(only, Opts, prod) of\n\t\t\tprod -> true;\n\t\t\tL when is_list(L) -> lists:member(prod, L);\n\t\t\t_ -> false\n\t\tend\n\tend,\n\tlists:foreach(fun\n\t\t({Name, Req}) when is_binary(Req) ->\n\t\t\t{ok, Vsn} = HexVersionResolve(Name, Req),\n\t\t\tWrite([\"DEPS += \", atom_to_list(Name), \"\\n\"]),\n\t\t\tWrite([\"dep_\", atom_to_list(Name), \" = hex \", Vsn, \" \", atom_to_list(Name), \"\\n\"]);\n\t\t({Name, Opts}) when is_list(Opts) ->\n\t\t\tPath = proplists:get_value(path, Opts),\n\t\t\tcase IsRequiredProdDep(Opts) of\n\t\t\t\ttrue when Path =/= undefined ->\n\t\t\t\t\tWrite([\"DEPS += \", atom_to_list(Name), \"\\n\"]),\n\t\t\t\t\tWrite([\"dep_\", atom_to_list(Name), \" = ln \", Path, \"\\n\"]);\n\t\t\t\ttrue when Path =:= undefined ->\n\t\t\t\t\tWrite([\"DEPS += \", atom_to_list(Name), \"\\n\"]),\n\t\t\t\t\tio:format(standard_error, \"Warning: No version given for ~p.\", [Name]);\n\t\t\t\tfalse ->\n\t\t\t\t\tok\n\t\t\tend;\n\t\t({Name, Req, Opts}) ->\n\t\t\tcase IsRequiredProdDep(Opts) of\n\t\t\t\ttrue ->\n\t\t\t\t\t{ok, Vsn} = HexVersionResolve(Name, Req),\n\t\t\t\t\tWrite([\"DEPS += \", atom_to_list(Name), \"\\n\"]),\n\t\t\t\t\tWrite([\"dep_\", atom_to_list(Name), \" = hex \", Vsn, \" \", atom_to_list(Name), \"\\n\"]);\n\t\t\t\tfalse ->\n\t\t\t\t\tok\n\t\t\tend;\n\t\t(_) ->\n\t\t\tok\n\tend, Deps),\n\tcase lists:member(elixir_make, proplists:get_value(compilers, Project, [])) of\n\t\tfalse -> \n\t\t\tok;\n\t\ttrue ->\n\t\t\tWrite(\"# https://hexdocs.pm/elixir_make/Mix.Tasks.Compile.ElixirMake.html\\n\"),\n\t\t\tMakeVal = fun(Key, Proplist, DefaultVal, DefaultReplacement) ->\n\t\t\t\tcase proplists:get_value(Key, Proplist, DefaultVal) of\n\t\t\t\t\tDefaultVal -> DefaultReplacement;\n\t\t\t\t\tValue -> Value\n\t\t\t\tend\n\t\t\tend,\n\t\t\tMakeMakefile = binary_to_list(MakeVal(make_makefile, Project, default, <<\"Makefile\">>)),\n\t\t\tMakeExe = MakeVal(make_executable, Project, default, \"$$\\(MAKE)\"),\n\t\t\tMakeCwd = MakeVal(make_cwd, Project, undefined, <<\".\">>),\n\t\t\tMakeTargets = MakeVal(make_targets, Project, [], []),\n\t\t\tMakeArgs = MakeVal(make_args, Project, undefined, []),\n\t\t\tcase file:rename(\"$(DEPS_DIR)/$1/\" ++ MakeMakefile, \"$(DEPS_DIR)/$1/elixir_make.mk\") of\n\t\t\t\tok -> ok;\n\t\t\t\tErr = {error, _} ->\n\t\t\t\t\tio:format(standard_error, \"Failed to copy Makefile with error ~p~n\", [Err]),\n\t\t\t\t\thalt(90)\n\t\t\tend,\n\t\t\tWrite([\"app::\\n\"\n\t\t\t\t\"\\t\", MakeExe, \" -C \", MakeCwd, \" -f $(DEPS_DIR)/$1/elixir_make.mk\",\n\t\t\t\tlists:join(\" \", MakeTargets),\n\t\t\t\tlists:join(\" \", MakeArgs),\n\t\t\t\t\"\\n\\n\"]),\n\t\t\tcase MakeVal(make_clean, Project, nil, undefined) of\n\t\t\t\tundefined ->\n\t\t\t\t\tok;\n\t\t\t\tClean ->\n\t\t\t\t\tWrite([\"clean::\\n\\t\", Clean, \"\\n\\n\"])\n\t\t\tend\n\tend,\n\tWrite(\"ERLC_OPTS = +debug_info\\n\\n\"),\n\tWrite(\"include $$\\(if $$\\(ERLANG_MK_FILENAME),$$\\(ERLANG_MK_FILENAME),erlang.mk)\"),\n\thalt()\nendef\n\ndefine dep_autopatch_mix\n\tsed 's|\\(defmodule.*do\\)|\\1\\n  try do\\n    Code.compiler_options(on_undefined_variable: :warn)\\n    rescue _ -> :ok\\n  end\\n|g' $(DEPS_DIR)/$(1)/mix.exs > $(DEPS_DIR)/$(1)/mix.exs.new; \\\n\tmv $(DEPS_DIR)/$(1)/mix.exs.new $(DEPS_DIR)/$(1)/mix.exs; \\\n\t$(MAKE) $(DEPS_DIR)/hex_core/ebin/dep_built; \\\n\tMIX_ENV=\"$(if $(MIX_ENV),$(strip $(MIX_ENV)),prod)\" \\\n\t\t$(call erlang,$(call dep_autopatch_mix.erl,$1))\nendef\n\n# We change the group leader so the Elixir io:format output\n# isn't captured as we need to either print the modules on\n# success, or print _ERROR_ on failure.\ndefine compile_ex.erl\n\t{ok, _} = application:ensure_all_started(elixir),\n\t{ok, _} = application:ensure_all_started(mix),\n\t$(foreach dep,$(LOCAL_DEPS),_ = application:load($(dep)),)\n\tModCode = list_to_atom(\"Elixir.Code\"),\n\tModCode:put_compiler_option(ignore_module_conflict, true),\n\tModComp = list_to_atom(\"Elixir.Kernel.ParallelCompiler\"),\n\tModMixProject = list_to_atom(\"Elixir.Mix.Project\"),\n\terlang:group_leader(whereis(standard_error), self()),\n\tModMixProject:in_project($(PROJECT), \".\", [], fun(_MixFile) ->\n\t\tcase ModComp:compile_to_path([$(call comma_list,$(patsubst %,<<\"%\">>,$1))], <<\"ebin/\">>) of\n\t\t\t{ok, Modules, _} ->\n\t\t\t\tlists:foreach(fun(E) -> io:format(user, \"~p \", [E]) end, Modules),\n\t\t\t\thalt(0);\n\t\t\t{error, _ErroredModules, _WarnedModules} ->\n\t\t\t\tio:format(user, \"_ERROR_\", []),\n\t\t\t\thalt(1)\n\t\tend\n\tend)\nendef\n\n# Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>\n# Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: docs-deps\n\n# Configuration.\n\nALL_DOC_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(DOC_DEPS))\n\n# Targets.\n\n$(foreach dep,$(DOC_DEPS),$(eval $(call dep_target,$(dep))))\n\nifneq ($(SKIP_DEPS),)\ndoc-deps:\nelse\ndoc-deps: $(ALL_DOC_DEPS_DIRS)\n\t$(verbose) set -e; for dep in $(ALL_DOC_DEPS_DIRS) ; do $(MAKE) -C $$dep IS_DEP=1; done\nendif\n\n# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: rel-deps\n\n# Configuration.\n\nALL_REL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(REL_DEPS))\n\n# Targets.\n\n$(foreach dep,$(REL_DEPS),$(eval $(call dep_target,$(dep))))\n\nifneq ($(SKIP_DEPS),)\nrel-deps:\nelse\nrel-deps: $(ALL_REL_DEPS_DIRS)\n\t$(verbose) set -e; for dep in $(ALL_REL_DEPS_DIRS) ; do $(MAKE) -C $$dep; done\nendif\n\n# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: test-deps test-dir test-build clean-test-dir\n\n# Configuration.\n\nTEST_DIR ?= $(CURDIR)/test\n\nALL_TEST_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(TEST_DEPS))\n\nTEST_ERLC_OPTS ?= +debug_info +warn_export_vars +warn_shadow_vars +warn_obsolete_guard\nTEST_ERLC_OPTS += -DTEST=1\n\n# Targets.\n\n$(foreach dep,$(TEST_DEPS),$(eval $(call dep_target,$(dep))))\n\nifneq ($(SKIP_DEPS),)\ntest-deps:\nelse\ntest-deps: $(ALL_TEST_DEPS_DIRS)\n\t$(verbose) set -e; for dep in $(ALL_TEST_DEPS_DIRS) ; do \\\n\t\tif [ -z \"$(strip $(FULL))\" ] && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \\\n\t\t\t:; \\\n\t\telse \\\n\t\t\t$(MAKE) -C $$dep IS_DEP=1; \\\n\t\t\tif [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \\\n\t\tfi \\\n\tdone\nendif\n\nifneq ($(wildcard $(TEST_DIR)),)\ntest-dir: $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build\n\t@:\n\ntest_erlc_verbose_0 = @echo \" ERLC  \" $(filter-out $(patsubst %,%.erl,$(ERLC_EXCLUDE)),\\\n\t$(filter %.erl %.core,$(notdir $(FILES_TO_COMPILE))));\ntest_erlc_verbose_2 = set -x;\ntest_erlc_verbose = $(test_erlc_verbose_$(V))\n\ndefine compile_test_erl\n\t$(test_erlc_verbose) erlc -v $(TEST_ERLC_OPTS) -o $(TEST_DIR) \\\n\t\t-pa ebin/ -I include/ $1\nendef\n\nERL_TEST_FILES = $(call core_find,$(TEST_DIR)/,*.erl)\n\n$(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build: $(ERL_TEST_FILES) $(MAKEFILE_LIST)\n# When we have to recompile files in src/ the .d file always gets rebuilt.\n# Therefore we want to ignore it when rebuilding test files.\n\t$(eval FILES_TO_COMPILE := $(if $(filter $(filter-out $(PROJECT).d,$(MAKEFILE_LIST)),$?),$(filter $(ERL_TEST_FILES),$^),$(filter $(ERL_TEST_FILES),$?)))\n\t$(if $(strip $(FILES_TO_COMPILE)),$(call compile_test_erl,$(FILES_TO_COMPILE)) && touch $@)\nendif\n\ntest-build:: IS_TEST=1\ntest-build:: ERLC_OPTS=$(TEST_ERLC_OPTS)\ntest-build:: $(if $(wildcard src),$(if $(wildcard ebin/test),,beam-cache-restore-test)) $(if $(IS_APP),,deps test-deps)\n# We already compiled everything when IS_APP=1.\nifndef IS_APP\nifneq ($(wildcard src),)\n\t$(verbose) $(MAKE) --no-print-directory $(PROJECT).d ERLC_OPTS=\"$(call escape_dquotes,$(TEST_ERLC_OPTS))\"\n\t$(verbose) $(MAKE) --no-print-directory app-build ERLC_OPTS=\"$(call escape_dquotes,$(TEST_ERLC_OPTS))\"\n\t$(gen_verbose) touch ebin/test\nendif\nifneq ($(wildcard $(TEST_DIR)),)\n\t$(verbose) $(MAKE) --no-print-directory test-dir ERLC_OPTS=\"$(call escape_dquotes,$(TEST_ERLC_OPTS))\"\nendif\nendif\n\n# Roughly the same as test-build, but when IS_APP=1.\n# We only care about compiling the current application.\nifdef IS_APP\ntest-build-app:: ERLC_OPTS=$(TEST_ERLC_OPTS)\ntest-build-app:: deps test-deps\nifneq ($(wildcard src),)\n\t$(verbose) $(MAKE) --no-print-directory $(PROJECT).d ERLC_OPTS=\"$(call escape_dquotes,$(TEST_ERLC_OPTS))\"\n\t$(verbose) $(MAKE) --no-print-directory app-build ERLC_OPTS=\"$(call escape_dquotes,$(TEST_ERLC_OPTS))\"\n\t$(gen_verbose) touch ebin/test\nendif\nifneq ($(wildcard $(TEST_DIR)),)\n\t$(verbose) $(MAKE) --no-print-directory test-dir ERLC_OPTS=\"$(call escape_dquotes,$(TEST_ERLC_OPTS))\"\nendif\nendif\n\nclean:: clean-test-dir\n\nclean-test-dir:\nifneq ($(wildcard $(TEST_DIR)/*.beam),)\n\t$(gen_verbose) rm -f $(TEST_DIR)/*.beam $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build\nendif\n\n# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: rebar.config\n\ncompat_ref = {$(shell (git -C $(DEPS_DIR)/$1 show-ref -q --verify \"refs/heads/$2\" && echo branch) || (git -C $(DEPS_DIR)/$1 show-ref -q --verify \"refs/tags/$2\" && echo tag) || echo ref),\"$2\"}\n\n# We strip out -Werror because we don't want to fail due to\n# warnings when used as a dependency.\n\ncompat_prepare_erlc_opts = $(shell echo \"$1\" | sed 's/, */,/g')\n\ndefine compat_convert_erlc_opts\n$(if $(filter-out -Werror,$1),\\\n\t$(if $(findstring +,$1),\\\n\t\t$(shell echo $1 | cut -b 2-)))\nendef\n\ndefine compat_erlc_opts_to_list\n[$(call comma_list,$(foreach o,$(call compat_prepare_erlc_opts,$1),$(call compat_convert_erlc_opts,$o)))]\nendef\n\ndefine compat_rebar_config\n{deps, [\n$(call comma_list,$(foreach d,$(DEPS),\\\n\t$(if $(filter hex,$(call query_fetch_method,$d)),\\\n\t\t{$(call query_name,$d)$(comma)\"$(call query_version_hex,$d)\"},\\\n\t\t{$(call query_name,$d)$(comma)\".*\"$(comma){git,\"$(call query_repo,$d)\"$(comma)$(call compat_ref,$(call query_name,$d),$(call query_version,$d))}})))\n]}.\n{erl_opts, $(call compat_erlc_opts_to_list,$(ERLC_OPTS))}.\nendef\n\nrebar.config: deps\n\t$(gen_verbose) $(call core_render,compat_rebar_config,rebar.config)\n\ndefine tpl_application.app.src\n{application, project_name, [\n\t{description, \"\"},\n\t{vsn, \"0.1.0\"},\n\t{id, \"git\"},\n\t{modules, []},\n\t{registered, []},\n\t{applications, [\n\t\tkernel,\n\t\tstdlib\n\t]},\n\t{mod, {project_name_app, []}},\n\t{env, []}\n]}.\nendef\n\ndefine tpl_application\n-module(project_name_app).\n-behaviour(application).\n\n-export([start/2]).\n-export([stop/1]).\n\nstart(_Type, _Args) ->\n\tproject_name_sup:start_link().\n\nstop(_State) ->\n\tok.\nendef\n\ndefine tpl_apps_Makefile\nPROJECT = project_name\nPROJECT_DESCRIPTION = New project\nPROJECT_VERSION = 0.1.0\ntemplate_sp\n# Make sure we know where the applications are located.\nROOT_DIR ?= rel_root_dir\nAPPS_DIR ?= ..\nDEPS_DIR ?= rel_deps_dir\n\ninclude rel_root_dir/erlang.mk\nendef\n\ndefine tpl_cowboy_http_h\n-module(template_name).\n-behaviour(cowboy_http_handler).\n\n-export([init/3]).\n-export([handle/2]).\n-export([terminate/3]).\n\n-record(state, {\n}).\n\ninit(_, Req, _Opts) ->\n\t{ok, Req, #state{}}.\n\nhandle(Req, State=#state{}) ->\n\t{ok, Req2} = cowboy_req:reply(200, Req),\n\t{ok, Req2, State}.\n\nterminate(_Reason, _Req, _State) ->\n\tok.\nendef\n\ndefine tpl_cowboy_loop_h\n-module(template_name).\n-behaviour(cowboy_loop_handler).\n\n-export([init/3]).\n-export([info/3]).\n-export([terminate/3]).\n\n-record(state, {\n}).\n\ninit(_, Req, _Opts) ->\n\t{loop, Req, #state{}, 5000, hibernate}.\n\ninfo(_Info, Req, State) ->\n\t{loop, Req, State, hibernate}.\n\nterminate(_Reason, _Req, _State) ->\n\tok.\nendef\n\ndefine tpl_cowboy_rest_h\n-module(template_name).\n\n-export([init/3]).\n-export([content_types_provided/2]).\n-export([get_html/2]).\n\ninit(_, _Req, _Opts) ->\n\t{upgrade, protocol, cowboy_rest}.\n\ncontent_types_provided(Req, State) ->\n\t{[{{<<\"text\">>, <<\"html\">>, '*'}, get_html}], Req, State}.\n\nget_html(Req, State) ->\n\t{<<\"<html><body>This is REST!</body></html>\">>, Req, State}.\nendef\n\ndefine tpl_cowboy_websocket_h\n-module(template_name).\n-behaviour(cowboy_websocket_handler).\n\n-export([init/3]).\n-export([websocket_init/3]).\n-export([websocket_handle/3]).\n-export([websocket_info/3]).\n-export([websocket_terminate/3]).\n\n-record(state, {\n}).\n\ninit(_, _, _) ->\n\t{upgrade, protocol, cowboy_websocket}.\n\nwebsocket_init(_, Req, _Opts) ->\n\tReq2 = cowboy_req:compact(Req),\n\t{ok, Req2, #state{}}.\n\nwebsocket_handle({text, Data}, Req, State) ->\n\t{reply, {text, Data}, Req, State};\nwebsocket_handle({binary, Data}, Req, State) ->\n\t{reply, {binary, Data}, Req, State};\nwebsocket_handle(_Frame, Req, State) ->\n\t{ok, Req, State}.\n\nwebsocket_info(_Info, Req, State) ->\n\t{ok, Req, State}.\n\nwebsocket_terminate(_Reason, _Req, _State) ->\n\tok.\nendef\n\ndefine tpl_gen_fsm\n-module(template_name).\n-behaviour(gen_fsm).\n\n%% API.\n-export([start_link/0]).\n\n%% gen_fsm.\n-export([init/1]).\n-export([state_name/2]).\n-export([handle_event/3]).\n-export([state_name/3]).\n-export([handle_sync_event/4]).\n-export([handle_info/3]).\n-export([terminate/3]).\n-export([code_change/4]).\n\n-record(state, {\n}).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tgen_fsm:start_link(?MODULE, [], []).\n\n%% gen_fsm.\n\ninit([]) ->\n\t{ok, state_name, #state{}}.\n\nstate_name(_Event, StateData) ->\n\t{next_state, state_name, StateData}.\n\nhandle_event(_Event, StateName, StateData) ->\n\t{next_state, StateName, StateData}.\n\nstate_name(_Event, _From, StateData) ->\n\t{reply, ignored, state_name, StateData}.\n\nhandle_sync_event(_Event, _From, StateName, StateData) ->\n\t{reply, ignored, StateName, StateData}.\n\nhandle_info(_Info, StateName, StateData) ->\n\t{next_state, StateName, StateData}.\n\nterminate(_Reason, _StateName, _StateData) ->\n\tok.\n\ncode_change(_OldVsn, StateName, StateData, _Extra) ->\n\t{ok, StateName, StateData}.\nendef\n\ndefine tpl_gen_server\n-module(template_name).\n-behaviour(gen_server).\n\n%% API.\n-export([start_link/0]).\n\n%% gen_server.\n-export([init/1]).\n-export([handle_call/3]).\n-export([handle_cast/2]).\n-export([handle_info/2]).\n-export([terminate/2]).\n-export([code_change/3]).\n\n-record(state, {\n}).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tgen_server:start_link(?MODULE, [], []).\n\n%% gen_server.\n\ninit([]) ->\n\t{ok, #state{}}.\n\nhandle_call(_Request, _From, State) ->\n\t{reply, ignored, State}.\n\nhandle_cast(_Msg, State) ->\n\t{noreply, State}.\n\nhandle_info(_Info, State) ->\n\t{noreply, State}.\n\nterminate(_Reason, _State) ->\n\tok.\n\ncode_change(_OldVsn, State, _Extra) ->\n\t{ok, State}.\nendef\n\ndefine tpl_gen_statem\n-module(template_name).\n-behaviour(gen_statem).\n\n%% API.\n-export([start_link/0]).\n\n%% gen_statem.\n-export([callback_mode/0]).\n-export([init/1]).\n-export([state_name/3]).\n-export([handle_event/4]).\n-export([terminate/3]).\n-export([code_change/4]).\n\n-record(state, {\n}).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tgen_statem:start_link(?MODULE, [], []).\n\n%% gen_statem.\n\ncallback_mode() ->\n\tstate_functions.\n\ninit([]) ->\n\t{ok, state_name, #state{}}.\n\nstate_name(_EventType, _EventData, StateData) ->\n\t{next_state, state_name, StateData}.\n\nhandle_event(_EventType, _EventData, StateName, StateData) ->\n\t{next_state, StateName, StateData}.\n\nterminate(_Reason, _StateName, _StateData) ->\n\tok.\n\ncode_change(_OldVsn, StateName, StateData, _Extra) ->\n\t{ok, StateName, StateData}.\nendef\n\ndefine tpl_library.app.src\n{application, project_name, [\n\t{description, \"\"},\n\t{vsn, \"0.1.0\"},\n\t{id, \"git\"},\n\t{modules, []},\n\t{registered, []},\n\t{applications, [\n\t\tkernel,\n\t\tstdlib\n\t]}\n]}.\nendef\n\ndefine tpl_module\n-module(template_name).\n-export([]).\nendef\n\ndefine tpl_ranch_protocol\n-module(template_name).\n-behaviour(ranch_protocol).\n\n-export([start_link/4]).\n-export([init/4]).\n\n-type opts() :: [].\n-export_type([opts/0]).\n\n-record(state, {\n\tsocket :: inet:socket(),\n\ttransport :: module()\n}).\n\nstart_link(Ref, Socket, Transport, Opts) ->\n\tPid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),\n\t{ok, Pid}.\n\n-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.\ninit(Ref, Socket, Transport, _Opts) ->\n\tok = ranch:accept_ack(Ref),\n\tloop(#state{socket=Socket, transport=Transport}).\n\nloop(State) ->\n\tloop(State).\nendef\n\ndefine tpl_relx.config\n{release, {project_name_release, \"1\"}, [project_name, sasl, runtime_tools]}.\n{dev_mode, false}.\n{include_erts, true}.\n{extended_start_script, true}.\n{sys_config, \"config/sys.config\"}.\n{vm_args, \"config/vm.args\"}.\nendef\n\ndefine tpl_supervisor\n-module(template_name).\n-behaviour(supervisor).\n\n-export([start_link/0]).\n-export([init/1]).\n\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 1, 5}, Procs}}.\nendef\n\ndefine tpl_sys.config\n[\n].\nendef\n\ndefine tpl_top_Makefile\nPROJECT = project_name\nPROJECT_DESCRIPTION = New project\nPROJECT_VERSION = 0.1.0\ntemplate_sp\ninclude erlang.mk\nendef\n\ndefine tpl_vm.args\n-name project_name@127.0.0.1\n-setcookie project_name\n-heart\nendef\n\n\n# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\nifeq ($(filter asciideck,$(DEPS) $(DOC_DEPS)),asciideck)\n\n.PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc-guide distclean-asciidoc-manual\n\n# Core targets.\n\ndocs:: asciidoc\n\ndistclean:: distclean-asciidoc-guide distclean-asciidoc-manual\n\n# Plugin-specific targets.\n\nasciidoc: asciidoc-guide asciidoc-manual\n\n# User guide.\n\nifeq ($(wildcard doc/src/guide/book.asciidoc),)\nasciidoc-guide:\nelse\nasciidoc-guide: distclean-asciidoc-guide doc-deps\n\ta2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf\n\ta2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/\n\ndistclean-asciidoc-guide:\n\t$(gen_verbose) rm -rf doc/html/ doc/guide.pdf\nendif\n\n# Man pages.\n\nASCIIDOC_MANUAL_FILES := $(wildcard doc/src/manual/*.asciidoc)\n\nifeq ($(ASCIIDOC_MANUAL_FILES),)\nasciidoc-manual:\nelse\n\n# Configuration.\n\nMAN_INSTALL_PATH ?= /usr/local/share/man\nMAN_SECTIONS ?= 3 7\nMAN_PROJECT ?= $(shell echo $(PROJECT) | sed 's/^./\\U&\\E/')\nMAN_VERSION ?= $(PROJECT_VERSION)\n\n# Plugin-specific targets.\n\ndefine asciidoc2man.erl\ntry\n\t[begin\n\t\tio:format(\" ADOC   ~s~n\", [F]),\n\t\tok = asciideck:to_manpage(asciideck:parse_file(F), #{\n\t\t\tcompress => gzip,\n\t\t\toutdir => filename:dirname(F),\n\t\t\textra2 => \"$(MAN_PROJECT) $(MAN_VERSION)\",\n\t\t\textra3 => \"$(MAN_PROJECT) Function Reference\"\n\t\t})\n\tend || F <- [$(shell echo $(addprefix $(comma)\\\",$(addsuffix \\\",$1)) | sed 's/^.//')]],\n\thalt(0)\ncatch C:E$(if $V,:S) ->\n\tio:format(\"Exception: ~p:~p~n$(if $V,Stacktrace: ~p~n)\", [C, E$(if $V,$(comma) S)]),\n\thalt(1)\nend.\nendef\n\nasciidoc-manual:: doc-deps\n\nasciidoc-manual:: $(ASCIIDOC_MANUAL_FILES)\n\t$(gen_verbose) $(call erlang,$(call asciidoc2man.erl,$?))\n\t$(verbose) $(foreach s,$(MAN_SECTIONS),mkdir -p doc/man$s/ && mv doc/src/manual/*.$s.gz doc/man$s/;)\n\ninstall-docs:: install-asciidoc\n\ninstall-asciidoc: asciidoc-manual\n\t$(foreach s,$(MAN_SECTIONS),\\\n\t\tmkdir -p $(MAN_INSTALL_PATH)/man$s/ && \\\n\t\tinstall -g `id -g` -o `id -u` -m 0644 doc/man$s/*.gz $(MAN_INSTALL_PATH)/man$s/;)\n\ndistclean-asciidoc-manual:\n\t$(gen_verbose) rm -rf $(addprefix doc/man,$(MAN_SECTIONS))\nendif\nendif\n\n# Copyright (c) 2014-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates\n\n# Core targets.\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \"\" \\\n\t\t\"Bootstrap targets:\" \\\n\t\t\"  bootstrap          Generate a skeleton of an OTP application\" \\\n\t\t\"  bootstrap-lib      Generate a skeleton of an OTP library\" \\\n\t\t\"  bootstrap-rel      Generate the files needed to build a release\" \\\n\t\t\"  new-app in=NAME    Create a new local OTP application NAME\" \\\n\t\t\"  new-lib in=NAME    Create a new local OTP library NAME\" \\\n\t\t\"  new t=TPL n=NAME   Generate a module NAME based on the template TPL\" \\\n\t\t\"  new t=T n=N in=APP Generate a module NAME based on the template TPL in APP\" \\\n\t\t\"  list-templates     List available templates\"\n\n# Plugin-specific targets.\n\nifndef WS\nifdef SP\nWS = $(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))\nelse\nWS = $(tab)\nendif\nendif\n\nifdef SP\ndefine template_sp\n\n# By default templates indent with a single tab per indentation\n# level. Set this variable to the number of spaces you prefer:\nSP = $(SP)\n\nendef\nelse\ntemplate_sp =\nendif\n\n# @todo Additional template placeholders could be added.\nsubst_template = $(subst rel_root_dir,$(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app),$(subst rel_deps_dir,$(call core_relpath,$(DEPS_DIR),$(APPS_DIR)/app),$(subst template_sp,$(template_sp),$(subst project_name,$p,$(subst template_name,$n,$1)))))\n\ndefine core_render_template\n\t$(eval define _tpl_$(1)$(newline)$(call subst_template,$(tpl_$(1)))$(newline)endef)\n\t$(verbose) $(call core_render,_tpl_$(1),$2)\nendef\n\nbootstrap:\nifneq ($(wildcard src/),)\n\t$(error Error: src/ directory already exists)\nendif\n\t$(eval p := $(PROJECT))\n\t$(if $(shell echo $p | LC_ALL=C grep -x \"[a-z0-9_]*\"),,\\\n\t\t$(error Error: Invalid characters in the application name))\n\t$(eval n := $(PROJECT)_sup)\n\t$(verbose) $(call core_render_template,top_Makefile,Makefile)\n\t$(verbose) mkdir src/\nifdef LEGACY\n\t$(verbose) $(call core_render_template,application.app.src,src/$(PROJECT).app.src)\nendif\n\t$(verbose) $(call core_render_template,application,src/$(PROJECT)_app.erl)\n\t$(verbose) $(call core_render_template,supervisor,src/$(PROJECT)_sup.erl)\n\nbootstrap-lib:\nifneq ($(wildcard src/),)\n\t$(error Error: src/ directory already exists)\nendif\n\t$(eval p := $(PROJECT))\n\t$(if $(shell echo $p | LC_ALL=C grep -x \"[a-z0-9_]*\"),,\\\n\t\t$(error Error: Invalid characters in the application name))\n\t$(verbose) $(call core_render_template,top_Makefile,Makefile)\n\t$(verbose) mkdir src/\nifdef LEGACY\n\t$(verbose) $(call core_render_template,library.app.src,src/$(PROJECT).app.src)\nendif\n\nbootstrap-rel:\nifneq ($(wildcard relx.config),)\n\t$(error Error: relx.config already exists)\nendif\nifneq ($(wildcard config/),)\n\t$(error Error: config/ directory already exists)\nendif\n\t$(eval p := $(PROJECT))\n\t$(verbose) $(call core_render_template,relx.config,relx.config)\n\t$(verbose) mkdir config/\n\t$(verbose) $(call core_render_template,sys.config,config/sys.config)\n\t$(verbose) $(call core_render_template,vm.args,config/vm.args)\n\t$(verbose) awk '/^include erlang.mk/ && !ins {print \"REL_DEPS += relx\";ins=1};{print}' Makefile > Makefile.bak\n\t$(verbose) mv Makefile.bak Makefile\n\nnew-app:\nifndef in\n\t$(error Usage: $(MAKE) new-app in=APP)\nendif\nifneq ($(wildcard $(APPS_DIR)/$in),)\n\t$(error Error: Application $in already exists)\nendif\n\t$(eval p := $(in))\n\t$(if $(shell echo $p | LC_ALL=C grep -x \"[a-z0-9_]*\"),,\\\n\t\t$(error Error: Invalid characters in the application name))\n\t$(eval n := $(in)_sup)\n\t$(verbose) mkdir -p $(APPS_DIR)/$p/src/\n\t$(verbose) $(call core_render_template,apps_Makefile,$(APPS_DIR)/$p/Makefile)\nifdef LEGACY\n\t$(verbose) $(call core_render_template,application.app.src,$(APPS_DIR)/$p/src/$p.app.src)\nendif\n\t$(verbose) $(call core_render_template,application,$(APPS_DIR)/$p/src/$p_app.erl)\n\t$(verbose) $(call core_render_template,supervisor,$(APPS_DIR)/$p/src/$p_sup.erl)\n\nnew-lib:\nifndef in\n\t$(error Usage: $(MAKE) new-lib in=APP)\nendif\nifneq ($(wildcard $(APPS_DIR)/$in),)\n\t$(error Error: Application $in already exists)\nendif\n\t$(eval p := $(in))\n\t$(if $(shell echo $p | LC_ALL=C grep -x \"[a-z0-9_]*\"),,\\\n\t\t$(error Error: Invalid characters in the application name))\n\t$(verbose) mkdir -p $(APPS_DIR)/$p/src/\n\t$(verbose) $(call core_render_template,apps_Makefile,$(APPS_DIR)/$p/Makefile)\nifdef LEGACY\n\t$(verbose) $(call core_render_template,library.app.src,$(APPS_DIR)/$p/src/$p.app.src)\nendif\n\n# These are not necessary because we don't expose those as \"normal\" templates.\nBOOTSTRAP_TEMPLATES = apps_Makefile top_Makefile \\\n\tapplication.app.src library.app.src application \\\n\trelx.config sys.config vm.args\n\n# Templates may override the path they will be written to when using 'new'.\n# Only special template paths must be listed. Default is src/template_name.erl\n# Substitution is also applied to the paths. Examples:\n#\n#tplp_top_Makefile = Makefile\n#tplp_application.app.src = src/project_name.app.src\n#tplp_application = src/project_name_app.erl\n#tplp_relx.config = relx.config\n\n# Erlang.mk bundles its own templates at build time into the erlang.mk file.\n\nnew:\n\t$(if $(t),,$(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP]))\n\t$(if $(n),,$(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP]))\n\t$(if $(tpl_$(t)),,$(error Error: $t template does not exist; try $(Make) list-templates))\n\t$(eval dest := $(if $(in),$(APPS_DIR)/$(in)/)$(call subst_template,$(if $(tplp_$(t)),$(tplp_$(t)),src/template_name.erl)))\n\t$(if $(wildcard $(dir $(dest))),,$(error Error: $(dir $(dest)) directory does not exist))\n\t$(if $(wildcard $(dest)),$(error Error: The file $(dest) already exists))\n\t$(eval p := $(PROJECT))\n\t$(call core_render_template,$(t),$(dest))\n\nlist-templates:\n\t$(verbose) @echo Available templates:\n\t$(verbose) printf \"    %s\\n\" $(sort $(filter-out $(BOOTSTRAP_TEMPLATES),$(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES)))))\n\n# Copyright (c) 2014-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: clean-c_src distclean-c_src-env\n\n# Configuration.\n\nC_SRC_DIR ?= $(CURDIR)/c_src\nC_SRC_ENV ?= $(C_SRC_DIR)/env.mk\nC_SRC_OUTPUT ?= $(CURDIR)/priv/$(PROJECT)\nC_SRC_TYPE ?= shared\n\n# System type and C compiler/flags.\n\nifeq ($(PLATFORM),msys2)\n\tC_SRC_OUTPUT_EXECUTABLE_EXTENSION ?= .exe\n\tC_SRC_OUTPUT_SHARED_EXTENSION ?= .dll\n\tC_SRC_OUTPUT_STATIC_EXTENSION ?= .lib\nelse\n\tC_SRC_OUTPUT_EXECUTABLE_EXTENSION ?=\n\tC_SRC_OUTPUT_SHARED_EXTENSION ?= .so\n\tC_SRC_OUTPUT_STATIC_EXTENSION ?= .a\nendif\n\nifeq ($(C_SRC_TYPE),shared)\n\tC_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_SHARED_EXTENSION)\nelse ifeq ($(C_SRC_TYPE),static)\n\tC_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_STATIC_EXTENSION)\nelse\n\tC_SRC_OUTPUT_FILE = $(C_SRC_OUTPUT)$(C_SRC_OUTPUT_EXECUTABLE_EXTENSION)\nendif\n\nRANLIB ?= ranlib\nARFLAGS ?= cr\n\nifeq ($(PLATFORM),msys2)\n# We hardcode the compiler used on MSYS2. The default CC=cc does\n# not produce working code. The \"gcc\" MSYS2 package also doesn't.\n\tCC = /mingw64/bin/gcc\n\texport CC\n\tCFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes\n\tCXXFLAGS ?= -O3 -finline-functions -Wall\nelse ifeq ($(PLATFORM),darwin)\n\tCC ?= cc\n\tCFLAGS ?= -O3 -std=c99 -Wall -Wmissing-prototypes\n\tCXXFLAGS ?= -O3 -Wall\n\tLDFLAGS ?= -flat_namespace -undefined suppress\nelse ifeq ($(PLATFORM),freebsd)\n\tCC ?= cc\n\tCFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes\n\tCXXFLAGS ?= -O3 -finline-functions -Wall\nelse ifeq ($(PLATFORM),linux)\n\tCC ?= gcc\n\tCFLAGS ?= -O3 -std=c99 -finline-functions -Wall -Wmissing-prototypes\n\tCXXFLAGS ?= -O3 -finline-functions -Wall\nendif\n\nifneq ($(PLATFORM),msys2)\n\tCFLAGS += -fPIC\n\tCXXFLAGS += -fPIC\nendif\n\nifeq ($(C_SRC_TYPE),static)\n\tCFLAGS += -DSTATIC_ERLANG_NIF=1\n\tCXXFLAGS += -DSTATIC_ERLANG_NIF=1\nendif\n\nCFLAGS += -I\"$(ERTS_INCLUDE_DIR)\" -I\"$(ERL_INTERFACE_INCLUDE_DIR)\"\nCXXFLAGS += -I\"$(ERTS_INCLUDE_DIR)\" -I\"$(ERL_INTERFACE_INCLUDE_DIR)\"\n\nLDLIBS += -L\"$(ERL_INTERFACE_LIB_DIR)\" -lei\n\n# Verbosity.\n\nc_verbose_0 = @echo \" C     \" $(filter-out $(notdir $(MAKEFILE_LIST) $(C_SRC_ENV)),$(^F));\nc_verbose = $(c_verbose_$(V))\n\ncpp_verbose_0 = @echo \" CPP   \" $(filter-out $(notdir $(MAKEFILE_LIST) $(C_SRC_ENV)),$(^F));\ncpp_verbose = $(cpp_verbose_$(V))\n\nlink_verbose_0 = @echo \" LD    \" $(@F);\nlink_verbose = $(link_verbose_$(V))\n\nar_verbose_0 = @echo \" AR    \" $(@F);\nar_verbose = $(ar_verbose_$(V))\n\nranlib_verbose_0 = @echo \" RANLIB\" $(@F);\nranlib_verbose = $(ranlib_verbose_$(V))\n\n# Targets.\n\nifeq ($(wildcard $(C_SRC_DIR)),)\nelse ifneq ($(wildcard $(C_SRC_DIR)/Makefile),)\napp:: app-c_src\n\ntest-build:: app-c_src\n\napp-c_src:\n\t$(MAKE) -C $(C_SRC_DIR)\n\nclean::\n\t$(MAKE) -C $(C_SRC_DIR) clean\n\nelse\n\nifeq ($(SOURCES),)\nSOURCES := $(sort $(foreach pat,*.c *.C *.cc *.cpp,$(call core_find,$(C_SRC_DIR)/,$(pat))))\nendif\nOBJECTS = $(addsuffix .o, $(basename $(SOURCES)))\n\nCOMPILE_C = $(c_verbose) $(CC) $(CFLAGS) $(CPPFLAGS) -c\nCOMPILE_CPP = $(cpp_verbose) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c\n\napp:: $(C_SRC_ENV) $(C_SRC_OUTPUT_FILE)\n\ntest-build:: $(C_SRC_ENV) $(C_SRC_OUTPUT_FILE)\n\nifneq ($(C_SRC_TYPE),static)\n$(C_SRC_OUTPUT_FILE): $(OBJECTS)\n\t$(verbose) mkdir -p $(dir $@)\n\t$(link_verbose) $(CC) $(OBJECTS) \\\n\t\t$(LDFLAGS) $(if $(filter $(C_SRC_TYPE),shared),-shared) $(LDLIBS) \\\n\t\t-o $(C_SRC_OUTPUT_FILE)\nelse\n$(C_SRC_OUTPUT_FILE): $(OBJECTS)\n\t$(verbose) mkdir -p $(dir $@)\n\t$(ar_verbose) $(AR) $(ARFLAGS) $(C_SRC_OUTPUT_FILE) $(OBJECTS)\n\t$(ranlib_verbose) $(RANLIB) $(C_SRC_OUTPUT_FILE)\nendif\n\n\n$(OBJECTS): $(MAKEFILE_LIST) $(C_SRC_ENV)\n\n%.o: %.c\n\t$(COMPILE_C) $(OUTPUT_OPTION) $<\n\n%.o: %.cc\n\t$(COMPILE_CPP) $(OUTPUT_OPTION) $<\n\n%.o: %.C\n\t$(COMPILE_CPP) $(OUTPUT_OPTION) $<\n\n%.o: %.cpp\n\t$(COMPILE_CPP) $(OUTPUT_OPTION) $<\n\nclean:: clean-c_src\n\nclean-c_src:\n\t$(gen_verbose) rm -f $(C_SRC_OUTPUT_FILE) $(OBJECTS)\n\nendif\n\nifneq ($(wildcard $(C_SRC_DIR)),)\nERL_ERTS_DIR = $(shell $(ERL) -eval 'io:format(\"~s~n\", [code:lib_dir(erts)]), halt().')\n\n$(C_SRC_ENV):\n\t$(verbose) $(ERL) -eval \"file:write_file(\\\"$(call core_native_path,$(C_SRC_ENV))\\\", \\\n\t\tio_lib:format( \\\n\t\t\t\\\"# Generated by Erlang.mk. Edit at your own risk!~n~n\\\" \\\n\t\t\t\\\"ERTS_INCLUDE_DIR ?= ~s/erts-~s/include/~n\\\" \\\n\t\t\t\\\"ERL_INTERFACE_INCLUDE_DIR ?= ~s~n\\\" \\\n\t\t\t\\\"ERL_INTERFACE_LIB_DIR ?= ~s~n\\\" \\\n\t\t\t\\\"ERTS_DIR ?= $(ERL_ERTS_DIR)~n\\\", \\\n\t\t\t[code:root_dir(), erlang:system_info(version), \\\n\t\t\tcode:lib_dir(erl_interface, include), \\\n\t\t\tcode:lib_dir(erl_interface, lib)])), \\\n\t\thalt().\"\n\ndistclean:: distclean-c_src-env\n\ndistclean-c_src-env:\n\t$(gen_verbose) rm -f $(C_SRC_ENV)\n\n-include $(C_SRC_ENV)\n\nifneq ($(ERL_ERTS_DIR),$(ERTS_DIR))\n$(shell rm -f $(C_SRC_ENV))\nendif\nendif\n\n# Templates.\n\ndefine bs_c_nif\n#include \"erl_nif.h\"\n\nstatic int loads = 0;\n\nstatic int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)\n{\n\t/* Initialize private data. */\n\t*priv_data = NULL;\n\n\tloads++;\n\n\treturn 0;\n}\n\nstatic int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)\n{\n\t/* Convert the private data to the new version. */\n\t*priv_data = *old_priv_data;\n\n\tloads++;\n\n\treturn 0;\n}\n\nstatic void unload(ErlNifEnv* env, void* priv_data)\n{\n\tif (loads == 1) {\n\t\t/* Destroy the private data. */\n\t}\n\n\tloads--;\n}\n\nstatic ERL_NIF_TERM hello(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])\n{\n\tif (enif_is_atom(env, argv[0])) {\n\t\treturn enif_make_tuple2(env,\n\t\t\tenif_make_atom(env, \"hello\"),\n\t\t\targv[0]);\n\t}\n\n\treturn enif_make_tuple2(env,\n\t\tenif_make_atom(env, \"error\"),\n\t\tenif_make_atom(env, \"badarg\"));\n}\n\nstatic ErlNifFunc nif_funcs[] = {\n\t{\"hello\", 1, hello}\n};\n\nERL_NIF_INIT($n, nif_funcs, load, NULL, upgrade, unload)\nendef\n\ndefine bs_erl_nif\n-module($n).\n\n-export([hello/1]).\n\n-on_load(on_load/0).\non_load() ->\n\tPrivDir = case code:priv_dir(?MODULE) of\n\t\t{error, _} ->\n\t\t\tAppPath = filename:dirname(filename:dirname(code:which(?MODULE))),\n\t\t\tfilename:join(AppPath, \"priv\");\n\t\tPath ->\n\t\t\tPath\n\tend,\n\terlang:load_nif(filename:join(PrivDir, atom_to_list(?MODULE)), 0).\n\nhello(_) ->\n\terlang:nif_error({not_loaded, ?MODULE}).\nendef\n\nnew-nif:\nifneq ($(wildcard $(C_SRC_DIR)/$n.c),)\n\t$(error Error: $(C_SRC_DIR)/$n.c already exists)\nendif\nifneq ($(wildcard src/$n.erl),)\n\t$(error Error: src/$n.erl already exists)\nendif\nifndef n\n\t$(error Usage: $(MAKE) new-nif n=NAME [in=APP])\nendif\nifdef in\n\t$(verbose) $(MAKE) -C $(APPS_DIR)/$(in)/ new-nif n=$n in=\nelse\n\t$(verbose) mkdir -p $(C_SRC_DIR) src/\n\t$(verbose) $(call core_render,bs_c_nif,$(C_SRC_DIR)/$n.c)\n\t$(verbose) $(call core_render,bs_erl_nif,src/$n.erl)\nendif\n\n# Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: ci ci-prepare ci-setup\n\nCI_OTP ?=\n\nifeq ($(strip $(CI_OTP)),)\nci::\nelse\n\nci:: $(addprefix ci-,$(CI_OTP))\n\nci-prepare: $(addprefix ci-prepare-,$(CI_OTP))\n\nci-setup::\n\t$(verbose) :\n\nci-extra::\n\t$(verbose) :\n\nci_verbose_0 = @echo \" CI    \" $1;\nci_verbose = $(ci_verbose_$(V))\n\ndefine ci_target\nci-prepare-$1: $(KERL_INSTALL_DIR)/$2\n\t$(verbose) :\n\nci-$1: ci-prepare-$1\n\t$(verbose) $(MAKE) --no-print-directory clean\n\t$(ci_verbose) \\\n\t\tPATH=\"$(KERL_INSTALL_DIR)/$2/bin:$(PATH)\" \\\n\t\tCI_OTP_RELEASE=\"$1\" \\\n\t\tCT_OPTS=\"-label $1\" \\\n\t\tCI_VM=\"$3\" \\\n\t\t$(MAKE) ci-setup tests\n\t$(verbose) $(MAKE) --no-print-directory ci-extra\nendef\n\n$(foreach otp,$(CI_OTP),$(eval $(call ci_target,$(otp),$(otp),otp)))\n\n$(foreach otp,$(filter-out $(ERLANG_OTP),$(CI_OTP)),$(eval $(call kerl_otp_target,$(otp))))\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \"\" \\\n\t\t\"Continuous Integration targets:\" \\\n\t\t\"  ci          Run '$(MAKE) tests' on all configured Erlang versions.\" \\\n\t\t\"\" \\\n\t\t\"The CI_OTP variable must be defined with the Erlang versions\" \\\n\t\t\"that must be tested. For example: CI_OTP = OTP-17.3.4 OTP-17.5.3\"\n\nendif\n\n# Copyright (c) 2020, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\nifdef CONCUERROR_TESTS\n\n.PHONY: concuerror distclean-concuerror\n\n# Configuration\n\nCONCUERROR_LOGS_DIR ?= $(CURDIR)/logs\nCONCUERROR_OPTS ?=\n\n# Core targets.\n\ncheck:: concuerror\n\nifndef KEEP_LOGS\ndistclean:: distclean-concuerror\nendif\n\n# Plugin-specific targets.\n\n$(ERLANG_MK_TMP)/Concuerror/bin/concuerror: | $(ERLANG_MK_TMP)\n\t$(verbose) git clone https://github.com/parapluu/Concuerror $(ERLANG_MK_TMP)/Concuerror\n\t$(verbose) $(MAKE) -C $(ERLANG_MK_TMP)/Concuerror\n\n$(CONCUERROR_LOGS_DIR):\n\t$(verbose) mkdir -p $(CONCUERROR_LOGS_DIR)\n\ndefine concuerror_html_report\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Concuerror HTML report</title>\n</head>\n<body>\n<h1>Concuerror HTML report</h1>\n<p>Generated on $(concuerror_date)</p>\n<ul>\n$(foreach t,$(concuerror_targets),<li><a href=\"$(t).txt\">$(t)</a></li>)\n</ul>\n</body>\n</html>\nendef\n\nconcuerror: $(addprefix concuerror-,$(subst :,-,$(CONCUERROR_TESTS)))\n\t$(eval concuerror_date := $(shell date))\n\t$(eval concuerror_targets := $^)\n\t$(verbose) $(call core_render,concuerror_html_report,$(CONCUERROR_LOGS_DIR)/concuerror.html)\n\ndefine concuerror_target\n.PHONY: concuerror-$1-$2\n\nconcuerror-$1-$2: test-build | $(ERLANG_MK_TMP)/Concuerror/bin/concuerror $(CONCUERROR_LOGS_DIR)\n\t$(ERLANG_MK_TMP)/Concuerror/bin/concuerror \\\n\t\t--pa $(CURDIR)/ebin --pa $(TEST_DIR) \\\n\t\t-o $(CONCUERROR_LOGS_DIR)/concuerror-$1-$2.txt \\\n\t\t$$(CONCUERROR_OPTS) -m $1 -t $2\nendef\n\n$(foreach test,$(CONCUERROR_TESTS),$(eval $(call concuerror_target,$(firstword $(subst :, ,$(test))),$(lastword $(subst :, ,$(test))))))\n\ndistclean-concuerror:\n\t$(gen_verbose) rm -rf $(CONCUERROR_LOGS_DIR)\n\nendif\n\n# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: ct apps-ct distclean-ct\n\n# Configuration.\n\nCT_OPTS ?=\n\nifneq ($(wildcard $(TEST_DIR)),)\nifndef CT_SUITES\nCT_SUITES := $(sort $(subst _SUITE.erl,,$(notdir $(call core_find,$(TEST_DIR)/,*_SUITE.erl))))\nendif\nendif\nCT_SUITES ?=\nCT_LOGS_DIR ?= $(CURDIR)/logs\n\n# Core targets.\n\ntests:: ct\n\nifndef KEEP_LOGS\ndistclean:: distclean-ct\nendif\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \"\" \\\n\t\t\"Common_test targets:\" \\\n\t\t\"  ct          Run all the common_test suites for this project\" \\\n\t\t\"\" \\\n\t\t\"All your common_test suites have their associated targets.\" \\\n\t\t\"A suite named http_SUITE can be ran using the ct-http target.\"\n\n# Plugin-specific targets.\n\nCT_RUN = ct_run \\\n\t-no_auto_compile \\\n\t-noinput \\\n\t-pa $(CURDIR)/ebin $(TEST_DIR) \\\n\t-dir $(TEST_DIR) \\\n\t-logdir $(CT_LOGS_DIR)\n\nifeq ($(CT_SUITES),)\nct: $(if $(IS_APP)$(ROOT_DIR),,apps-ct)\nelse\n# We do not run tests if we are in an apps/* with no test directory.\nifneq ($(IS_APP)$(wildcard $(TEST_DIR)),1)\nct: test-build $(if $(IS_APP)$(ROOT_DIR),,apps-ct)\n\t$(verbose) mkdir -p $(CT_LOGS_DIR)\n\t$(gen_verbose) $(CT_RUN) -sname ct_$(PROJECT) -suite $(addsuffix _SUITE,$(CT_SUITES)) $(CT_OPTS)\nendif\nendif\n\nifneq ($(ALL_APPS_DIRS),)\ndefine ct_app_target\napps-ct-$1: test-build\n\t$$(MAKE) -C $1 ct IS_APP=1\nendef\n\n$(foreach app,$(ALL_APPS_DIRS),$(eval $(call ct_app_target,$(app))))\n\napps-ct: $(addprefix apps-ct-,$(ALL_APPS_DIRS))\nendif\n\nifdef t\nifeq (,$(findstring :,$t))\nCT_EXTRA = -group $t\nelse\nt_words = $(subst :, ,$t)\nCT_EXTRA = -group $(firstword $(t_words)) -case $(lastword $(t_words))\nendif\nelse\nifdef c\nCT_EXTRA = -case $c\nelse\nCT_EXTRA =\nendif\nendif\n\ndefine ct_suite_target\nct-$1: test-build\n\t$$(verbose) mkdir -p $$(CT_LOGS_DIR)\n\t$$(gen_verbose_esc) $$(CT_RUN) -sname ct_$$(PROJECT) -suite $$(addsuffix _SUITE,$1) $$(CT_EXTRA) $$(CT_OPTS)\nendef\n\n$(foreach test,$(CT_SUITES),$(eval $(call ct_suite_target,$(test))))\n\ndistclean-ct:\n\t$(gen_verbose) rm -rf $(CT_LOGS_DIR)\n\n# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: plt distclean-plt dialyze\n\n# Configuration.\n\nDIALYZER_PLT ?= $(CURDIR)/.$(PROJECT).plt\nexport DIALYZER_PLT\n\nPLT_APPS ?=\nDIALYZER_DIRS ?= --src -r $(wildcard src) $(ALL_APPS_DIRS)\nDIALYZER_OPTS ?= -Werror_handling -Wunmatched_returns # -Wunderspecs\nDIALYZER_PLT_OPTS ?=\n\n# Core targets.\n\ncheck:: dialyze\n\ndistclean:: distclean-plt\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \"\" \\\n\t\t\"Dialyzer targets:\" \\\n\t\t\"  plt         Build a PLT file for this project\" \\\n\t\t\"  dialyze     Analyze the project using Dialyzer\"\n\n# Plugin-specific targets.\n\ndefine filter_opts.erl\n\tOpts = init:get_plain_arguments(),\n\t{Filtered, _} = lists:foldl(fun\n\t\t(O,                         {Os, true}) -> {[O|Os], false};\n\t\t(O = \"-D\",                  {Os, _})    -> {[O|Os], true};\n\t\t(O = [\\\\$$-, \\\\$$D, _ | _], {Os, _})    -> {[O|Os], false};\n\t\t(O = \"-I\",                  {Os, _})    -> {[O|Os], true};\n\t\t(O = [\\\\$$-, \\\\$$I, _ | _], {Os, _})    -> {[O|Os], false};\n\t\t(O = \"-pa\",                 {Os, _})    -> {[O|Os], true};\n\t\t(_,                         Acc)        -> Acc\n\tend, {[], false}, Opts),\n\tio:format(\"~s~n\", [string:join(lists:reverse(Filtered), \" \")]),\n\thalt().\nendef\n\n# DIALYZER_PLT is a variable understood directly by Dialyzer.\n#\n# We append the path to erts at the end of the PLT. This works\n# because the PLT file is in the external term format and the\n# function binary_to_term/1 ignores any trailing data.\n$(DIALYZER_PLT): deps app\n\t$(eval DEPS_LOG := $(shell test -f $(ERLANG_MK_TMP)/deps.log && \\\n\t\twhile read p; do test -d $$p/ebin && echo $$p/ebin; done <$(ERLANG_MK_TMP)/deps.log))\n\t$(verbose) dialyzer --build_plt $(DIALYZER_PLT_OPTS) --apps \\\n\t\terts kernel stdlib $(PLT_APPS) $(OTP_DEPS) $(LOCAL_DEPS) $(DEPS_LOG) || test $$? -eq 2\n\t$(verbose) $(ERL) -eval 'io:format(\"~n~s~n\", [code:lib_dir(erts)]), halt().' >> $@\n\nplt: $(DIALYZER_PLT)\n\ndistclean-plt:\n\t$(gen_verbose) rm -f $(DIALYZER_PLT)\n\nifneq ($(wildcard $(DIALYZER_PLT)),)\ndialyze: $(if $(filter --src,$(DIALYZER_DIRS)),,deps app)\n\t$(verbose) if ! tail -n1 $(DIALYZER_PLT) | \\\n\t\tgrep -q \"^`$(ERL) -eval 'io:format(\"~s\", [code:lib_dir(erts)]), halt().'`$$\"; then \\\n\t\trm $(DIALYZER_PLT); \\\n\t\t$(MAKE) plt; \\\n\tfi\nelse\ndialyze: $(DIALYZER_PLT)\nendif\n\t$(verbose) dialyzer `$(ERL) \\\n\t\t-eval \"$(subst $(newline),,$(call escape_dquotes,$(call filter_opts.erl)))\" \\\n\t\t-extra $(ERLC_OPTS)` $(DIALYZER_DIRS) $(DIALYZER_OPTS) $(if $(wildcard ebin/),-pa ebin/)\n\n# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: distclean-edoc edoc\n\n# Configuration.\n\nEDOC_OPTS ?=\nEDOC_SRC_DIRS ?=\nEDOC_OUTPUT ?= doc\n\ndefine edoc.erl\n\tSrcPaths = lists:foldl(fun(P, Acc) ->\n\t\tfilelib:wildcard(atom_to_list(P) ++ \"/{src,c_src}\")\n\t\t++ lists:filter(fun(D) ->\n\t\t\tfilelib:is_dir(D)\n\t\tend, filelib:wildcard(atom_to_list(P) ++ \"/{src,c_src}/**\"))\n\t\t++ Acc\n\tend, [], [$(call comma_list,$(patsubst %,'%',$(call core_native_path,$(EDOC_SRC_DIRS))))]),\n\tDefaultOpts = [{dir, \"$(EDOC_OUTPUT)\"}, {source_path, SrcPaths}, {subpackages, false}],\n\tedoc:application($(1), \".\", [$(2)] ++ DefaultOpts),\n\thalt(0).\nendef\n\n# Core targets.\n\nifneq ($(strip $(EDOC_SRC_DIRS)$(wildcard doc/overview.edoc)),)\ndocs:: edoc\nendif\n\ndistclean:: distclean-edoc\n\n# Plugin-specific targets.\n\nedoc: distclean-edoc doc-deps\n\t$(gen_verbose) $(call erlang,$(call edoc.erl,$(PROJECT),$(EDOC_OPTS)))\n\ndistclean-edoc:\n\t$(gen_verbose) rm -f $(EDOC_OUTPUT)/*.css $(EDOC_OUTPUT)/*.html $(EDOC_OUTPUT)/*.png $(EDOC_OUTPUT)/edoc-info\n\n# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n# Configuration.\n\nDTL_FULL_PATH ?=\nDTL_PATH ?= templates/\nDTL_PREFIX ?=\nDTL_SUFFIX ?= _dtl\nDTL_OPTS ?=\n\n# Verbosity.\n\ndtl_verbose_0 = @echo \" DTL   \" $(filter %.dtl,$(?F));\ndtl_verbose = $(dtl_verbose_$(V))\n\n# Core targets.\n\nDTL_PATH := $(abspath $(DTL_PATH))\nDTL_FILES := $(sort $(call core_find,$(DTL_PATH),*.dtl))\n\nifneq ($(DTL_FILES),)\n\nDTL_NAMES   = $(addprefix $(DTL_PREFIX),$(addsuffix $(DTL_SUFFIX),$(DTL_FILES:$(DTL_PATH)/%.dtl=%)))\nDTL_MODULES = $(if $(DTL_FULL_PATH),$(subst /,_,$(DTL_NAMES)),$(notdir $(DTL_NAMES)))\nBEAM_FILES += $(addsuffix .beam,$(addprefix ebin/,$(DTL_MODULES)))\n\nifneq ($(words $(DTL_FILES)),0)\n# Rebuild templates when the Makefile changes.\n$(ERLANG_MK_TMP)/last-makefile-change-erlydtl: $(MAKEFILE_LIST) | $(ERLANG_MK_TMP)\n\t$(verbose) if test -f $@; then \\\n\t\ttouch $(DTL_FILES); \\\n\tfi\n\t$(verbose) touch $@\n\nebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change-erlydtl\nendif\n\ndefine erlydtl_compile.erl\n\t[begin\n\t\tModule0 = case \"$(strip $(DTL_FULL_PATH))\" of\n\t\t\t\"\" ->\n\t\t\t\tfilename:basename(F, \".dtl\");\n\t\t\t_ ->\n\t\t\t\t\"$(call core_native_path,$(DTL_PATH))/\" ++ F2 = filename:rootname(F, \".dtl\"),\n\t\t\t\tre:replace(F2, \"/\",  \"_\",  [{return, list}, global])\n\t\tend,\n\t\tModule = list_to_atom(\"$(DTL_PREFIX)\" ++ string:to_lower(Module0) ++ \"$(DTL_SUFFIX)\"),\n\t\tcase erlydtl:compile(F, Module, [$(DTL_OPTS)] ++ [{out_dir, \"ebin/\"}, return_errors]) of\n\t\t\tok -> ok;\n\t\t\t{ok, _} -> ok\n\t\tend\n\tend || F <- string:tokens(\"$(1)\", \" \")],\n\thalt().\nendef\n\nebin/$(PROJECT).app:: $(DTL_FILES) | ebin/\n\t$(if $(strip $?),\\\n\t\t$(dtl_verbose) $(call erlang,$(call erlydtl_compile.erl,$(call core_native_path,$?)),\\\n\t\t\t-pa ebin/))\n\nendif\n\n# Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>\n# Copyright (c) 2014, Dave Cottlehuber <dch@skunkwerks.at>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: distclean-escript escript escript-zip\n\n# Configuration.\n\nESCRIPT_NAME ?= $(PROJECT)\nESCRIPT_FILE ?= $(ESCRIPT_NAME)\n\nESCRIPT_SHEBANG ?= /usr/bin/env escript\nESCRIPT_COMMENT ?= This is an -*- erlang -*- file\nESCRIPT_EMU_ARGS ?= -escript main $(ESCRIPT_NAME)\n\nESCRIPT_ZIP ?= 7z a -tzip -mx=9 -mtc=off $(if $(filter-out 0,$(V)),,> /dev/null)\nESCRIPT_ZIP_FILE ?= $(ERLANG_MK_TMP)/escript.zip\n\n# Core targets.\n\ndistclean:: distclean-escript\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \"\" \\\n\t\t\"Escript targets:\" \\\n\t\t\"  escript     Build an executable escript archive\" \\\n\n# Plugin-specific targets.\n\nALL_ESCRIPT_DEPS_DIRS = $(LOCAL_DEPS_DIRS) $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(DEPS)),$(call query_name,$(dep))))\n\nESCRIPT_RUNTIME_DEPS_FILE ?= $(ERLANG_MK_TMP)/escript-deps.log\n\nescript-list-runtime-deps:\nifeq ($(IS_DEP),)\n\t$(verbose) rm -f $(ESCRIPT_RUNTIME_DEPS_FILE)\nendif\n\t$(verbose) touch $(ESCRIPT_RUNTIME_DEPS_FILE)\n\t$(verbose) set -e; for dep in $(ALL_ESCRIPT_DEPS_DIRS) ; do \\\n\t\tif ! grep -qs ^$$dep$$ $(ESCRIPT_RUNTIME_DEPS_FILE); then \\\n\t\t\techo $$dep >> $(ESCRIPT_RUNTIME_DEPS_FILE); \\\n\t\t\tif grep -qs -E \"^[[:blank:]]*include[[:blank:]]+(erlang\\.mk|.*/erlang\\.mk|.*ERLANG_MK_FILENAME.*)$$\" \\\n\t\t\t $$dep/GNUmakefile $$dep/makefile $$dep/Makefile; then \\\n\t\t\t\t$(MAKE) -C $$dep escript-list-runtime-deps \\\n\t\t\t\t IS_DEP=1 \\\n\t\t\t\t ESCRIPT_RUNTIME_DEPS_FILE=$(ESCRIPT_RUNTIME_DEPS_FILE); \\\n\t\t\tfi \\\n\t\tfi \\\n\tdone\nifeq ($(IS_DEP),)\n\t$(verbose) sort < $(ESCRIPT_RUNTIME_DEPS_FILE) | uniq > $(ESCRIPT_RUNTIME_DEPS_FILE).sorted\n\t$(verbose) mv $(ESCRIPT_RUNTIME_DEPS_FILE).sorted $(ESCRIPT_RUNTIME_DEPS_FILE)\nendif\n\nescript-prepare: deps app\n\t$(MAKE) escript-list-runtime-deps\n\nescript-zip:: escript-prepare\n\t$(verbose) mkdir -p $(dir $(abspath $(ESCRIPT_ZIP_FILE)))\n\t$(verbose) rm -f $(abspath $(ESCRIPT_ZIP_FILE))\n\t$(gen_verbose) cd .. && $(ESCRIPT_ZIP) $(abspath $(ESCRIPT_ZIP_FILE)) $(notdir $(CURDIR))/ebin/*\nifneq ($(DEPS),)\n\t$(verbose) cd $(DEPS_DIR) && $(ESCRIPT_ZIP) $(abspath $(ESCRIPT_ZIP_FILE)) \\\n\t\t$(subst $(DEPS_DIR)/,,$(addsuffix /*,$(wildcard \\\n\t\t$(addsuffix /ebin,$(shell cat $(ESCRIPT_RUNTIME_DEPS_FILE))))))\nendif\n\n# @todo Only generate the zip file if there were changes.\nescript:: escript-zip\n\t$(gen_verbose) printf \"%s\\n\" \\\n\t\t\"#!$(ESCRIPT_SHEBANG)\" \\\n\t\t\"%% $(ESCRIPT_COMMENT)\" \\\n\t\t\"%%! $(ESCRIPT_EMU_ARGS)\" > $(ESCRIPT_FILE)\n\t$(verbose) cat $(abspath $(ESCRIPT_ZIP_FILE)) >> $(ESCRIPT_FILE)\n\t$(verbose) chmod +x $(ESCRIPT_FILE)\n\ndistclean-escript:\n\t$(gen_verbose) rm -f $(ESCRIPT_FILE) $(abspath $(ESCRIPT_ZIP_FILE))\n\n# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>\n# Copyright (c) 2014, Enrique Fernandez <enrique.fernandez@erlang-solutions.com>\n# This file is contributed to erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: eunit apps-eunit\n\n# Eunit can be disabled by setting this to any other value.\nEUNIT ?= system\n\nifeq ($(EUNIT),system)\n\n# Configuration\n\nEUNIT_OPTS ?=\nEUNIT_ERL_OPTS ?=\nEUNIT_TEST_SPEC ?= $1\n\n# Core targets.\n\ntests:: eunit\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \"\" \\\n\t\t\"EUnit targets:\" \\\n\t\t\"  eunit       Run all the EUnit tests for this project\"\n\n# Plugin-specific targets.\n\ndefine eunit.erl\n\t$(call cover.erl)\n\tCoverSetup(),\n\tcase eunit:test($(call EUNIT_TEST_SPEC,$1), [$(EUNIT_OPTS)]) of\n\t\tok -> ok;\n\t\terror -> halt(2)\n\tend,\n\tCoverExport(\"$(call core_native_path,$(COVER_DATA_DIR))/eunit.coverdata\"),\n\thalt()\nendef\n\nEUNIT_ERL_OPTS += -pa $(TEST_DIR) $(CURDIR)/ebin\n\nifdef t\nifeq (,$(findstring :,$(t)))\neunit: test-build cover-data-dir\n\t$(gen_verbose) $(call erlang,$(call eunit.erl,['$(t)']),$(EUNIT_ERL_OPTS))\nelse\neunit: test-build cover-data-dir\n\t$(gen_verbose) $(call erlang,$(call eunit.erl,fun $(t)/0),$(EUNIT_ERL_OPTS))\nendif\nelse\nEUNIT_EBIN_MODS = $(notdir $(basename $(ERL_FILES) $(BEAM_FILES)))\nEUNIT_TEST_MODS = $(notdir $(basename $(call core_find,$(TEST_DIR)/,*.erl)))\n\nEUNIT_MODS = $(foreach mod,$(EUNIT_EBIN_MODS) $(filter-out \\\n\t$(patsubst %,%_tests,$(EUNIT_EBIN_MODS)),$(EUNIT_TEST_MODS)),'$(mod)')\n\neunit: test-build $(if $(IS_APP)$(ROOT_DIR),,apps-eunit) cover-data-dir\nifneq ($(wildcard src/ $(TEST_DIR)),)\n\t$(gen_verbose) $(call erlang,$(call eunit.erl,[$(call comma_list,$(EUNIT_MODS))]),$(EUNIT_ERL_OPTS))\nendif\n\nifneq ($(ALL_APPS_DIRS),)\napps-eunit: test-build\n\t$(verbose) eunit_retcode=0 ; for app in $(ALL_APPS_DIRS); do $(MAKE) -C $$app eunit IS_APP=1; \\\n\t\t[ $$? -ne 0 ] && eunit_retcode=1 ; done ; \\\n\t\texit $$eunit_retcode\nendif\nendif\n\nendif\n\n# Copyright (c) 2020, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\ndefine hex_user_create.erl\n\t{ok, _} = application:ensure_all_started(ssl),\n\t{ok, _} = application:ensure_all_started(inets),\n\tConfig = $(hex_config.erl),\n\tcase hex_api_user:create(Config, <<\"$(strip $1)\">>, <<\"$(strip $2)\">>, <<\"$(strip $3)\">>) of\n\t\t{ok, {201, _, #{<<\"email\">> := Email, <<\"url\">> := URL, <<\"username\">> := Username}}} ->\n\t\t\tio:format(\"User ~s (~s) created at ~s~n\"\n\t\t\t\t\"Please check your inbox for a confirmation email.~n\"\n\t\t\t\t\"You must confirm before you are allowed to publish packages.~n\",\n\t\t\t\t[Username, Email, URL]),\n\t\t\thalt(0);\n\t\t{ok, {Status, _, Errors}} ->\n\t\t\tio:format(\"Error ~b: ~0p~n\", [Status, Errors]),\n\t\t\thalt(80)\n\tend\nendef\n\n# The $(info ) call inserts a new line after the password prompt.\nhex-user-create: $(DEPS_DIR)/hex_core/ebin/dep_built\n\t$(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p \"Username: \" username; echo $$username)))\n\t$(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p \"Password: \" password; stty echo; echo $$password) $(info )))\n\t$(if $(HEX_EMAIL),,$(eval HEX_EMAIL := $(shell read -p \"Email: \" email; echo $$email)))\n\t$(gen_verbose) $(call erlang,$(call hex_user_create.erl,$(HEX_USERNAME),$(HEX_PASSWORD),$(HEX_EMAIL)))\n\ndefine hex_key_add.erl\n\t{ok, _} = application:ensure_all_started(ssl),\n\t{ok, _} = application:ensure_all_started(inets),\n\tConfig = $(hex_config.erl),\n\tConfigF = Config#{api_key => iolist_to_binary([<<\"Basic \">>, base64:encode(<<\"$(strip $1):$(strip $2)\">>)])},\n\tPermissions = [\n\t\tcase string:split(P, <<\":\">>) of\n\t\t\t[D] -> #{domain => D};\n\t\t\t[D, R] -> #{domain => D, resource => R}\n\t\tend\n\t|| P <- string:split(<<\"$(strip $4)\">>, <<\",\">>, all)],\n\tcase hex_api_key:add(ConfigF, <<\"$(strip $3)\">>, Permissions) of\n\t\t{ok, {201, _, #{<<\"secret\">> := Secret}}} ->\n\t\t\tio:format(\"Key ~s created for user ~s~nSecret: ~s~n\"\n\t\t\t\t\"Please store the secret in a secure location, such as a password store.~n\"\n\t\t\t\t\"The secret will be requested for most Hex-related operations.~n\",\n\t\t\t\t[<<\"$(strip $3)\">>, <<\"$(strip $1)\">>, Secret]),\n\t\t\thalt(0);\n\t\t{ok, {Status, _, Errors}} ->\n\t\t\tio:format(\"Error ~b: ~0p~n\", [Status, Errors]),\n\t\t\thalt(81)\n\tend\nendef\n\nhex-key-add: $(DEPS_DIR)/hex_core/ebin/dep_built\n\t$(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p \"Username: \" username; echo $$username)))\n\t$(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p \"Password: \" password; stty echo; echo $$password) $(info )))\n\t$(gen_verbose) $(call erlang,$(call hex_key_add.erl,$(HEX_USERNAME),$(HEX_PASSWORD),\\\n\t\t$(if $(name),$(name),$(shell hostname)-erlang-mk),\\\n\t\t$(if $(perm),$(perm),api)))\n\nHEX_TARBALL_EXTRA_METADATA ?=\n\n# @todo Check that we can += files\nHEX_TARBALL_FILES ?= \\\n\t$(wildcard early-plugins.mk) \\\n\t$(wildcard ebin/$(PROJECT).app) \\\n\t$(wildcard ebin/$(PROJECT).appup) \\\n\t$(wildcard $(notdir $(ERLANG_MK_FILENAME))) \\\n\t$(sort $(call core_find,include/,*.hrl)) \\\n\t$(wildcard LICENSE*) \\\n\t$(wildcard Makefile) \\\n\t$(wildcard plugins.mk) \\\n\t$(sort $(call core_find,priv/,*)) \\\n\t$(wildcard README*) \\\n\t$(wildcard rebar.config) \\\n\t$(sort $(if $(LEGACY),$(filter-out src/$(PROJECT).app.src,$(call core_find,src/,*)),$(call core_find,src/,*)))\n\nHEX_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT).tar\n\n# @todo Need to check for rebar.config and/or the absence of DEPS to know\n# whether a project will work with Rebar.\n#\n# @todo contributors licenses links in HEX_TARBALL_EXTRA_METADATA\n\n# In order to build the requirements metadata we look into DEPS.\n# We do not require that the project use Hex dependencies, however\n# Hex.pm does require that the package name and version numbers\n# correspond to a real Hex package.\ndefine hex_tarball_create.erl\n\tFiles0 = [$(call comma_list,$(patsubst %,\"%\",$(HEX_TARBALL_FILES)))],\n\tRequirements0 = #{\n\t\t$(foreach d,$(DEPS),\n\t\t\t<<\"$(if $(subst hex,,$(call query_fetch_method,$d)),$d,$(if $(word 3,$(dep_$d)),$(word 3,$(dep_$d)),$d))\">> => #{\n\t\t\t\t<<\"app\">> => <<\"$d\">>,\n\t\t\t\t<<\"optional\">> => false,\n\t\t\t\t<<\"requirement\">> => <<\"$(if $(hex_req_$d),$(strip $(hex_req_$d)),$(call query_version,$d))\">>\n\t\t\t},)\n\t\t$(if $(DEPS),dummy => dummy)\n\t},\n\tRequirements = maps:remove(dummy, Requirements0),\n\tMetadata0 = #{\n\t\tapp => <<\"$(strip $(PROJECT))\">>,\n\t\tbuild_tools => [<<\"make\">>, <<\"rebar3\">>],\n\t\tdescription => <<\"$(strip $(PROJECT_DESCRIPTION))\">>,\n\t\tfiles => [unicode:characters_to_binary(F) || F <- Files0],\n\t\tname => <<\"$(strip $(PROJECT))\">>,\n\t\trequirements => Requirements,\n\t\tversion => <<\"$(strip $(PROJECT_VERSION))\">>\n\t},\n\tMetadata = Metadata0$(HEX_TARBALL_EXTRA_METADATA),\n\tFiles = [case file:read_file(F) of\n\t\t{ok, Bin} ->\n\t\t\t{F, Bin};\n\t\t{error, Reason} ->\n\t\t\tio:format(\"Error trying to open file ~0p: ~0p~n\", [F, Reason]),\n\t\t\thalt(82)\n\tend || F <- Files0],\n\tcase hex_tarball:create(Metadata, Files) of\n\t\t{ok, #{tarball := Tarball}} ->\n\t\t\tok = file:write_file(\"$(strip $(HEX_TARBALL_OUTPUT_FILE))\", Tarball),\n\t\t\thalt(0);\n\t\t{error, Reason} ->\n\t\t\tio:format(\"Error ~0p~n\", [Reason]),\n\t\t\thalt(83)\n\tend\nendef\n\nhex_tar_verbose_0 = @echo \" TAR    $(notdir $(ERLANG_MK_TMP))/$(@F)\";\nhex_tar_verbose_2 = set -x;\nhex_tar_verbose = $(hex_tar_verbose_$(V))\n\n$(HEX_TARBALL_OUTPUT_FILE): $(DEPS_DIR)/hex_core/ebin/dep_built app\n\t$(hex_tar_verbose) $(call erlang,$(call hex_tarball_create.erl))\n\nhex-tarball-create: $(HEX_TARBALL_OUTPUT_FILE)\n\ndefine hex_release_publish_summary.erl\n\t{ok, Tarball} = erl_tar:open(\"$(strip $(HEX_TARBALL_OUTPUT_FILE))\", [read]),\n\tok = erl_tar:extract(Tarball, [{cwd, \"$(ERLANG_MK_TMP)\"}, {files, [\"metadata.config\"]}]),\n\t{ok, Metadata} = file:consult(\"$(ERLANG_MK_TMP)/metadata.config\"),\n\t#{\n\t\t<<\"name\">> := Name,\n\t\t<<\"version\">> := Version,\n\t\t<<\"files\">> := Files,\n\t\t<<\"requirements\">> := Deps\n\t} = maps:from_list(Metadata),\n\tio:format(\"Publishing ~s ~s~n  Dependencies:~n\", [Name, Version]),\n\tcase Deps of\n\t\t[] ->\n\t\t\tio:format(\"    (none)~n\");\n\t\t_ ->\n\t\t\t[begin\n\t\t\t\t#{<<\"app\">> := DA, <<\"requirement\">> := DR} = maps:from_list(D),\n\t\t\t\tio:format(\"    ~s ~s~n\", [DA, DR])\n\t\t\tend || {_, D} <- Deps]\n\tend,\n\tio:format(\"  Included files:~n\"),\n\t[io:format(\"    ~s~n\", [F]) || F <- Files],\n\tio:format(\"You may also review the contents of the tarball file.~n\"\n\t\t\"Please enter your secret key to proceed.~n\"),\n\thalt(0)\nendef\n\ndefine hex_release_publish.erl\n\t{ok, _} = application:ensure_all_started(ssl),\n\t{ok, _} = application:ensure_all_started(inets),\n\tConfig = $(hex_config.erl),\n\tConfigF = Config#{api_key => <<\"$(strip $1)\">>},\n\t{ok, Tarball} = file:read_file(\"$(strip $(HEX_TARBALL_OUTPUT_FILE))\"),\n\tcase hex_api_release:publish(ConfigF, Tarball, [{replace, $2}]) of\n\t\t{ok, {200, _, #{}}} ->\n\t\t\tio:format(\"Release replaced~n\"),\n\t\t\thalt(0);\n\t\t{ok, {201, _, #{}}} ->\n\t\t\tio:format(\"Release published~n\"),\n\t\t\thalt(0);\n\t\t{ok, {Status, _, Errors}} ->\n\t\t\tio:format(\"Error ~b: ~0p~n\", [Status, Errors]),\n\t\t\thalt(84)\n\tend\nendef\n\nhex-release-tarball: $(DEPS_DIR)/hex_core/ebin/dep_built $(HEX_TARBALL_OUTPUT_FILE)\n\t$(verbose) $(call erlang,$(call hex_release_publish_summary.erl))\n\nhex-release-publish: $(DEPS_DIR)/hex_core/ebin/dep_built hex-release-tarball\n\t$(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p \"Secret: \" secret; stty echo; echo $$secret) $(info )))\n\t$(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),false))\n\nhex-release-replace: $(DEPS_DIR)/hex_core/ebin/dep_built hex-release-tarball\n\t$(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p \"Secret: \" secret; stty echo; echo $$secret) $(info )))\n\t$(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),true))\n\ndefine hex_release_delete.erl\n\t{ok, _} = application:ensure_all_started(ssl),\n\t{ok, _} = application:ensure_all_started(inets),\n\tConfig = $(hex_config.erl),\n\tConfigF = Config#{api_key => <<\"$(strip $1)\">>},\n\tcase hex_api_release:delete(ConfigF, <<\"$(strip $(PROJECT))\">>, <<\"$(strip $(PROJECT_VERSION))\">>) of\n\t\t{ok, {204, _, _}} ->\n\t\t\tio:format(\"Release $(strip $(PROJECT_VERSION)) deleted~n\"),\n\t\t\thalt(0);\n\t\t{ok, {Status, _, Errors}} ->\n\t\t\tio:format(\"Error ~b: ~0p~n\", [Status, Errors]),\n\t\t\thalt(85)\n\tend\nendef\n\nhex-release-delete: $(DEPS_DIR)/hex_core/ebin/dep_built\n\t$(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p \"Secret: \" secret; stty echo; echo $$secret) $(info )))\n\t$(gen_verbose) $(call erlang,$(call hex_release_delete.erl,$(HEX_SECRET)))\n\ndefine hex_release_retire.erl\n\t{ok, _} = application:ensure_all_started(ssl),\n\t{ok, _} = application:ensure_all_started(inets),\n\tConfig = $(hex_config.erl),\n\tConfigF = Config#{api_key => <<\"$(strip $1)\">>},\n\tParams = #{<<\"reason\">> => <<\"$(strip $3)\">>, <<\"message\">> => <<\"$(strip $4)\">>},\n\tcase hex_api_release:retire(ConfigF, <<\"$(strip $(PROJECT))\">>, <<\"$(strip $2)\">>, Params) of\n\t\t{ok, {204, _, _}} ->\n\t\t\tio:format(\"Release $(strip $2) has been retired~n\"),\n\t\t\thalt(0);\n\t\t{ok, {Status, _, Errors}} ->\n\t\t\tio:format(\"Error ~b: ~0p~n\", [Status, Errors]),\n\t\t\thalt(86)\n\tend\nendef\n\nhex-release-retire: $(DEPS_DIR)/hex_core/ebin/dep_built\n\t$(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p \"Secret: \" secret; stty echo; echo $$secret) $(info )))\n\t$(gen_verbose) $(call erlang,$(call hex_release_retire.erl,$(HEX_SECRET),\\\n\t\t$(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)),\\\n\t\t$(if $(HEX_REASON),$(HEX_REASON),invalid),\\\n\t\t$(HEX_MESSAGE)))\n\ndefine hex_release_unretire.erl\n\t{ok, _} = application:ensure_all_started(ssl),\n\t{ok, _} = application:ensure_all_started(inets),\n\tConfig = $(hex_config.erl),\n\tConfigF = Config#{api_key => <<\"$(strip $1)\">>},\n\tcase hex_api_release:unretire(ConfigF, <<\"$(strip $(PROJECT))\">>, <<\"$(strip $2)\">>) of\n\t\t{ok, {204, _, _}} ->\n\t\t\tio:format(\"Release $(strip $2) is not retired anymore~n\"),\n\t\t\thalt(0);\n\t\t{ok, {Status, _, Errors}} ->\n\t\t\tio:format(\"Error ~b: ~0p~n\", [Status, Errors]),\n\t\t\thalt(87)\n\tend\nendef\n\nhex-release-unretire: $(DEPS_DIR)/hex_core/ebin/dep_built\n\t$(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p \"Secret: \" secret; stty echo; echo $$secret) $(info )))\n\t$(gen_verbose) $(call erlang,$(call hex_release_unretire.erl,$(HEX_SECRET),\\\n\t\t$(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION))))\n\nHEX_DOCS_DOC_DIR ?= doc/\nHEX_DOCS_TARBALL_FILES ?= $(sort $(call core_find,$(HEX_DOCS_DOC_DIR),*))\nHEX_DOCS_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT)-docs.tar.gz\n\n$(HEX_DOCS_TARBALL_OUTPUT_FILE): $(DEPS_DIR)/hex_core/ebin/dep_built app docs\n\t$(hex_tar_verbose) tar czf $(HEX_DOCS_TARBALL_OUTPUT_FILE) -C $(HEX_DOCS_DOC_DIR) \\\n\t\t$(HEX_DOCS_TARBALL_FILES:$(HEX_DOCS_DOC_DIR)%=%)\n\nhex-docs-tarball-create: $(HEX_DOCS_TARBALL_OUTPUT_FILE)\n\ndefine hex_docs_publish.erl\n\t{ok, _} = application:ensure_all_started(ssl),\n\t{ok, _} = application:ensure_all_started(inets),\n\tConfig = $(hex_config.erl),\n\tConfigF = Config#{api_key => <<\"$(strip $1)\">>},\n\t{ok, Tarball} = file:read_file(\"$(strip $(HEX_DOCS_TARBALL_OUTPUT_FILE))\"),\n\tcase hex_api:post(ConfigF,\n\t\t\t[\"packages\", \"$(strip $(PROJECT))\", \"releases\", \"$(strip $(PROJECT_VERSION))\", \"docs\"],\n\t\t\t{\"application/octet-stream\", Tarball}) of\n\t\t{ok, {Status, _, _}} when Status >= 200, Status < 300 ->\n\t\t\tio:format(\"Docs published~n\"),\n\t\t\thalt(0);\n\t\t{ok, {Status, _, Errors}} ->\n\t\t\tio:format(\"Error ~b: ~0p~n\", [Status, Errors]),\n\t\t\thalt(88)\n\tend\nendef\n\nhex-docs-publish: $(DEPS_DIR)/hex_core/ebin/dep_built hex-docs-tarball-create\n\t$(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p \"Secret: \" secret; stty echo; echo $$secret) $(info )))\n\t$(gen_verbose) $(call erlang,$(call hex_docs_publish.erl,$(HEX_SECRET)))\n\ndefine hex_docs_delete.erl\n\t{ok, _} = application:ensure_all_started(ssl),\n\t{ok, _} = application:ensure_all_started(inets),\n\tConfig = $(hex_config.erl),\n\tConfigF = Config#{api_key => <<\"$(strip $1)\">>},\n\tcase hex_api:delete(ConfigF,\n\t\t\t[\"packages\", \"$(strip $(PROJECT))\", \"releases\", \"$(strip $2)\", \"docs\"]) of\n\t\t{ok, {Status, _, _}} when Status >= 200, Status < 300 ->\n\t\t\tio:format(\"Docs removed~n\"),\n\t\t\thalt(0);\n\t\t{ok, {Status, _, Errors}} ->\n\t\t\tio:format(\"Error ~b: ~0p~n\", [Status, Errors]),\n\t\t\thalt(89)\n\tend\nendef\n\nhex-docs-delete: $(DEPS_DIR)/hex_core/ebin/dep_built\n\t$(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p \"Secret: \" secret; stty echo; echo $$secret) $(info )))\n\t$(gen_verbose) $(call erlang,$(call hex_docs_delete.erl,$(HEX_SECRET),\\\n\t\t$(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION))))\n\n# Copyright (c) 2015-2017, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\nifeq ($(filter proper,$(DEPS) $(TEST_DEPS)),proper)\n.PHONY: proper\n\n# Targets.\n\ntests:: proper\n\ndefine proper_check.erl\n\t$(call cover.erl)\n\tcode:add_pathsa([\n\t\t\"$(call core_native_path,$(CURDIR)/ebin)\",\n\t\t\"$(call core_native_path,$(DEPS_DIR)/*/ebin)\",\n\t\t\"$(call core_native_path,$(TEST_DIR))\"]),\n\tModule = fun(M) ->\n\t\t[true] =:= lists:usort([\n\t\t\tcase atom_to_list(F) of\n\t\t\t\t\"prop_\" ++ _ ->\n\t\t\t\t\tio:format(\"Testing ~p:~p/0~n\", [M, F]),\n\t\t\t\t\tproper:quickcheck(M:F(), nocolors);\n\t\t\t\t_ ->\n\t\t\t\t\ttrue\n\t\t\tend\n\t\t|| {F, 0} <- M:module_info(exports)])\n\tend,\n\ttry begin\n\t\tCoverSetup(),\n\t\tRes = case $(1) of\n\t\t\tall -> [true] =:= lists:usort([Module(M) || M <- [$(call comma_list,$(3))]]);\n\t\t\tmodule -> Module($(2));\n\t\t\tfunction -> proper:quickcheck($(2), nocolors)\n\t\tend,\n\t\tCoverExport(\"$(COVER_DATA_DIR)/proper.coverdata\"),\n\t\tRes\n\tend of\n\t\ttrue -> halt(0);\n\t\t_ -> halt(1)\n\tcatch error:undef$(if $V,:Stacktrace) ->\n\t\tio:format(\"Undefined property or module?~n$(if $V,~p~n)\", [$(if $V,Stacktrace)]),\n\t\thalt(0)\n\tend.\nendef\n\nifdef t\nifeq (,$(findstring :,$(t)))\nproper: test-build cover-data-dir\n\t$(verbose) $(call erlang,$(call proper_check.erl,module,$(t)))\nelse\nproper: test-build cover-data-dir\n\t$(verbose) echo Testing $(t)/0\n\t$(verbose) $(call erlang,$(call proper_check.erl,function,$(t)()))\nendif\nelse\nproper: test-build cover-data-dir\n\t$(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \\\n\t\t$(wildcard ebin/*.beam) $(call core_find,$(TEST_DIR)/,*.beam))))))\n\t$(gen_verbose) $(call erlang,$(call proper_check.erl,all,undefined,$(MODULES)))\nendif\nendif\n\n# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n# Verbosity.\n\nproto_verbose_0 = @echo \" PROTO \" $(filter %.proto,$(?F));\nproto_verbose = $(proto_verbose_$(V))\n\n# Core targets.\n\nifneq ($(wildcard src/),)\nifneq ($(filter gpb protobuffs,$(BUILD_DEPS) $(DEPS)),)\nPROTO_FILES := $(filter %.proto,$(ALL_SRC_FILES))\nERL_FILES += $(addprefix src/,$(patsubst %.proto,%_pb.erl,$(notdir $(PROTO_FILES))))\n\nifeq ($(PROTO_FILES),)\n$(ERLANG_MK_TMP)/last-makefile-change-protobuffs:\n\t$(verbose) :\nelse\n# Rebuild proto files when the Makefile changes.\n# We exclude $(PROJECT).d to avoid a circular dependency.\n$(ERLANG_MK_TMP)/last-makefile-change-protobuffs: $(filter-out $(PROJECT).d,$(MAKEFILE_LIST)) | $(ERLANG_MK_TMP)\n\t$(verbose) if test -f $@; then \\\n\t\ttouch $(PROTO_FILES); \\\n\tfi\n\t$(verbose) touch $@\n\n$(PROJECT).d:: $(ERLANG_MK_TMP)/last-makefile-change-protobuffs\nendif\n\nifeq ($(filter gpb,$(BUILD_DEPS) $(DEPS)),)\ndefine compile_proto.erl\n\t[begin\n\t\tprotobuffs_compile:generate_source(F, [\n\t\t\t{output_include_dir, \"./include\"},\n\t\t\t{output_src_dir, \"./src\"}])\n\tend || F <- string:tokens(\"$1\", \" \")],\n\thalt().\nendef\nelse\ndefine compile_proto.erl\n\t[begin\n\t\tgpb_compile:file(F, [\n\t\t\t$(foreach i,$(sort $(dir $(PROTO_FILES))),{i$(comma) \"$i\"}$(comma))\n\t\t\t{include_as_lib, true},\n\t\t\t{module_name_suffix, \"_pb\"},\n\t\t\t{o_hrl, \"./include\"},\n\t\t\t{o_erl, \"./src\"},\n\t\t\t{use_packages, true}\n\t\t])\n\tend || F <- string:tokens(\"$1\", \" \")],\n\thalt().\nendef\nendif\n\nifneq ($(PROTO_FILES),)\n$(PROJECT).d:: $(PROTO_FILES)\n\t$(verbose) mkdir -p ebin/ include/\n\t$(if $(strip $?),$(proto_verbose) $(call erlang,$(call compile_proto.erl,$?)))\nendif\nendif\nendif\n\n# Copyright (c) 2013-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\nifeq ($(filter relx,$(BUILD_DEPS) $(DEPS) $(REL_DEPS)),relx)\n.PHONY: relx-rel relx-relup distclean-relx-rel run\n\n# Configuration.\n\nRELX_CONFIG ?= $(CURDIR)/relx.config\nRELX_CONFIG_SCRIPT ?= $(CURDIR)/relx.config.script\n\nRELX_OUTPUT_DIR ?= _rel\nRELX_REL_EXT ?=\nRELX_TAR ?= 1\n\nifdef SFX\n\tRELX_TAR = 1\nendif\n\n# Core targets.\n\nifeq ($(IS_DEP),)\nifneq ($(wildcard $(RELX_CONFIG))$(wildcard $(RELX_CONFIG_SCRIPT)),)\nrel:: relx-rel\n\nrelup:: relx-relup\nendif\nendif\n\ndistclean:: distclean-relx-rel\n\n# Plugin-specific targets.\n\ndefine relx_get_config.erl\n\t(fun() ->\n\t\tConfig0 =\n\t\t\tcase file:consult(\"$(call core_native_path,$(RELX_CONFIG))\") of\n\t\t\t\t{ok, Terms} ->\n\t\t\t\t\tTerms;\n\t\t\t\t{error, _} ->\n\t\t\t\t\t[]\n\t\t\tend,\n\t\tcase filelib:is_file(\"$(call core_native_path,$(RELX_CONFIG_SCRIPT))\") of\n\t\t\ttrue ->\n\t\t\t\tBindings = erl_eval:add_binding('CONFIG', Config0, erl_eval:new_bindings()),\n\t\t\t\t{ok, Config1} = file:script(\"$(call core_native_path,$(RELX_CONFIG_SCRIPT))\", Bindings),\n\t\t\t\tConfig1;\n\t\t\tfalse ->\n\t\t\t\tConfig0\n\t\tend\n\tend)()\nendef\n\ndefine relx_release.erl\n\tConfig = $(call relx_get_config.erl),\n\t{release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),\n\tVsn = case Vsn0 of\n\t\t{cmd, Cmd} -> os:cmd(Cmd);\n\t\tsemver -> \"\";\n\t\t{semver, _} -> \"\";\n\t\t{git, short} -> string:trim(os:cmd(\"git rev-parse --short HEAD\"), both, \"\\n\");\n\t\t{git, long} -> string:trim(os:cmd(\"git rev-parse HEAD\"), both, \"\\n\");\n\t\tVsnStr -> Vsn0\n\tend,\n\t{ok, _} = relx:build_release(#{name => Name, vsn => Vsn}, Config ++ [{output_dir, \"$(RELX_OUTPUT_DIR)\"}]),\n\thalt(0).\nendef\n\ndefine relx_tar.erl\n\tConfig = $(call relx_get_config.erl),\n\t{release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),\n\tVsn = case Vsn0 of\n\t\t{cmd, Cmd} -> os:cmd(Cmd);\n\t\tsemver -> \"\";\n\t\t{semver, _} -> \"\";\n\t\t{git, short} -> string:trim(os:cmd(\"git rev-parse --short HEAD\"), both, \"\\n\");\n\t\t{git, long} -> string:trim(os:cmd(\"git rev-parse HEAD\"), both, \"\\n\");\n\t\tVsnStr -> Vsn0\n\tend,\n\t{ok, _} = relx:build_tar(#{name => Name, vsn => Vsn}, Config ++ [{output_dir, \"$(RELX_OUTPUT_DIR)\"}]),\n\thalt(0).\nendef\n\ndefine relx_relup.erl\n\tConfig = $(call relx_get_config.erl),\n\t{release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),\n\tVsn = case Vsn0 of\n\t\t{cmd, Cmd} -> os:cmd(Cmd);\n\t\tsemver -> \"\";\n\t\t{semver, _} -> \"\";\n\t\t{git, short} -> string:trim(os:cmd(\"git rev-parse --short HEAD\"), both, \"\\n\");\n\t\t{git, long} -> string:trim(os:cmd(\"git rev-parse HEAD\"), both, \"\\n\");\n\t\tVsnStr -> Vsn0\n\tend,\n\t{ok, _} = relx:build_relup(Name, Vsn, undefined, Config ++ [{output_dir, \"$(RELX_OUTPUT_DIR)\"}]),\n\thalt(0).\nendef\n\nrelx-rel: rel-deps app\n\t$(call erlang,$(call relx_release.erl),-pa ebin/)\n\t$(verbose) $(MAKE) relx-post-rel\n\t$(if $(filter-out 0,$(RELX_TAR)),$(call erlang,$(call relx_tar.erl),-pa ebin/))\n\nrelx-relup: rel-deps app\n\t$(call erlang,$(call relx_release.erl),-pa ebin/)\n\t$(MAKE) relx-post-rel\n\t$(call erlang,$(call relx_relup.erl),-pa ebin/)\n\t$(if $(filter-out 0,$(RELX_TAR)),$(call erlang,$(call relx_tar.erl),-pa ebin/))\n\ndistclean-relx-rel:\n\t$(gen_verbose) rm -rf $(RELX_OUTPUT_DIR)\n\n# Default hooks.\nrelx-post-rel::\n\t$(verbose) :\n\n# Run target.\n\nifeq ($(wildcard $(RELX_CONFIG))$(wildcard $(RELX_CONFIG_SCRIPT)),)\nrun::\nelse\n\ndefine get_relx_release.erl\n\tConfig = $(call relx_get_config.erl),\n\t{release, {Name, Vsn0}, _} = lists:keyfind(release, 1, Config),\n\tVsn = case Vsn0 of\n\t\t{cmd, Cmd} -> os:cmd(Cmd);\n\t\tsemver -> \"\";\n\t\t{semver, _} -> \"\";\n\t\t{git, short} -> string:trim(os:cmd(\"git rev-parse --short HEAD\"), both, \"\\n\");\n\t\t{git, long} -> string:trim(os:cmd(\"git rev-parse HEAD\"), both, \"\\n\");\n\t\tVsnStr -> Vsn0\n\tend,\n\tExtended = case lists:keyfind(extended_start_script, 1, Config) of\n\t\t{_, true} -> \"1\";\n\t\t_ -> \"\"\n\tend,\n\tio:format(\"~s ~s ~s\", [Name, Vsn, Extended]),\n\thalt(0).\nendef\n\nRELX_REL := $(shell $(call erlang,$(get_relx_release.erl)))\nRELX_REL_NAME := $(word 1,$(RELX_REL))\nRELX_REL_VSN := $(word 2,$(RELX_REL))\nRELX_REL_CMD := $(if $(word 3,$(RELX_REL)),console)\n\nifeq ($(PLATFORM),msys2)\nRELX_REL_EXT := .cmd\nendif\n\nrun:: RELX_TAR := 0\nrun:: all\n\t$(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) $(RELX_REL_CMD)\n\nifdef RELOAD\nrel::\n\t$(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) ping\n\t$(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) \\\n\t\teval \"io:format(\\\"~p~n\\\", [c:lm()]).\"\nendif\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \"\" \\\n\t\t\"Relx targets:\" \\\n\t\t\"  run         Compile the project, build the release and run it\"\n\nendif\nendif\n\n# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>\n# Copyright (c) 2014, M Robert Martin <rob@version2beta.com>\n# This file is contributed to erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: shell\n\n# Configuration.\n\nSHELL_ERL ?= erl\nSHELL_PATHS ?= $(CURDIR)/ebin $(TEST_DIR)\nSHELL_OPTS ?=\n\nALL_SHELL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(SHELL_DEPS))\n\n# Core targets\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \"\" \\\n\t\t\"Shell targets:\" \\\n\t\t\"  shell       Run an erlang shell with SHELL_OPTS or reasonable default\"\n\n# Plugin-specific targets.\n\n$(foreach dep,$(SHELL_DEPS),$(eval $(call dep_target,$(dep))))\n\nifneq ($(SKIP_DEPS),)\nbuild-shell-deps:\nelse\nbuild-shell-deps: $(ALL_SHELL_DEPS_DIRS)\n\t$(verbose) set -e; for dep in $(ALL_SHELL_DEPS_DIRS) ; do \\\n\t\tif [ -z \"$(strip $(FULL))\" ] && [ ! -L $$dep ] && [ -f $$dep/ebin/dep_built ]; then \\\n\t\t\t:; \\\n\t\telse \\\n\t\t\t$(MAKE) -C $$dep IS_DEP=1; \\\n\t\t\tif [ ! -L $$dep ] && [ -d $$dep/ebin ]; then touch $$dep/ebin/dep_built; fi; \\\n\t\tfi \\\n\tdone\nendif\n\nshell:: build-shell-deps\n\t$(gen_verbose) $(SHELL_ERL) -pa $(SHELL_PATHS) $(SHELL_OPTS)\n\n# Copyright 2017, Stanislaw Klekot <dozzie@jarowit.net>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: distclean-sphinx sphinx\n\n# Configuration.\n\nSPHINX_BUILD ?= sphinx-build\nSPHINX_SOURCE ?= doc\nSPHINX_CONFDIR ?=\nSPHINX_FORMATS ?= html\nSPHINX_DOCTREES ?= $(ERLANG_MK_TMP)/sphinx.doctrees\nSPHINX_OPTS ?=\n\n#sphinx_html_opts =\n#sphinx_html_output = html\n#sphinx_man_opts =\n#sphinx_man_output = man\n#sphinx_latex_opts =\n#sphinx_latex_output = latex\n\n# Helpers.\n\nsphinx_build_0 = @echo \" SPHINX\" $1; $(SPHINX_BUILD) -N -q\nsphinx_build_1 = $(SPHINX_BUILD) -N\nsphinx_build_2 = set -x; $(SPHINX_BUILD)\nsphinx_build = $(sphinx_build_$(V))\n\ndefine sphinx.build\n$(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)\n\nendef\n\ndefine sphinx.output\n$(if $(sphinx_$1_output),$(sphinx_$1_output),$1)\nendef\n\n# Targets.\n\nifneq ($(wildcard $(if $(SPHINX_CONFDIR),$(SPHINX_CONFDIR),$(SPHINX_SOURCE))/conf.py),)\ndocs:: sphinx\ndistclean:: distclean-sphinx\nendif\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \"\" \\\n\t\t\"Sphinx targets:\" \\\n\t\t\"  sphinx      Generate Sphinx documentation.\" \\\n\t\t\"\" \\\n\t\t\"ReST sources and 'conf.py' file are expected in directory pointed by\" \\\n\t\t\"SPHINX_SOURCE ('doc' by default). SPHINX_FORMATS lists formats to build (only\" \\\n\t\t\"'html' format is generated by default); target directory can be specified by\" \\\n\t\t'setting sphinx_$${format}_output, for example: sphinx_html_output = output/html' \\\n\t\t\"Additional Sphinx options can be set in SPHINX_OPTS.\"\n\n# Plugin-specific targets.\n\nsphinx:\n\t$(foreach F,$(SPHINX_FORMATS),$(call sphinx.build,$F))\n\ndistclean-sphinx:\n\t$(gen_verbose) rm -rf $(filter-out $(SPHINX_SOURCE),$(foreach F,$(SPHINX_FORMATS),$(call sphinx.output,$F)))\n\n# Copyright (c) 2017, Jean-Sébastien Pédron <jean-sebastien@rabbitmq.com>\n# This file is contributed to erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: show-ERL_LIBS show-ERLC_OPTS show-TEST_ERLC_OPTS\n\nshow-ERL_LIBS:\n\t@echo $(ERL_LIBS)\n\nshow-ERLC_OPTS:\n\t@$(foreach opt,$(ERLC_OPTS) -pa ebin -I include,echo \"$(opt)\";)\n\nshow-TEST_ERLC_OPTS:\n\t@$(foreach opt,$(TEST_ERLC_OPTS) -pa ebin -I include,echo \"$(opt)\";)\n\n# Copyright (c) 2015-2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\nifeq ($(filter triq,$(DEPS) $(TEST_DEPS)),triq)\n.PHONY: triq\n\n# Targets.\n\ntests:: triq\n\ndefine triq_check.erl\n\t$(call cover.erl)\n\tcode:add_pathsa([\n\t\t\"$(call core_native_path,$(CURDIR)/ebin)\",\n\t\t\"$(call core_native_path,$(DEPS_DIR)/*/ebin)\",\n\t\t\"$(call core_native_path,$(TEST_DIR))\"]),\n\ttry begin\n\t\tCoverSetup(),\n\t\tRes = case $(1) of\n\t\t\tall -> [true] =:= lists:usort([triq:check(M) || M <- [$(call comma_list,$(3))]]);\n\t\t\tmodule -> triq:check($(2));\n\t\t\tfunction -> triq:check($(2))\n\t\tend,\n\t\tCoverExport(\"$(COVER_DATA_DIR)/triq.coverdata\"),\n\t\tRes\n\tend of\n\t\ttrue -> halt(0);\n\t\t_ -> halt(1)\n\tcatch error:undef$(if $V,:Stacktrace) ->\n\t\tio:format(\"Undefined property or module?~n$(if $V,~p~n)\", [$(if $V,Stacktrace)]),\n\t\thalt(0)\n\tend.\nendef\n\nifdef t\nifeq (,$(findstring :,$(t)))\ntriq: test-build cover-data-dir\n\t$(verbose) $(call erlang,$(call triq_check.erl,module,$(t)))\nelse\ntriq: test-build cover-data-dir\n\t$(verbose) echo Testing $(t)/0\n\t$(verbose) $(call erlang,$(call triq_check.erl,function,$(t)()))\nendif\nelse\ntriq: test-build cover-data-dir\n\t$(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \\\n\t\t$(wildcard ebin/*.beam) $(call core_find,$(TEST_DIR)/,*.beam))))))\n\t$(gen_verbose) $(call erlang,$(call triq_check.erl,all,undefined,$(MODULES)))\nendif\nendif\n\n# Copyright (c) 2022, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: xref\n\n# Configuration.\n\n# We do not use locals_not_used or deprecated_function_calls\n# because the compiler will error out by default in those\n# cases with Erlang.mk. Deprecated functions may make sense\n# in some cases but few libraries define them. We do not\n# use exports_not_used by default because it hinders more\n# than it helps library projects such as Cowboy. Finally,\n# undefined_functions provides little that undefined_function_calls\n# doesn't already provide, so it's not enabled by default.\nXREF_CHECKS ?= [undefined_function_calls]\n\n# Instead of predefined checks a query can be evaluated\n# using the Xref DSL. The $q variable is used in that case.\n\n# The scope is a list of keywords that correspond to\n# application directories, being essentially an easy way\n# to configure which applications to analyze. With:\n#\n# - app:  .\n# - apps: $(ALL_APPS_DIRS)\n# - deps: $(ALL_DEPS_DIRS)\n# - otp:  Built-in Erlang/OTP applications.\n#\n# The default is conservative (app) and will not be\n# appropriate for all types of queries (for example\n# application_call requires adding all applications\n# that might be called or they will not be found).\nXREF_SCOPE ?= app # apps deps otp\n\n# If the above is not enough, additional application\n# directories can be configured.\nXREF_EXTRA_APP_DIRS ?=\n\n# As well as additional non-application directories.\nXREF_EXTRA_DIRS ?=\n\n# Erlang.mk supports -ignore_xref([...]) with forms\n# {M, F, A} | {F, A} | M, the latter ignoring whole\n# modules. Ignores can also be provided project-wide.\nXREF_IGNORE ?= []\n\n# All callbacks may be ignored. Erlang.mk will ignore\n# them automatically for exports_not_used (unless it\n# is explicitly disabled by the user).\nXREF_IGNORE_CALLBACKS ?=\n\n# Core targets.\n\nhelp::\n\t$(verbose) printf '%s\\n' '' \\\n\t\t'Xref targets:' \\\n\t\t'  xref         Analyze the project using Xref' \\\n\t\t'  xref q=QUERY Evaluate an Xref query'\n\n# Plugin-specific targets.\n\ndefine xref.erl\n\t{ok, Xref} = xref:start([]),\n\tScope = [$(call comma_list,$(XREF_SCOPE))],\n\tAppDirs0 = [$(call comma_list,$(foreach d,$(XREF_EXTRA_APP_DIRS),\"$d\"))],\n\tAppDirs1 = case lists:member(otp, Scope) of\n\t\tfalse -> AppDirs0;\n\t\ttrue ->\n\t\t\tRootDir = code:root_dir(),\n\t\t\tAppDirs0 ++ [filename:dirname(P) || P <- code:get_path(), lists:prefix(RootDir, P)]\n\tend,\n\tAppDirs2 = case lists:member(deps, Scope) of\n\t\tfalse -> AppDirs1;\n\t\ttrue -> [$(call comma_list,$(foreach d,$(ALL_DEPS_DIRS),\"$d\"))] ++ AppDirs1\n\tend,\n\tAppDirs3 = case lists:member(apps, Scope) of\n\t\tfalse -> AppDirs2;\n\t\ttrue -> [$(call comma_list,$(foreach d,$(ALL_APPS_DIRS),\"$d\"))] ++ AppDirs2\n\tend,\n\tAppDirs = case lists:member(app, Scope) of\n\t\tfalse -> AppDirs3;\n\t\ttrue -> [\"../$(notdir $(CURDIR))\"|AppDirs3]\n\tend,\n\t[{ok, _} = xref:add_application(Xref, AppDir, [{builtins, true}]) || AppDir <- AppDirs],\n\tExtraDirs = [$(call comma_list,$(foreach d,$(XREF_EXTRA_DIRS),\"$d\"))],\n\t[{ok, _} = xref:add_directory(Xref, ExtraDir, [{builtins, true}]) || ExtraDir <- ExtraDirs],\n\tok = xref:set_library_path(Xref, code:get_path() -- ([\"ebin\", \".\"] ++ AppDirs ++ ExtraDirs)),\n\tChecks = case {$1, is_list($2)} of\n\t\t{check, true} -> $2;\n\t\t{check, false} -> [$2];\n\t\t{query, _} -> [$2]\n\tend,\n\tFinalRes = [begin\n\t\tIsInformational = case $1 of\n\t\t\tquery -> true;\n\t\t\tcheck ->\n\t\t\t\tis_tuple(Check) andalso\n\t\t\t\t\tlists:member(element(1, Check),\n\t\t\t\t\t\t[call, use, module_call, module_use, application_call, application_use])\n\t\tend,\n\t\t{ok, Res0} = case $1 of\n\t\t\tcheck -> xref:analyze(Xref, Check);\n\t\t\tquery -> xref:q(Xref, Check)\n\t\tend,\n\t\tRes = case IsInformational of\n\t\t\ttrue -> Res0;\n\t\t\tfalse ->\n\t\t\t\tlists:filter(fun(R) ->\n\t\t\t\t\t{Mod, InMFA, MFA} = case R of\n\t\t\t\t\t\t{InMFA0 = {M, _, _}, MFA0} -> {M, InMFA0, MFA0};\n\t\t\t\t\t\t{M, _, _} -> {M, R, R}\n\t\t\t\t\tend,\n\t\t\t\t\tAttrs = try\n\t\t\t\t\t\tMod:module_info(attributes)\n\t\t\t\t\tcatch error:undef ->\n\t\t\t\t\t\t[]\n\t\t\t\t\tend,\n\t\t\t\t\tInlineIgnores = lists:flatten([\n\t\t\t\t\t\t[case V of\n\t\t\t\t\t\t\tM when is_atom(M) -> {M, '_', '_'};\n\t\t\t\t\t\t\t{F, A} -> {Mod, F, A};\n\t\t\t\t\t\t\t_ -> V\n\t\t\t\t\t\tend || V <- Values]\n\t\t\t\t\t|| {ignore_xref, Values} <- Attrs]),\n\t\t\t\t\tBuiltinIgnores = [\n\t\t\t\t\t\t{eunit_test, wrapper_test_exported_, 0}\n\t\t\t\t\t],\n\t\t\t\t\tDoCallbackIgnores = case {Check, \"$(strip $(XREF_IGNORE_CALLBACKS))\"} of\n\t\t\t\t\t\t{exports_not_used, \"\"} -> true;\n\t\t\t\t\t\t{_, \"0\"} -> false;\n\t\t\t\t\t\t_ -> true\n\t\t\t\t\tend,\n\t\t\t\t\tCallbackIgnores = case DoCallbackIgnores of\n\t\t\t\t\t\tfalse -> [];\n\t\t\t\t\t\ttrue ->\n\t\t\t\t\t\t\tBehaviors = lists:flatten([\n\t\t\t\t\t\t\t\t[BL || {behavior, BL} <- Attrs],\n\t\t\t\t\t\t\t\t[BL || {behaviour, BL} <- Attrs]\n\t\t\t\t\t\t\t]),\n\t\t\t\t\t\t\t[{Mod, CF, CA} || B <- Behaviors, {CF, CA} <- B:behaviour_info(callbacks)]\n\t\t\t\t\tend,\n\t\t\t\t\tWideIgnores = if\n\t\t\t\t\t\tis_list($(XREF_IGNORE)) ->\n\t\t\t\t\t\t\t[if is_atom(I) -> {I, '_', '_'}; true -> I end\n\t\t\t\t\t\t\t\t|| I <- $(XREF_IGNORE)];\n\t\t\t\t\t\ttrue -> [$(XREF_IGNORE)]\n\t\t\t\t\tend,\n\t\t\t\t\tIgnores = InlineIgnores ++ BuiltinIgnores ++ CallbackIgnores ++ WideIgnores,\n\t\t\t\t\tnot (lists:member(InMFA, Ignores)\n\t\t\t\t\torelse lists:member(MFA, Ignores)\n\t\t\t\t\torelse lists:member({Mod, '_', '_'}, Ignores))\n\t\t\t\tend, Res0)\n\t\tend,\n\t\tcase Res of\n\t\t\t[] -> ok;\n\t\t\t_ when IsInformational ->\n\t\t\t\tcase Check of\n\t\t\t\t\t{call, {CM, CF, CA}} ->\n\t\t\t\t\t\tio:format(\"Functions that ~s:~s/~b calls:~n\", [CM, CF, CA]);\n\t\t\t\t\t{use, {CM, CF, CA}} ->\n\t\t\t\t\t\tio:format(\"Function ~s:~s/~b is called by:~n\", [CM, CF, CA]);\n\t\t\t\t\t{module_call, CMod} ->\n\t\t\t\t\t\tio:format(\"Modules that ~s calls:~n\", [CMod]);\n\t\t\t\t\t{module_use, CMod} ->\n\t\t\t\t\t\tio:format(\"Module ~s is used by:~n\", [CMod]);\n\t\t\t\t\t{application_call, CApp} ->\n\t\t\t\t\t\tio:format(\"Applications that ~s calls:~n\", [CApp]);\n\t\t\t\t\t{application_use, CApp} ->\n\t\t\t\t\t\tio:format(\"Application ~s is used by:~n\", [CApp]);\n\t\t\t\t\t_ when $1 =:= query ->\n\t\t\t\t\t\tio:format(\"Query ~s returned:~n\", [Check])\n\t\t\t\tend,\n\t\t\t\t[case R of\n\t\t\t\t\t{{InM, InF, InA}, {M, F, A}} ->\n\t\t\t\t\t\tio:format(\"- ~s:~s/~b called by ~s:~s/~b~n\",\n\t\t\t\t\t\t\t[M, F, A, InM, InF, InA]);\n\t\t\t\t\t{M, F, A} ->\n\t\t\t\t\t\tio:format(\"- ~s:~s/~b~n\", [M, F, A]);\n\t\t\t\t\tModOrApp ->\n\t\t\t\t\t\tio:format(\"- ~s~n\", [ModOrApp])\n\t\t\t\tend || R <- Res],\n\t\t\t\tok;\n\t\t\t_ ->\n\t\t\t\t[case {Check, R} of\n\t\t\t\t\t{undefined_function_calls, {{InM, InF, InA}, {M, F, A}}} ->\n\t\t\t\t\t\tio:format(\"Undefined function ~s:~s/~b called by ~s:~s/~b~n\",\n\t\t\t\t\t\t\t[M, F, A, InM, InF, InA]);\n\t\t\t\t\t{undefined_functions, {M, F, A}} ->\n\t\t\t\t\t\tio:format(\"Undefined function ~s:~s/~b~n\", [M, F, A]);\n\t\t\t\t\t{locals_not_used, {M, F, A}} ->\n\t\t\t\t\t\tio:format(\"Unused local function ~s:~s/~b~n\", [M, F, A]);\n\t\t\t\t\t{exports_not_used, {M, F, A}} ->\n\t\t\t\t\t\tio:format(\"Unused exported function ~s:~s/~b~n\", [M, F, A]);\n\t\t\t\t\t{deprecated_function_calls, {{InM, InF, InA}, {M, F, A}}} ->\n\t\t\t\t\t\tio:format(\"Deprecated function ~s:~s/~b called by ~s:~s/~b~n\",\n\t\t\t\t\t\t\t[M, F, A, InM, InF, InA]);\n\t\t\t\t\t{deprecated_functions, {M, F, A}} ->\n\t\t\t\t\t\tio:format(\"Deprecated function ~s:~s/~b~n\", [M, F, A]);\n\t\t\t\t\t_ ->\n\t\t\t\t\t\tio:format(\"~p: ~p~n\", [Check, R])\n\t\t\t\tend || R <- Res],\n\t\t\t\terror\n\t\tend\n\tend || Check <- Checks],\n\tstopped = xref:stop(Xref),\n\tcase lists:usort(FinalRes) of\n\t\t[ok] -> halt(0);\n\t\t_ -> halt(1)\n\tend\nendef\n\nxref: deps app\nifdef q\n\t$(verbose) $(call erlang,$(call xref.erl,query,\"$q\"),-pa ebin/)\nelse\n\t$(verbose) $(call erlang,$(call xref.erl,check,$(XREF_CHECKS)),-pa ebin/)\nendif\n\n# Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>\n# Copyright (c) 2015, Viktor Söderqvist <viktor@zuiderkwast.se>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\nCOVER_REPORT_DIR ?= cover\nCOVER_DATA_DIR ?= $(COVER_REPORT_DIR)\n\nifdef COVER\nCOVER_APPS ?= $(notdir $(ALL_APPS_DIRS))\nCOVER_DEPS ?=\nCOVER_EXCLUDE_MODS ?=\nendif\n\n# Code coverage for Common Test.\n\nifdef COVER\nifdef CT_RUN\nifneq ($(wildcard $(TEST_DIR)),)\ntest-build:: $(TEST_DIR)/ct.cover.spec\n\n$(TEST_DIR)/ct.cover.spec: cover-data-dir\n\t$(gen_verbose) printf \"%s\\n\" \\\n\t\t\"{incl_app, '$(PROJECT)', details}.\" \\\n\t\t\"{incl_dirs, '$(PROJECT)', [\\\"$(call core_native_path,$(CURDIR)/ebin)\\\" \\\n\t\t\t$(foreach a,$(COVER_APPS),$(comma) \\\"$(call core_native_path,$(APPS_DIR)/$a/ebin)\\\") \\\n\t\t\t$(foreach d,$(COVER_DEPS),$(comma) \\\"$(call core_native_path,$(DEPS_DIR)/$d/ebin)\\\")]}.\" \\\n\t\t'{export,\"$(call core_native_path,$(abspath $(COVER_DATA_DIR))/ct.coverdata)\"}.' \\\n\t\t\"{excl_mods, '$(PROJECT)', [$(call comma_list,$(COVER_EXCLUDE_MODS))]}.\" > $@\n\nCT_RUN += -cover $(TEST_DIR)/ct.cover.spec\nendif\nendif\nendif\n\n# Code coverage for other tools.\n\nifdef COVER\ndefine cover.erl\n\tCoverSetup = fun() ->\n\t\tDirs = [\"$(call core_native_path,$(CURDIR)/ebin)\"\n\t\t\t$(foreach a,$(COVER_APPS),$(comma) \"$(call core_native_path,$(APPS_DIR)/$a/ebin)\")\n\t\t\t$(foreach d,$(COVER_DEPS),$(comma) \"$(call core_native_path,$(DEPS_DIR)/$d/ebin)\")],\n\t\tExcludes = [$(call comma_list,$(foreach e,$(COVER_EXCLUDE_MODS),\"$e\"))],\n\t\t[case file:list_dir(Dir) of\n\t\t\t{error, enotdir} -> false;\n\t\t\t{error, _} ->\thalt(2);\n\t\t\t{ok, Files} ->\n\t\t\tBeamFiles =  [filename:join(Dir, File) ||\n\t\t\t\tFile <- Files,\n\t\t\t\tnot lists:member(filename:basename(File, \".beam\"), Excludes),\n\t\t\t\tfilename:extension(File) =:= \".beam\"],\n\t\t\tcase cover:compile_beam(BeamFiles) of\n\t\t\t\t{error, _} -> halt(1);\n\t\t\t\t_ -> true\n\t\t\tend\n\t\tend || Dir <- Dirs]\n\tend,\n\tCoverExport = fun(Filename) -> cover:export(Filename) end,\nendef\nelse\ndefine cover.erl\n\tCoverSetup = fun() -> ok end,\n\tCoverExport = fun(_) -> ok end,\nendef\nendif\n\n# Core targets\n\nifdef COVER\nifneq ($(COVER_REPORT_DIR),)\ntests::\n\t$(verbose) $(MAKE) --no-print-directory cover-report\nendif\n\ncover-data-dir: | $(COVER_DATA_DIR)\n\n$(COVER_DATA_DIR):\n\t$(verbose) mkdir -p $(COVER_DATA_DIR)\nelse\ncover-data-dir:\nendif\n\nclean:: coverdata-clean\n\nifneq ($(COVER_REPORT_DIR),)\ndistclean:: cover-report-clean\nendif\n\nhelp::\n\t$(verbose) printf \"%s\\n\" \"\" \\\n\t\t\"Cover targets:\" \\\n\t\t\"  cover-report  Generate a HTML coverage report from previously collected\" \\\n\t\t\"                cover data.\" \\\n\t\t\"  all.coverdata Merge all coverdata files into all.coverdata.\" \\\n\t\t\"\" \\\n\t\t\"If COVER=1 is set, coverage data is generated by the targets eunit and ct. The\" \\\n\t\t\"target tests additionally generates a HTML coverage report from the combined\" \\\n\t\t\"coverdata files from each of these testing tools. HTML reports can be disabled\" \\\n\t\t\"by setting COVER_REPORT_DIR to empty.\"\n\n# Plugin specific targets\n\nCOVERDATA = $(filter-out $(COVER_DATA_DIR)/all.coverdata,$(wildcard $(COVER_DATA_DIR)/*.coverdata))\n\n.PHONY: coverdata-clean\ncoverdata-clean:\n\t$(gen_verbose) rm -f $(COVER_DATA_DIR)/*.coverdata $(TEST_DIR)/ct.cover.spec\n\n# Merge all coverdata files into one.\ndefine cover_export.erl\n\t$(foreach f,$(COVERDATA),cover:import(\"$(f)\") == ok orelse halt(1),)\n\tcover:export(\"$(COVER_DATA_DIR)/$@\"), halt(0).\nendef\n\nall.coverdata: $(COVERDATA) cover-data-dir\n\t$(gen_verbose) $(call erlang,$(cover_export.erl))\n\n# These are only defined if COVER_REPORT_DIR is non-empty. Set COVER_REPORT_DIR to\n# empty if you want the coverdata files but not the HTML report.\nifneq ($(COVER_REPORT_DIR),)\n\n.PHONY: cover-report-clean cover-report\n\ncover-report-clean:\n\t$(gen_verbose) rm -rf $(COVER_REPORT_DIR)\nifneq ($(COVER_REPORT_DIR),$(COVER_DATA_DIR))\n\t$(if $(shell ls -A $(COVER_DATA_DIR)/),,$(verbose) rmdir $(COVER_DATA_DIR))\nendif\n\nifeq ($(COVERDATA),)\ncover-report:\nelse\n\n# Modules which include eunit.hrl always contain one line without coverage\n# because eunit defines test/0 which is never called. We compensate for this.\nEUNIT_HRL_MODS = $(subst $(space),$(comma),$(shell \\\n\tgrep -H -e '^\\s*-include.*include/eunit\\.hrl\"' src/*.erl \\\n\t| sed \"s/^src\\/\\(.*\\)\\.erl:.*/'\\1'/\" | uniq))\n\ndefine cover_report.erl\n\t$(foreach f,$(COVERDATA),cover:import(\"$(f)\") == ok orelse halt(1),)\n\tMs = cover:imported_modules(),\n\t[cover:analyse_to_file(M, \"$(COVER_REPORT_DIR)/\" ++ atom_to_list(M)\n\t\t++ \".COVER.html\", [html])  || M <- Ms],\n\tReport = [begin {ok, R} = cover:analyse(M, module), R end || M <- Ms],\n\tEunitHrlMods = [$(EUNIT_HRL_MODS)],\n\tReport1 = [{M, {Y, case lists:member(M, EunitHrlMods) of\n\t\ttrue -> N - 1; false -> N end}} || {M, {Y, N}} <- Report],\n\tTotalY = lists:sum([Y || {_, {Y, _}} <- Report1]),\n\tTotalN = lists:sum([N || {_, {_, N}} <- Report1]),\n\tPerc = fun(Y, N) -> case Y + N of 0 -> 100; S -> round(100 * Y / S) end end,\n\tTotalPerc = Perc(TotalY, TotalN),\n\t{ok, F} = file:open(\"$(COVER_REPORT_DIR)/index.html\", [write]),\n\tio:format(F, \"<!DOCTYPE html><html>~n\"\n\t\t\"<head><meta charset=\\\"UTF-8\\\">~n\"\n\t\t\"<title>Coverage report</title></head>~n\"\n\t\t\"<body>~n\", []),\n\tio:format(F, \"<h1>Coverage</h1>~n<p>Total: ~p%</p>~n\", [TotalPerc]),\n\tio:format(F, \"<table><tr><th>Module</th><th>Coverage</th></tr>~n\", []),\n\t[io:format(F, \"<tr><td><a href=\\\"~p.COVER.html\\\">~p</a></td>\"\n\t\t\"<td>~p%</td></tr>~n\",\n\t\t[M, M, Perc(Y, N)]) || {M, {Y, N}} <- Report1],\n\tHow = \"$(subst $(space),$(comma)$(space),$(basename $(COVERDATA)))\",\n\tDate = \"$(shell date -u \"+%Y-%m-%dT%H:%M:%SZ\")\",\n\tio:format(F, \"</table>~n\"\n\t\t\"<p>Generated using ~s and erlang.mk on ~s.</p>~n\"\n\t\t\"</body></html>\", [How, Date]),\n\thalt().\nendef\n\ncover-report:\n\t$(verbose) mkdir -p $(COVER_REPORT_DIR)\n\t$(gen_verbose) $(call erlang,$(cover_report.erl))\n\nendif\nendif # ifneq ($(COVER_REPORT_DIR),)\n\n# Copyright (c) 2016, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n.PHONY: sfx\n\nifdef RELX_REL\nifdef SFX\n\n# Configuration.\n\nSFX_ARCHIVE ?= $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/$(RELX_REL_NAME)-$(RELX_REL_VSN).tar.gz\nSFX_OUTPUT_FILE ?= $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME).run\n\n# Core targets.\n\nrel:: sfx\n\n# Plugin-specific targets.\n\ndefine sfx_stub\n#!/bin/sh\n\nTMPDIR=`mktemp -d`\nARCHIVE=`awk '/^__ARCHIVE_BELOW__$$/ {print NR + 1; exit 0;}' $$0`\nFILENAME=$$(basename $$0)\nREL=$${FILENAME%.*}\n\ntail -n+$$ARCHIVE $$0 | tar -xzf - -C $$TMPDIR\n\n$$TMPDIR/bin/$$REL console\nRET=$$?\n\nrm -rf $$TMPDIR\n\nexit $$RET\n\n__ARCHIVE_BELOW__\nendef\n\nsfx:\n\t$(verbose) $(call core_render,sfx_stub,$(SFX_OUTPUT_FILE))\n\t$(gen_verbose) cat $(SFX_ARCHIVE) >> $(SFX_OUTPUT_FILE)\n\t$(verbose) chmod +x $(SFX_OUTPUT_FILE)\n\nendif\nendif\n\n# Copyright (c) 2013-2017, Loïc Hoguin <essen@ninenines.eu>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n# External plugins.\n\nDEP_PLUGINS ?=\n\n$(foreach p,$(DEP_PLUGINS),\\\n\t$(eval $(if $(findstring /,$p),\\\n\t\t$(call core_dep_plugin,$p,$(firstword $(subst /, ,$p))),\\\n\t\t$(call core_dep_plugin,$p/plugins.mk,$p))))\n\nhelp:: help-plugins\n\nhelp-plugins::\n\t$(verbose) :\n\n# Copyright (c) 2013-2015, Loïc Hoguin <essen@ninenines.eu>\n# Copyright (c) 2015-2016, Jean-Sébastien Pédron <jean-sebastien@rabbitmq.com>\n# This file is part of erlang.mk and subject to the terms of the ISC License.\n\n# Fetch dependencies recursively (without building them).\n\n.PHONY: fetch-deps fetch-doc-deps fetch-rel-deps fetch-test-deps \\\n\tfetch-shell-deps\n\n.PHONY: $(ERLANG_MK_RECURSIVE_DEPS_LIST) \\\n\t$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \\\n\t$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \\\n\t$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \\\n\t$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST)\n\nfetch-deps: $(ERLANG_MK_RECURSIVE_DEPS_LIST)\nfetch-doc-deps: $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST)\nfetch-rel-deps: $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST)\nfetch-test-deps: $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST)\nfetch-shell-deps: $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST)\n\nifneq ($(SKIP_DEPS),)\n$(ERLANG_MK_RECURSIVE_DEPS_LIST) \\\n$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \\\n$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \\\n$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \\\n$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST):\n\t$(verbose) :> $@\nelse\n# By default, we fetch \"normal\" dependencies. They are also included no\n# matter the type of requested dependencies.\n#\n# $(ALL_DEPS_DIRS) includes $(BUILD_DEPS).\n\n$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS)\n$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_DOC_DEPS_DIRS)\n$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_REL_DEPS_DIRS)\n$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_TEST_DEPS_DIRS)\n$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): $(LOCAL_DEPS_DIRS) $(ALL_DEPS_DIRS) $(ALL_SHELL_DEPS_DIRS)\n\n# Allow to use fetch-deps and $(DEP_TYPES) to fetch multiple types of\n# dependencies with a single target.\nifneq ($(filter doc,$(DEP_TYPES)),)\n$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_DOC_DEPS_DIRS)\nendif\nifneq ($(filter rel,$(DEP_TYPES)),)\n$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_REL_DEPS_DIRS)\nendif\nifneq ($(filter test,$(DEP_TYPES)),)\n$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_TEST_DEPS_DIRS)\nendif\nifneq ($(filter shell,$(DEP_TYPES)),)\n$(ERLANG_MK_RECURSIVE_DEPS_LIST): $(ALL_SHELL_DEPS_DIRS)\nendif\n\nERLANG_MK_RECURSIVE_TMP_LIST := $(abspath $(ERLANG_MK_TMP)/recursive-tmp-deps-$(shell echo $$PPID).log)\n\n$(ERLANG_MK_RECURSIVE_DEPS_LIST) \\\n$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST) \\\n$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST) \\\n$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST) \\\n$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST): | $(ERLANG_MK_TMP)\nifeq ($(IS_APP)$(IS_DEP),)\n\t$(verbose) rm -f $(ERLANG_MK_RECURSIVE_TMP_LIST)\nendif\n\t$(verbose) touch $(ERLANG_MK_RECURSIVE_TMP_LIST)\n\t$(verbose) set -e; for dep in $^ ; do \\\n\t\tif ! grep -qs ^$$dep$$ $(ERLANG_MK_RECURSIVE_TMP_LIST); then \\\n\t\t\techo $$dep >> $(ERLANG_MK_RECURSIVE_TMP_LIST); \\\n\t\t\tif grep -qs -E \"^[[:blank:]]*include[[:blank:]]+(erlang\\.mk|.*/erlang\\.mk|.*ERLANG_MK_FILENAME.*)$$\" \\\n\t\t\t $$dep/GNUmakefile $$dep/makefile $$dep/Makefile; then \\\n\t\t\t\t$(MAKE) -C $$dep fetch-deps \\\n\t\t\t\t IS_DEP=1 \\\n\t\t\t\t ERLANG_MK_RECURSIVE_TMP_LIST=$(ERLANG_MK_RECURSIVE_TMP_LIST); \\\n\t\t\tfi \\\n\t\tfi \\\n\tdone\nifeq ($(IS_APP)$(IS_DEP),)\n\t$(verbose) sort < $(ERLANG_MK_RECURSIVE_TMP_LIST) | \\\n\t\tuniq > $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted\n\t$(verbose) mv $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@\n\t$(verbose) rm $(ERLANG_MK_RECURSIVE_TMP_LIST)\nendif\nendif # ifneq ($(SKIP_DEPS),)\n\n# List dependencies recursively.\n\n.PHONY: list-deps list-doc-deps list-rel-deps list-test-deps \\\n\tlist-shell-deps\n\nlist-deps: $(ERLANG_MK_RECURSIVE_DEPS_LIST)\nlist-doc-deps: $(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST)\nlist-rel-deps: $(ERLANG_MK_RECURSIVE_REL_DEPS_LIST)\nlist-test-deps: $(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST)\nlist-shell-deps: $(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST)\n\nlist-deps list-doc-deps list-rel-deps list-test-deps list-shell-deps:\n\t$(verbose) cat $^\n\n# Query dependencies recursively.\n\n.PHONY: query-deps query-doc-deps query-rel-deps query-test-deps \\\n\tquery-shell-deps\n\nQUERY ?= name fetch_method repo version\n\ndefine query_target\n$1: $2 clean-tmp-query.log\nifeq ($(IS_APP)$(IS_DEP),)\n\t$(verbose) rm -f $4\nendif\n\t$(verbose) $(foreach dep,$3,\\\n\t\techo $(PROJECT): $(foreach q,$(QUERY),$(call query_$(q),$(dep))) >> $4 ;)\n\t$(if $(filter-out query-deps,$1),,\\\n\t\t$(verbose) set -e; for dep in $3 ; do \\\n\t\t\tif grep -qs ^$$$$dep$$$$ $(ERLANG_MK_TMP)/query.log; then \\\n\t\t\t\t:; \\\n\t\t\telse \\\n\t\t\t\techo $$$$dep >> $(ERLANG_MK_TMP)/query.log; \\\n\t\t\t\t$(MAKE) -C $(DEPS_DIR)/$$$$dep $$@ QUERY=\"$(QUERY)\" IS_DEP=1 || true; \\\n\t\t\tfi \\\n\t\tdone)\nifeq ($(IS_APP)$(IS_DEP),)\n\t$(verbose) touch $4\n\t$(verbose) cat $4\nendif\nendef\n\nclean-tmp-query.log:\nifeq ($(IS_DEP),)\n\t$(verbose) rm -f $(ERLANG_MK_TMP)/query.log\nendif\n\n$(eval $(call query_target,query-deps,$(ERLANG_MK_RECURSIVE_DEPS_LIST),$(BUILD_DEPS) $(DEPS),$(ERLANG_MK_QUERY_DEPS_FILE)))\n$(eval $(call query_target,query-doc-deps,$(ERLANG_MK_RECURSIVE_DOC_DEPS_LIST),$(DOC_DEPS),$(ERLANG_MK_QUERY_DOC_DEPS_FILE)))\n$(eval $(call query_target,query-rel-deps,$(ERLANG_MK_RECURSIVE_REL_DEPS_LIST),$(REL_DEPS),$(ERLANG_MK_QUERY_REL_DEPS_FILE)))\n$(eval $(call query_target,query-test-deps,$(ERLANG_MK_RECURSIVE_TEST_DEPS_LIST),$(TEST_DEPS),$(ERLANG_MK_QUERY_TEST_DEPS_FILE)))\n$(eval $(call query_target,query-shell-deps,$(ERLANG_MK_RECURSIVE_SHELL_DEPS_LIST),$(SHELL_DEPS),$(ERLANG_MK_QUERY_SHELL_DEPS_FILE)))\n"
  },
  {
    "path": "examples/README.asciidoc",
    "content": "= Cowboy examples\n\n* link:chunked_hello_world[]:\n  demonstrate chunked data transfer with two one-second delays\n\n* link:compress_response[]:\n  send a response body compressed if the client supports it\n\n* link:cookie[]:\n  set cookies from server and client side\n\n* link:echo_get[]:\n  parse and echo a GET query string\n\n* link:echo_post[]:\n  parse and echo a POST parameter\n\n* link:eventsource[]:\n  eventsource emitter and consumer\n\n* link:file_server[]:\n  file server with directory listing\n\n* link:hello_world[]:\n  simplest example application\n\n* link:markdown_middleware[]:\n  static file handler with markdown preprocessor\n\n* link:rest_basic_auth[]:\n  basic HTTP authorization with REST\n\n* link:rest_hello_world[]:\n  return the data type that matches the request type (ex: html, text, json)\n\n* link:rest_pastebin[]:\n  create text objects and return the data type that matches the request type (html, text)\n\n* link:ssl_hello_world[]:\n  simplest SSL application\n\n* link:upload[]:\n  multipart/form-data upload\n\n* link:websocket[]:\n  websocket example\n\n== Other languages\n\n* https://github.com/joshrotenberg/elixir_cowboy_examples[Elixir]\n* https://github.com/quasiquoting/lfe-cowboy-examples[LFE]\n"
  },
  {
    "path": "examples/chunked_hello_world/Makefile",
    "content": "PROJECT = chunked_hello_world\nPROJECT_DESCRIPTION = Cowboy chunked Hello World example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/chunked_hello_world/README.asciidoc",
    "content": "= Chunked hello world example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\nor use `curl` to see the chunks arriving one at a time every second.\n\n== HTTP/1.1 example output\n\n[source,bash]\n----\n$ time curl -i http://localhost:8080\nHTTP/1.1 200 OK\ntransfer-encoding: chunked\nconnection: keep-alive\nserver: Cowboy\ndate: Fri, 28 Sep 2012 04:24:16 GMT\n\nHello\nWorld\nChunked!\ncurl -i http://localhost:8080  0.01s user 0.00s system 0% cpu 2.015 total\n----\n\n== HTTP/2 example output\n\n[source,bash]\n----\n$ nghttp -v http://localhost:8080\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.000] send HEADERS frame <length=38, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: */*\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n[  0.006] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.006] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.006] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.010] recv (stream_id=13) :status: 200\n[  0.010] recv (stream_id=13) date: Mon, 13 Jun 2016 14:16:26 GMT\n[  0.010] recv (stream_id=13) server: Cowboy\n[  0.010] recv HEADERS frame <length=32, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\nHello\n[  0.010] recv DATA frame <length=7, flags=0x00, stream_id=13>\nWorld\n[  1.012] recv DATA frame <length=7, flags=0x00, stream_id=13>\nChunked!\n[  2.013] recv DATA frame <length=10, flags=0x00, stream_id=13>\n[  2.013] recv DATA frame <length=0, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  2.013] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n"
  },
  {
    "path": "examples/chunked_hello_world/relx.config",
    "content": "{release, {chunked_hello_world_example, \"1\"}, [chunked_hello_world]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/chunked_hello_world/src/chunked_hello_world_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(chunked_hello_world_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", toppage_h, []}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\tchunked_hello_world_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/chunked_hello_world/src/chunked_hello_world_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(chunked_hello_world_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/chunked_hello_world/src/toppage_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc Chunked hello world handler.\n-module(toppage_h).\n\n-export([init/2]).\n\ninit(Req0, Opts) ->\n\tReq = cowboy_req:stream_reply(200, Req0),\n\tcowboy_req:stream_body(\"Hello\\r\\n\", nofin, Req),\n\ttimer:sleep(1000),\n\tcowboy_req:stream_body(\"World\\r\\n\", nofin, Req),\n\ttimer:sleep(1000),\n\tcowboy_req:stream_body(\"Chunked!\\r\\n\", fin, Req),\n\t{ok, Req, Opts}.\n"
  },
  {
    "path": "examples/compress_response/Makefile",
    "content": "PROJECT = compress_response\nPROJECT_DESCRIPTION = Cowboy compressed response example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/compress_response/README.asciidoc",
    "content": "= Compressed response example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\n\n== HTTP/1.1 example output\n\nWithout compression:\n\n[source,bash]\n----\n$ curl -i http://localhost:8080\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Mon, 07 Jan 2013 18:42:29 GMT\ncontent-length: 909\n\nA cowboy is an animal herder who tends cattle on ranches in North America,\ntraditionally on horseback, and often performs a multitude of other ranch-\nrelated tasks. The historic American cowboy of the late 19th century arose\nfrom the vaquero traditions of northern Mexico and became a figure of special\nsignificance and legend. A subtype, called a wrangler, specifically tends the\nhorses used to work cattle. In addition to ranch work, some cowboys work for\nor participate in rodeos. Cowgirls, first defined as such in the late 19th\ncentury, had a less-well documented historical role, but in the modern world\nhave established the ability to work at virtually identical tasks and obtained\nconsiderable respect for their achievements. There are also cattle handlers\nin many other parts of the world, particularly South America and Australia,\nwho perform work similar to the cowboy in their respective nations.\n----\n\nWith compression:\n\n[source,bash]\n----\n$ curl -i --compressed http://localhost:8080\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Mon, 07 Jan 2013 18:42:30 GMT\ncontent-encoding: gzip\ncontent-length: 510\n\nA cowboy is an animal herder who tends cattle on ranches in North America,\ntraditionally on horseback, and often performs a multitude of other ranch-\nrelated tasks. The historic American cowboy of the late 19th century arose\nfrom the vaquero traditions of northern Mexico and became a figure of special\nsignificance and legend. A subtype, called a wrangler, specifically tends the\nhorses used to work cattle. In addition to ranch work, some cowboys work for\nor participate in rodeos. Cowgirls, first defined as such in the late 19th\ncentury, had a less-well documented historical role, but in the modern world\nhave established the ability to work at virtually identical tasks and obtained\nconsiderable respect for their achievements. There are also cattle handlers\nin many other parts of the world, particularly South America and Australia,\nwho perform work similar to the cowboy in their respective nations.\n----\n\n== HTTP/2 example output\n\nWithout compression:\n\n[source,bash]\n----\n$ nghttp -v -H 'accept-encoding: compress' http://localhost:8080\n[  0.001] Connected\n[  0.001] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.001] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.001] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.001] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.001] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.001] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.002] send HEADERS frame <length=45, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: */*\n          accept-encoding: compress\n          user-agent: nghttp2/1.18.1\n[  0.002] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.002] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.002] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.003] recv (stream_id=13) :status: 200\n[  0.003] recv (stream_id=13) content-length: 909\n[  0.003] recv (stream_id=13) date: Sun, 22 Jan 2017 19:13:47 GMT\n[  0.003] recv (stream_id=13) server: Cowboy\n[  0.003] recv HEADERS frame <length=37, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\nA cowboy is an animal herder who tends cattle on ranches in North America,\ntraditionally on horseback, and often performs a multitude of other ranch-\nrelated tasks. The historic American cowboy of the late 19th century arose\nfrom the vaquero traditions of northern Mexico and became a figure of special\nsignificance and legend. A subtype, called a wrangler, specifically tends the\nhorses used to work cattle. In addition to ranch work, some cowboys work for\nor participate in rodeos. Cowgirls, first defined as such in the late 19th\ncentury, had a less-well documented historical role, but in the modern world\nhave established the ability to work at virtually identical tasks and obtained\nconsiderable respect for their achievements. There are also cattle handlers\nin many other parts of the world, particularly South America and Australia,\nwho perform work similar to the cowboy in their respective nations.\n[  0.003] recv DATA frame <length=909, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.003] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n\nWith compression:\n\n[source,bash]\n----\n$ nghttp -v http://localhost:8080 \n[ERROR] Could not connect to the address ::1\nTrying next address 127.0.0.1\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.000] send HEADERS frame <length=38, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: */*\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.18.1\n[  0.000] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.000] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.000] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.000] recv (stream_id=13) :status: 200\n[  0.000] recv (stream_id=13) content-encoding: gzip\n[  0.000] recv (stream_id=13) content-length: 510\n[  0.000] recv (stream_id=13) date: Sun, 22 Jan 2017 19:15:16 GMT\n[  0.000] recv (stream_id=13) server: Cowboy\n[  0.000] recv HEADERS frame <length=41, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\nA cowboy is an animal herder who tends cattle on ranches in North America,\ntraditionally on horseback, and often performs a multitude of other ranch-\nrelated tasks. The historic American cowboy of the late 19th century arose\nfrom the vaquero traditions of northern Mexico and became a figure of special\nsignificance and legend. A subtype, called a wrangler, specifically tends the\nhorses used to work cattle. In addition to ranch work, some cowboys work for\nor participate in rodeos. Cowgirls, first defined as such in the late 19th\ncentury, had a less-well documented historical role, but in the modern world\nhave established the ability to work at virtually identical tasks and obtained\nconsiderable respect for their achievements. There are also cattle handlers\nin many other parts of the world, particularly South America and Australia,\nwho perform work similar to the cowboy in their respective nations.\n[  0.000] recv DATA frame <length=510, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.000] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n"
  },
  {
    "path": "examples/compress_response/relx.config",
    "content": "{release, {compress_response_example, \"1\"}, [compress_response]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/compress_response/src/compress_response_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(compress_response_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", toppage_h, []}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch},\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h]\n\t}),\n\tcompress_response_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/compress_response/src/compress_response_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(compress_response_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/compress_response/src/toppage_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc Compress response handler.\n-module(toppage_h).\n\n-export([init/2]).\n\ninit(Req0, Opts) ->\n\tBigBody =\n<<\"A cowboy is an animal herder who tends cattle on ranches in North America,\ntraditionally on horseback, and often performs a multitude of other ranch-\nrelated tasks. The historic American cowboy of the late 19th century arose\nfrom the vaquero traditions of northern Mexico and became a figure of special\nsignificance and legend. A subtype, called a wrangler, specifically tends the\nhorses used to work cattle. In addition to ranch work, some cowboys work for\nor participate in rodeos. Cowgirls, first defined as such in the late 19th\ncentury, had a less-well documented historical role, but in the modern world\nhave established the ability to work at virtually identical tasks and obtained\nconsiderable respect for their achievements. There are also cattle handlers\nin many other parts of the world, particularly South America and Australia,\nwho perform work similar to the cowboy in their respective nations.\\n\">>,\n\tReq = cowboy_req:reply(200, #{}, BigBody, Req0),\n\t{ok, Req, Opts}.\n"
  },
  {
    "path": "examples/cookie/Makefile",
    "content": "PROJECT = cookie\nPROJECT_DESCRIPTION = Cowboy Cookie example\nPROJECT_VERSION = 1\n\nDEPS = cowboy erlydtl\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/cookie/README.asciidoc",
    "content": "= Cookie example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\n\nThis example allows you to use any path to show that the cookies\nare defined site-wide. Try it!\n"
  },
  {
    "path": "examples/cookie/relx.config",
    "content": "{release, {cookie_example, \"1\"}, [cookie]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/cookie/src/cookie_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(cookie_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{'_', toppage_h, []}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\tcookie_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/cookie/src/cookie_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(cookie_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/cookie/src/toppage_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc Cookie handler.\n-module(toppage_h).\n\n-export([init/2]).\n\ninit(Req0, Opts) ->\n\tNewValue = integer_to_list(rand:uniform(1000000)),\n\tReq1 = cowboy_req:set_resp_cookie(<<\"server\">>, NewValue,\n\t\tReq0, #{path => <<\"/\">>}),\n\t#{client := ClientCookie, server := ServerCookie}\n\t\t= cowboy_req:match_cookies([{client, [], <<>>}, {server, [], <<>>}], Req1),\n\t{ok, Body} = toppage_dtl:render([\n\t\t{client, ClientCookie},\n\t\t{server, ServerCookie}\n\t]),\n\tReq = cowboy_req:reply(200, #{\n\t\t<<\"content-type\">> => <<\"text/html\">>\n\t}, Body, Req1),\n\t{ok, Req, Opts}.\n"
  },
  {
    "path": "examples/cookie/templates/toppage.dtl",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"utf-8\">\n\t<title>Cowboy Cookie Example</title>\n</head>\n\n<body>\n\t<h1>Cowboy Cookie Example</h1>\n\t<p>Refresh the page to see the next cookie.</p>\n\n\t<h2>Cookie Set Server-Side</h2>\n\t<p>{{ server }}</p>\n\n\t<h2>Cookie Set Client-Side</h2>\n\t<p>{{ client }}</p>\n\n\t<script type=\"text/javascript\">\n// <![CDATA[\ndocument.cookie=\"client=test=\";\n// ]]>\n\t</script>\n</body>\n</html>\n"
  },
  {
    "path": "examples/echo_get/Makefile",
    "content": "PROJECT = echo_get\nPROJECT_DESCRIPTION = Cowboy GET echo example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/echo_get/README.asciidoc",
    "content": "= GET parameter echo example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080/?echo=hello\n\nYou can replace the `echo` parameter with another to check\nthat the handler is echoing it back properly.\n\n== HTTP/1.1 example output\n\n[source,bash]\n----\n$ curl -i \"http://localhost:8080/?echo=saymyname\"\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Fri, 28 Sep 2012 04:09:04 GMT\ncontent-length: 9\ncontent-type: text/plain; charset=utf-8\n\nsaymyname\n----\n\n== HTTP/2 example output\n\n[source,bash]\n----\n$ nghttp -v \"http://localhost:8080/?echo=saymyname\"\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.000] send HEADERS frame <length=51, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /?echo=saymyname\n          :scheme: http\n          :authority: localhost:8080\n          accept: */*\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n[  0.000] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.000] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.000] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.001] recv (stream_id=13) :status: 200\n[  0.001] recv (stream_id=13) content-length: 9\n[  0.001] recv (stream_id=13) content-type: text/plain; charset=utf-8\n[  0.001] recv (stream_id=13) date: Thu, 09 Jun 2016 09:06:05 GMT\n[  0.001] recv (stream_id=13) server: Cowboy\n[  0.001] recv HEADERS frame <length=55, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\nsaymyname[  0.001] recv DATA frame <length=9, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.001] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n"
  },
  {
    "path": "examples/echo_get/relx.config",
    "content": "{release, {echo_get_example, \"1\"}, [echo_get]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/echo_get/src/echo_get_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(echo_get_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", toppage_h, []}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\techo_get_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/echo_get/src/echo_get_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(echo_get_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/echo_get/src/toppage_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc GET echo handler.\n-module(toppage_h).\n\n-export([init/2]).\n\ninit(Req0, Opts) ->\n\tMethod = cowboy_req:method(Req0),\n\t#{echo := Echo} = cowboy_req:match_qs([{echo, [], undefined}], Req0),\n\tReq = echo(Method, Echo, Req0),\n\t{ok, Req, Opts}.\n\necho(<<\"GET\">>, undefined, Req) ->\n\tcowboy_req:reply(400, #{}, <<\"Missing echo parameter.\">>, Req);\necho(<<\"GET\">>, Echo, Req) ->\n\tcowboy_req:reply(200, #{\n\t\t<<\"content-type\">> => <<\"text/plain; charset=utf-8\">>\n\t}, Echo, Req);\necho(_, _, Req) ->\n\t%% Method not allowed.\n\tcowboy_req:reply(405, Req).\n"
  },
  {
    "path": "examples/echo_post/Makefile",
    "content": "PROJECT = echo_post\nPROJECT_DESCRIPTION = Cowboy POST echo example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/echo_post/README.asciidoc",
    "content": "= POST parameter echo example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nAs this example echoes a POST parameter, it is a little more\ncomplex to test. Some browsers feature tools that allow you\nto perform one such request, or you can use the command line\ntool `curl` as we will demonstrate.\n\n== HTTP/1.1 example output\n\n[source,bash]\n----\n$ curl -i -d echo=echomeplz http://localhost:8080\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Fri, 28 Sep 2012 04:12:36 GMT\ncontent-length: 9\ncontent-type: text/plain; charset=utf-8\n\nechomeplz\n----\n\n== HTTP/2 example output\n\n[source,bash]\n----\n$ echo echo=echomeplz | nghttp -v -d - http://localhost:8080\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.001] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.001] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.001] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.001] send HEADERS frame <length=43, flags=0x24, stream_id=13>\n          ; END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: POST\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: */*\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n          content-length: 15\n[  0.001] send DATA frame <length=15, flags=0x00, stream_id=13>\n[  0.001] send DATA frame <length=0, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.012] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.012] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.012] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.020] recv (stream_id=13) :status: 200\n[  0.020] recv (stream_id=13) content-length: 10\n[  0.020] recv (stream_id=13) content-type: text/plain; charset=utf-8\n[  0.020] recv (stream_id=13) date: Thu, 09 Jun 2016 09:19:35 GMT\n[  0.020] recv (stream_id=13) server: Cowboy\n[  0.020] recv HEADERS frame <length=57, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\nechomeplz\n[  0.020] recv DATA frame <length=10, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.020] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n"
  },
  {
    "path": "examples/echo_post/relx.config",
    "content": "{release, {echo_post_example, \"1\"}, [echo_post]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/echo_post/src/echo_post_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(echo_post_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", toppage_h, []}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\techo_post_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/echo_post/src/echo_post_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(echo_post_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/echo_post/src/toppage_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc POST echo handler.\n-module(toppage_h).\n\n-export([init/2]).\n\ninit(Req0, Opts) ->\n\tMethod = cowboy_req:method(Req0),\n\tHasBody = cowboy_req:has_body(Req0),\n\tReq = maybe_echo(Method, HasBody, Req0),\n\t{ok, Req, Opts}.\n\nmaybe_echo(<<\"POST\">>, true, Req0) ->\n\t{ok, PostVals, Req} = cowboy_req:read_urlencoded_body(Req0),\n\tEcho = proplists:get_value(<<\"echo\">>, PostVals),\n\techo(Echo, Req);\nmaybe_echo(<<\"POST\">>, false, Req) ->\n\tcowboy_req:reply(400, #{}, <<\"Missing body.\">>, Req);\nmaybe_echo(_, _, Req) ->\n\t%% Method not allowed.\n\tcowboy_req:reply(405, Req).\n\necho(undefined, Req) ->\n\tcowboy_req:reply(400, #{}, <<\"Missing echo parameter.\">>, Req);\necho(Echo, Req) ->\n\tcowboy_req:reply(200, #{\n\t\t<<\"content-type\">> => <<\"text/plain; charset=utf-8\">>\n\t}, Echo, Req).\n"
  },
  {
    "path": "examples/eventsource/Makefile",
    "content": "PROJECT = eventsource\nPROJECT_DESCRIPTION = Cowboy EventSource example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/eventsource/README.asciidoc",
    "content": "= EventSource example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\n"
  },
  {
    "path": "examples/eventsource/priv/index.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<script type=\"text/javascript\">\n\t\t\tfunction ready() {\n\t\t\t\tif (!!window.EventSource) {\n\t\t\t\t\tsetupEventSource();\n\t\t\t\t} else {\n\t\t\t\t\tdocument.getElementById('status').innerHTML =\n\t\t\t\t\t\t\"Sorry but your browser doesn't support the EventSource API\";\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction setupEventSource() {\n\t\t\t\tvar source = new EventSource('/eventsource');\n\n\t\t\t\tsource.addEventListener('message', function(event) {\n\t\t\t\t\taddStatus(\"server sent the following: '\" + event.data + \"'\");\n\t\t\t\t\t}, false);\n\n\t\t\t\t\tsource.addEventListener('open', function(event) {\n\t\t\t\t\t\taddStatus('eventsource connected.')\n\t\t\t\t\t}, false);\n\n\t\t\t\t\tsource.addEventListener('error', function(event) {\n\t\t\t\t\t\tif (event.eventPhase == EventSource.CLOSED) {\n\t\t\t\t\t\t\taddStatus('eventsource was closed.')\n\t\t\t\t\t\t}\n\t\t\t\t\t}, false);\n\t\t\t}\n\n\t\t\tfunction addStatus(text) {\n\t\t\t\tvar date = new Date();\n\t\t\t\tdocument.getElementById('status').innerHTML\n\t\t\t\t= document.getElementById('status').innerHTML\n\t\t\t\t+ date + \": \" + text + \"<br/>\";\n\t\t\t}\n\t\t</script>\n\t</head>\n\t<body onload=\"ready();\">\n\t\tHi!\n\t\t<div id=\"status\"></div>\n\t</body>\n</html>\n"
  },
  {
    "path": "examples/eventsource/relx.config",
    "content": "{release, {eventsource_example, \"1\"}, [eventsource]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/eventsource/src/eventsource_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(eventsource_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/eventsource\", eventsource_h, []},\n\t\t\t{\"/\", cowboy_static, {priv_file, eventsource, \"index.html\"}}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\teventsource_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/eventsource/src/eventsource_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc EventSource emitter.\n-module(eventsource_h).\n\n-export([init/2]).\n-export([info/3]).\n\ninit(Req0, Opts) ->\n\tReq = cowboy_req:stream_reply(200, #{\n\t\t<<\"content-type\">> => <<\"text/event-stream\">>\n\t}, Req0),\n\terlang:send_after(1000, self(), {message, \"Tick\"}),\n\t{cowboy_loop, Req, Opts}.\n\ninfo({message, Msg}, Req, State) ->\n\tcowboy_req:stream_events(#{\n\t\tid => id(),\n\t\tdata => Msg\n\t}, nofin, Req),\n\terlang:send_after(1000, self(), {message, \"Tick\"}),\n\t{ok, Req, State}.\n\nid() ->\n\tinteger_to_list(erlang:unique_integer([positive, monotonic]), 16).\n"
  },
  {
    "path": "examples/eventsource/src/eventsource_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(eventsource_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/file_server/Makefile",
    "content": "PROJECT = file_server\nPROJECT_DESCRIPTION = Cowboy file server example with directory listing\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/file_server/README.asciidoc",
    "content": "= File server example with directory listing\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\nto browse the contents of the `priv` directory.\n\nInteresting examples include:\n\n* http://localhost:8080/test.txt[Plain text file]\n* http://localhost:8080/video.html[HTML5 video demo]\n\n== HTTP/1.1 example output\n\n[source,bash]\n----\n$ curl -i http://localhost:8080/test.txt\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Mon, 09 Sep 2013 13:49:50 GMT\ncontent-length: 52\ncontent-type: text/plain\nlast-modified: Fri, 18 Jan 2013 16:33:31 GMT\n\nIf you read this then the static file server works!\n----\n\n== HTTP/2 example output\n\n[source,bash]\n----\n$ nghttp -v http://localhost:8080/test.txt\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.000] send HEADERS frame <length=46, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /test.txt\n          :scheme: http\n          :authority: localhost:8080\n          accept: */*\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n[  0.001] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.001] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.001] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.007] recv (stream_id=13) :status: 200\n[  0.007] recv (stream_id=13) content-length: 52\n[  0.007] recv (stream_id=13) content-type: text/plain\n[  0.007] recv (stream_id=13) date: Mon, 13 Jun 2016 11:25:20 GMT\n[  0.007] recv (stream_id=13) etag: \"1371478245\"\n[  0.007] recv (stream_id=13) last-modified: Tue, 12 Aug 2014 17:00:17 GMT\n[  0.007] recv (stream_id=13) server: Cowboy\n[  0.007] recv HEADERS frame <length=81, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\nIf you read this then the static file server works!\n[  0.007] recv DATA frame <length=52, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.007] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n"
  },
  {
    "path": "examples/file_server/priv/test.txt",
    "content": "If you read this then the static file server works!\n"
  },
  {
    "path": "examples/file_server/priv/video.html",
    "content": "<!DOCTYPE html>\n<html>\n<body>\n\t<h1>HTML5 Video Example</h1>\n\t<video controls>\n\t\t<source src=\"small.ogv\" type=\"video/ogg\"/>\n\t\t<source src=\"small.mp4\" type=\"video/mp4\"/>\n\t</video>\n\t<p>Videos taken from <a href=\"http://techslides.com/sample-webm-ogg-and-mp4-video-files-for-html5/\">TechSlides</a></p>\n</body>\n</html>\n"
  },
  {
    "path": "examples/file_server/priv/中文/中文.html",
    "content": "<html>\n  <head>\n    <meta charset='utf-8'>\n  </head>\n  <body>\n    中文！\n  </body>\n</html>\n"
  },
  {
    "path": "examples/file_server/relx.config",
    "content": "{release, {file_server_example, \"1\"}, [file_server]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/file_server/src/directory_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc Directory handler.\n-module(directory_h).\n\n%% REST Callbacks\n-export([init/2]).\n-export([allowed_methods/2]).\n-export([resource_exists/2]).\n-export([content_types_provided/2]).\n-export([charsets_provided/2]).\n\n%% Callback Callbacks\n-export([list_json/2]).\n-export([list_html/2]).\n\ninit(Req, Paths) ->\n\t{cowboy_rest, Req, Paths}.\n\nallowed_methods(Req, State) ->\n\t{[<<\"GET\">>], Req, State}.\n\nresource_exists(Req, {ReqPath, FilePath}) ->\n\tcase file:list_dir(FilePath) of\n\t\t{ok, Fs} -> {true, Req, {ReqPath, lists:sort(Fs)}};\n\t\t_Err -> {false, Req, {ReqPath, FilePath}}\n\tend.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{{<<\"text\">>, <<\"html\">>, []}, list_html},\n\t\t{{<<\"application\">>, <<\"json\">>, []}, list_json}\n\t], Req, State}.\n\ncharsets_provided(Req, State) ->\n\t{[<<\"utf-8\">>], Req, State}.\n\nlist_json(Req, {Path, Fs}) ->\n\tFiles = [unicode:characters_to_binary(F) || F <- Fs],\n\t{json:encode(Files), Req, Path}.\n\nlist_html(Req, {Path, Fs}) ->\n\tBody = [[links(Path, unicode:characters_to_binary(F)) || F <- [\"..\"|Fs]]],\n\tHTML = [<<\"<!DOCTYPE html><html><head><title>Index</title></head>\",\n\t\t\"<body>\">>, Body, <<\"</body></html>\\n\">>],\n\t{HTML, Req, Path}.\n\nlinks(<<>>, \"..\") ->\n\t\"<a href='/..'>..</a><br>\\n\";\nlinks(Prefix, \"..\") ->\n\tTokens = string:tokens(binary_to_list(Prefix), \"/\"),\n\tBack = lists:join(\"/\", lists:reverse(tl(lists:reverse(Tokens)))),\n\t[\"<a href='/../\", Back, \"'>..</a><br>\\n\"];\nlinks(<<>>, File) ->\n\t[\"<a href='/\", File, \"'>\", File, \"</a><br>\\n\"];\nlinks(Prefix, File) ->\n\t[\"<a href='/\", Prefix, File, \"'>\", File, \"</a><br>\\n\"].\n"
  },
  {
    "path": "examples/file_server/src/directory_lister.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n-module(directory_lister).\n-behaviour(cowboy_middleware).\n\n-export([execute/2]).\n\nexecute(Req, Env=#{handler := cowboy_static}) ->\n\tredirect_directory(Req, Env);\nexecute(Req, Env) ->\n\t{ok, Req, Env}.\n\nredirect_directory(Req, Env=#{handler_opts := {_, _, _, Extra}}) ->\n\tPath = cowboy_req:path_info(Req),\n\tPath1 = << <<S/binary, $/>> || S <- Path >>,\n\t{dir_handler, DirHandler} = lists:keyfind(dir_handler, 1, Extra),\n\tFullPath = resource_path(Path1),\n\tcase valid_path(Path) and filelib:is_dir(FullPath) of\n\t\ttrue -> handle_directory(Req, Env, Path1, FullPath, DirHandler);\n\t\tfalse -> {ok, Req, Env}\n\tend.\n\nhandle_directory(Req, Env, Prefix, Path, DirHandler) ->\n\t{ok, Req, Env#{handler => DirHandler, handler_opts => {Prefix, Path}}}.\n\nvalid_path([]) -> true;\nvalid_path([<<\"..\">> | _T]) -> false;\nvalid_path([<<\"/\", _/binary>> | _T]) -> false;\nvalid_path([_H | Rest]) -> valid_path(Rest).\n\nresource_path(Path) ->\n\tfilename:join([code:priv_dir(file_server), Path]).\n"
  },
  {
    "path": "examples/file_server/src/file_server_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(file_server_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/[...]\", cowboy_static, {priv_dir, file_server, \"\", [\n\t\t\t\t{mimetypes, cow_mimetypes, all},\n\t\t\t\t{dir_handler, directory_h},\n\t\t\t\t{charset, <<\"utf-8\">>}\n\t\t\t]}}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch},\n\t\tmiddlewares => [cowboy_router, directory_lister, cowboy_handler]\n\t}),\n\tfile_server_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/file_server/src/file_server_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(file_server_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/hello_world/Makefile",
    "content": "PROJECT = hello_world\nPROJECT_DESCRIPTION = Cowboy Hello World example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/hello_world/README.asciidoc",
    "content": "= Hello world example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\n\n== HTTP/1.1 example output\n\n[source,bash]\n----\n$ curl -i http://localhost:8080\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Fri, 28 Sep 2012 04:10:25 GMT\ncontent-length: 12\ncontent-type: text/plain\n\nHello world!\n----\n\n== HTTP/2 example output\n\n[source,bash]\n----\n$ nghttp -v http://localhost:8080\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.000] send HEADERS frame <length=38, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: */*\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n[  0.008] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.008] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.008] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.013] recv (stream_id=13) :status: 200\n[  0.013] recv (stream_id=13) content-length: 12\n[  0.013] recv (stream_id=13) content-type: text/plain\n[  0.013] recv (stream_id=13) date: Thu, 09 Jun 2016 08:56:56 GMT\n[  0.013] recv (stream_id=13) server: Cowboy\n[  0.013] recv HEADERS frame <length=46, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\nHello world![  0.013] recv DATA frame <length=12, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.013] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n"
  },
  {
    "path": "examples/hello_world/relx.config",
    "content": "{release, {hello_world_example, \"1\"}, [hello_world]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/hello_world/src/hello_world_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(hello_world_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", toppage_h, []}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\thello_world_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/hello_world/src/hello_world_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(hello_world_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/hello_world/src/toppage_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc Hello world handler.\n-module(toppage_h).\n\n-export([init/2]).\n\ninit(Req0, Opts) ->\n\tReq = cowboy_req:reply(200, #{\n\t\t<<\"content-type\">> => <<\"text/plain\">>\n\t}, <<\"Hello world!\">>, Req0),\n\t{ok, Req, Opts}.\n"
  },
  {
    "path": "examples/markdown_middleware/Makefile",
    "content": "PROJECT = markdown_middleware\nPROJECT_DESCRIPTION = Cowboy static file handler example with middleware component\nPROJECT_VERSION = 1\n\nDEPS = cowboy markdown\ndep_cowboy_commit = master\ndep_markdown = git https://github.com/hypernumbers/erlmarkdown master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/markdown_middleware/README.asciidoc",
    "content": "= Middleware example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080/video.html\n\nCowboy will serve all the files you put in the `priv` directory.\nIf you request a `.html` file that has a corresponding `.md` file\nthat has been modified more recently than the `.html` file, the\nMarkdown file will be converted to HTML and served by Cowboy.\n"
  },
  {
    "path": "examples/markdown_middleware/priv/video.md",
    "content": "HTML5 Video With Markdown\n=========================\n\n<video controls>\n<source src=\"small.ogv\" type=\"video/ogg\"/>\n<source src=\"small.mp4\" type=\"video/mp4\"/>\n</video>\n\nVideos taken from [TechSlides](http://techslides.com/sample-webm-ogg-and-mp4-video-files-for-html5/)\n"
  },
  {
    "path": "examples/markdown_middleware/relx.config",
    "content": "{release, {markdown_middleware_example, \"1\"}, [markdown_middleware]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/markdown_middleware/src/markdown_converter.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n-module(markdown_converter).\n-behaviour(cowboy_middleware).\n\n-export([execute/2]).\n\nexecute(Req, Env) ->\n\t[Path] = cowboy_req:path_info(Req),\n\tcase filename:extension(Path) of\n\t\t<<\".html\">> -> maybe_generate_markdown(resource_path(Path));\n\t\t_Ext -> ok\n\tend,\n\t{ok, Req, Env}.\n\nmaybe_generate_markdown(Path) ->\n\tModifiedAt = filelib:last_modified(source_path(Path)),\n\tGeneratedAt = filelib:last_modified(Path),\n\tcase ModifiedAt > GeneratedAt of\n\t\ttrue -> markdown:conv_file(source_path(Path), Path);\n\t\tfalse -> ok\n\tend.\n\nresource_path(Path) ->\n\tPrivDir = code:priv_dir(markdown_middleware),\n\tfilename:join([PrivDir, Path]).\n\nsource_path(Path) ->\n\t<< (filename:rootname(Path))/binary, \".md\" >>.\n"
  },
  {
    "path": "examples/markdown_middleware/src/markdown_middleware_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(markdown_middleware_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/[...]\", cowboy_static, {priv_dir, markdown_middleware, \"\"}}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch},\n\t\tmiddlewares => [cowboy_router, markdown_converter, cowboy_handler]\n\t}),\n\tmarkdown_middleware_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/markdown_middleware/src/markdown_middleware_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(markdown_middleware_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/rest_basic_auth/Makefile",
    "content": "PROJECT = rest_basic_auth\nPROJECT_DESCRIPTION = Cowboy Basic HTTP Authorization example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/rest_basic_auth/README.asciidoc",
    "content": "= Basic authorization example using REST\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\n\n== HTTP/1.1 example output\n\nRequest with no authentication:\n\n[source,bash]\n----\n$ curl -i http://localhost:8080\nHTTP/1.1 401 Unauthorized\nconnection: keep-alive\nserver: Cowboy\ndate: Sun, 20 Jan 2013 14:10:27 GMT\ncontent-length: 0\nwww-authenticate: Basic realm=\"cowboy\"\n----\n\nRequest with authentication:\n\n[source,bash]\n----\n$ curl -i -u \"Alladin:open sesame\" http://localhost:8080\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Sun, 20 Jan 2013 14:11:12 GMT\ncontent-length: 16\ncontent-type: text/plain\n\nHello, Alladin!\n----\n\n== HTTP/2 example output\n\nRequest with no authentication:\n\n[source,bash]\n----\n$ nghttp -v http://localhost:8080\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.000] send HEADERS frame <length=38, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: */*\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n[  0.004] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.004] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.004] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.004] recv (stream_id=13) :status: 401\n[  0.004] recv (stream_id=13) content-length: 0\n[  0.004] recv (stream_id=13) date: Tue, 14 Jun 2016 09:15:56 GMT\n[  0.004] recv (stream_id=13) server: Cowboy\n[  0.004] recv (stream_id=13) www-authenticate: Basic realm=\"cowboy\"\n[  0.004] recv HEADERS frame <length=56, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\n[  0.004] recv DATA frame <length=0, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.004] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n\nRequest with authentication:\n\n[source,bash]\n----\n$ nghttp -v -H \"Authorization: Basic `echo -n Alladin:open sesame | base64`\" http://localhost:8080\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.001] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.001] send HEADERS frame <length=68, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: */*\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n          authorization: Basic QWxsYWRpbjpvcGVuIHNlc2FtZQ==\n[  0.002] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.002] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.002] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.004] recv (stream_id=13) :status: 200\n[  0.004] recv (stream_id=13) content-length: 16\n[  0.004] recv (stream_id=13) content-type: text/plain\n[  0.004] recv (stream_id=13) date: Tue, 14 Jun 2016 09:15:48 GMT\n[  0.004] recv (stream_id=13) server: Cowboy\n[  0.004] recv HEADERS frame <length=45, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\nHello, Alladin!\n[  0.004] recv DATA frame <length=16, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.004] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n"
  },
  {
    "path": "examples/rest_basic_auth/relx.config",
    "content": "{release, {rest_basic_auth_example, \"1\"}, [rest_basic_auth]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/rest_basic_auth/src/rest_basic_auth_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(rest_basic_auth_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", toppage_h, []}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\trest_basic_auth_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/rest_basic_auth/src/rest_basic_auth_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(rest_basic_auth_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/rest_basic_auth/src/toppage_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc Handler with basic HTTP authorization.\n-module(toppage_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([is_authorized/2]).\n-export([to_text/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\nis_authorized(Req, State) ->\n\tcase cowboy_req:parse_header(<<\"authorization\">>, Req) of\n\t\t{basic, User = <<\"Alladin\">>, <<\"open sesame\">>} ->\n\t\t\t{true, Req, User};\n\t\t_ ->\n\t\t\t{{false, <<\"Basic realm=\\\"cowboy\\\"\">>}, Req, State}\n\tend.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{<<\"text/plain\">>, to_text}\n\t], Req, State}.\n\nto_text(Req, User) ->\n\t{<< \"Hello, \", User/binary, \"!\\n\" >>, Req, User}.\n"
  },
  {
    "path": "examples/rest_hello_world/Makefile",
    "content": "PROJECT = rest_hello_world\nPROJECT_DESCRIPTION = Cowboy REST Hello World example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/rest_hello_world/README.asciidoc",
    "content": "= REST hello world example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\n\n== HTTP/1.1 example output\n\nRequest HTML:\n\n[source,bash]\n----\n$ curl -i http://localhost:8080\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Fri, 28 Sep 2012 04:15:52 GMT\ncontent-length: 136\ncontent-type: text/html\nvary: Accept\n\n<html>\n<head>\n  <meta charset=\"utf-8\">\n  <title>REST Hello World!</title>\n</head>\n<body>\n  <p>REST Hello World as HTML!</p>\n</body>\n</html>\n----\n\nRequest JSON:\n\n[source,bash]\n----\n$ curl -i -H \"Accept: application/json\" http://localhost:8080\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Fri, 28 Sep 2012 04:16:46 GMT\ncontent-length: 24\ncontent-type: application/json\nvary: Accept\n\n{\"rest\": \"Hello World!\"}\n----\n\nRequest plain text:\n\n[source,bash]\n----\n$ curl -i -H \"Accept: text/plain\" http://localhost:8080\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Fri, 28 Sep 2012 04:18:35 GMT\ncontent-length: 25\ncontent-type: text/plain\nvary: Accept\n\nREST Hello World as text!\n----\n\nRequest a non acceptable content-type:\n\n[source,bash]\n----\n$ curl -i -H \"Accept: text/css\" http://localhost:8080\nHTTP/1.1 406 Not Acceptable\nconnection: keep-alive\nserver: Cowboy\ndate: Fri, 28 Sep 2012 04:18:51 GMT\ncontent-length: 0\n\n----\n\n== HTTP/2 example output\n\nRequest HTML:\n\n[source,bash]\n----\n$ nghttp -v http://localhost:8080\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.000] send HEADERS frame <length=38, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: */*\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n[  0.000] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.000] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.000] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.001] recv (stream_id=13) :status: 200\n[  0.001] recv (stream_id=13) content-length: 136\n[  0.001] recv (stream_id=13) content-type: text/html\n[  0.001] recv (stream_id=13) date: Thu, 09 Jun 2016 14:28:50 GMT\n[  0.001] recv (stream_id=13) server: Cowboy\n[  0.001] recv (stream_id=13) vary: accept\n[  0.001] recv HEADERS frame <length=52, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\n<html>\n<head>\n\t<meta charset=\"utf-8\">\n\t<title>REST Hello World!</title>\n</head>\n<body>\n\t<p>REST Hello World as HTML!</p>\n</body>\n</html>[  0.001] recv DATA frame <length=136, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.001] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n\nRequest JSON:\n\n[source,bash]\n----\n$ nghttp -v -H \"accept: application/json\" http://localhost:8080\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.001] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.001] send HEADERS frame <length=46, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: application/json\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n[  0.001] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.001] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.001] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.001] recv (stream_id=13) :status: 200\n[  0.001] recv (stream_id=13) content-length: 24\n[  0.001] recv (stream_id=13) content-type: application/json\n[  0.001] recv (stream_id=13) date: Thu, 09 Jun 2016 14:29:00 GMT\n[  0.001] recv (stream_id=13) server: Cowboy\n[  0.001] recv (stream_id=13) vary: accept\n[  0.001] recv HEADERS frame <length=55, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\n{\"rest\": \"Hello World!\"}[  0.002] recv DATA frame <length=24, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.002] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n\nRequest plain text:\n\n[source,bash]\n----\n$ nghttp -v -H \"accept: text/plain\" http://localhost:8080\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.000] send HEADERS frame <length=42, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: text/plain\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n[  0.000] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.000] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.000] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.000] recv (stream_id=13) :status: 200\n[  0.000] recv (stream_id=13) content-length: 25\n[  0.000] recv (stream_id=13) content-type: text/plain\n[  0.000] recv (stream_id=13) date: Thu, 09 Jun 2016 14:28:25 GMT\n[  0.000] recv (stream_id=13) server: Cowboy\n[  0.000] recv (stream_id=13) vary: accept\n[  0.000] recv HEADERS frame <length=51, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\nREST Hello World as text![  0.000] recv DATA frame <length=25, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.000] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n\nRequest a non acceptable content-type:\n\n[source,bash]\n----\n$ nghttp -v -H \"accept: text/css\" http://localhost:8080\n[  0.000] Connected\n[  0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.000] send HEADERS frame <length=41, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: http\n          :authority: localhost:8080\n          accept: text/css\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n[  0.007] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.007] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.007] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.021] recv (stream_id=13) :status: 406\n[  0.021] recv (stream_id=13) content-length: 0\n[  0.021] recv (stream_id=13) date: Thu, 09 Jun 2016 14:29:15 GMT\n[  0.021] recv (stream_id=13) server: Cowboy\n[  0.021] recv HEADERS frame <length=39, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\n[  0.021] recv DATA frame <length=0, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.021] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n"
  },
  {
    "path": "examples/rest_hello_world/relx.config",
    "content": "{release, {rest_hello_world_example, \"1\"}, [rest_hello_world]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/rest_hello_world/src/rest_hello_world_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(rest_hello_world_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", toppage_h, []}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\trest_hello_world_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/rest_hello_world/src/rest_hello_world_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(rest_hello_world_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/rest_hello_world/src/toppage_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc Hello world handler.\n-module(toppage_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([hello_to_html/2]).\n-export([hello_to_json/2]).\n-export([hello_to_text/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{<<\"text/html\">>, hello_to_html},\n\t\t{<<\"application/json\">>, hello_to_json},\n\t\t{<<\"text/plain\">>, hello_to_text}\n\t], Req, State}.\n\nhello_to_html(Req, State) ->\n\tBody = <<\"<html>\n<head>\n\t<meta charset=\\\"utf-8\\\">\n\t<title>REST Hello World!</title>\n</head>\n<body>\n\t<p>REST Hello World as HTML!</p>\n</body>\n</html>\">>,\n\t{Body, Req, State}.\n\nhello_to_json(Req, State) ->\n\tBody = <<\"{\\\"rest\\\": \\\"Hello World!\\\"}\">>,\n\t{Body, Req, State}.\n\nhello_to_text(Req, State) ->\n\t{<<\"REST Hello World as text!\">>, Req, State}.\n"
  },
  {
    "path": "examples/rest_pastebin/Makefile",
    "content": "PROJECT = rest_pastebin\nPROJECT_DESCRIPTION = Cowboy REST Pastebin example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/rest_pastebin/README.asciidoc",
    "content": "= REST pastebin example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\n\n== Usage\n\nTo upload something to the paste application, you can use `curl`:\n\n[source,bash]\n$ <command> | curl -i --data-urlencode paste@- localhost:8080\n\nOr, to upload the file `my_file`:\n\n[source,bash]\ncurl -i --data-urlencode paste@my_file localhost:8080\n\nThe URL of your data will be in the location header. Alternately, you can visit\nhttp://localhost:8080 with your favorite web browser and submit your paste via\nthe form.\n\nCode that has been pasted can be highlighted with ?lang=<language> option if\nyou have http://www.andre-simon.de/doku/highlight/en/highlight.html[highlight]\ninstalled (although `pygments` or any other should work just fine).\n\nThis will show the contents of the HTML file:\n\n[source,bash]\ncurl -i --data-urlencode paste@priv/index.html localhost:8080\ncurl <url from location header>\n\nIf your terminal supports color sequences and `highlight` is installed,\nthe following command will show the same contents but with HTML syntax\nhighlighting.\n\n[source,bash]\ncurl <url from location header>?lang=html\n\nIf you open the same URL in your web browser and your web browser tells\nCowboy that it prefers HTML files, you will see the file highlighted\nwith special HTML markup and CSS. Firefox is known to work.\n"
  },
  {
    "path": "examples/rest_pastebin/priv/index.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<title>Simple Pastebin</title>\n\t</head>\n\t<body>\n\t\t<h1>Simple Pastebin</h1>\n\t\t<p>\n\t\t\tYou can paste your text into the text field to submit, or you can\n\t\t\tcapture the output of a command with:\n\t\t</p>\n\t\t<code>\n\t\t\t<i>command</i> | curl -i --data-urlencode paste@- localhost:8080\n\t\t</code>\n\t\t<form action=\"/\" method=\"post\">\n\t\t\t<textarea cols=\"80\" rows=\"15\" name=\"paste\"></textarea>\n\t\t\t<div>\n\t\t\t\t<button type=\"submit\">Upload your code</button>\n\t\t\t</div>\n\t\t</form>\n\t</body>\n</html>\n"
  },
  {
    "path": "examples/rest_pastebin/priv/index.txt",
    "content": "Simple Pastebin\n---------------\n\nYou can paste your text into the text field to submit, or you can capture the\noutput of a command with:\n\n<command> | curl -i --data-urlencode paste@- localhost:8080\n"
  },
  {
    "path": "examples/rest_pastebin/relx.config",
    "content": "{release, {rest_pastebin_example, \"1\"}, [rest_pastebin]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/rest_pastebin/src/rest_pastebin_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(rest_pastebin_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/[:paste_id]\", toppage_h, []}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\trest_pastebin_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/rest_pastebin/src/rest_pastebin_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(rest_pastebin_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/rest_pastebin/src/toppage_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc Pastebin handler.\n-module(toppage_h).\n\n%% Standard callbacks.\n-export([init/2]).\n-export([allowed_methods/2]).\n-export([content_types_provided/2]).\n-export([content_types_accepted/2]).\n-export([resource_exists/2]).\n\n%% Custom callbacks.\n-export([create_paste/2]).\n-export([paste_html/2]).\n-export([paste_text/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\nallowed_methods(Req, State) ->\n\t{[<<\"GET\">>, <<\"POST\">>], Req, State}.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{{<<\"text\">>, <<\"plain\">>, []}, paste_text},\n\t\t{{<<\"text\">>, <<\"html\">>, []}, paste_html}\n\t], Req, State}.\n\ncontent_types_accepted(Req, State) ->\n\t{[{{<<\"application\">>, <<\"x-www-form-urlencoded\">>, '*'}, create_paste}],\n\t\tReq, State}.\n\nresource_exists(Req, _State) ->\n\tcase cowboy_req:binding(paste_id, Req) of\n\t\tundefined ->\n\t\t\t{true, Req, index};\n\t\tPasteID ->\n\t\t\tcase valid_path(PasteID) and file_exists(PasteID) of\n\t\t\t\ttrue -> {true, Req, PasteID};\n\t\t\t\tfalse -> {false, Req, PasteID}\n\t\t\tend\n\tend.\n\ncreate_paste(Req, State) ->\n\tPasteID = new_paste_id(),\n\t{ok, [{<<\"paste\">>, Paste}], Req2} = cowboy_req:read_urlencoded_body(Req),\n\tok = file:write_file(full_path(PasteID), Paste),\n\tcase cowboy_req:method(Req2) of\n\t\t<<\"POST\">> ->\n\t\t\t{{true, <<$/, PasteID/binary>>}, Req2, State};\n\t\t_ ->\n\t\t\t{true, Req2, State}\n\tend.\n\npaste_html(Req, index) ->\n\t{read_file(\"index.html\"), Req, index};\npaste_html(Req, Paste) ->\n\t#{lang := Lang} = cowboy_req:match_qs([{lang, [fun lang_constraint/2], plain}], Req),\n\t{format_html(Paste, Lang), Req, Paste}.\n\npaste_text(Req, index) ->\n\t{read_file(\"index.txt\"), Req, index};\npaste_text(Req, Paste) ->\n\t#{lang := Lang} = cowboy_req:match_qs([{lang, [fun lang_constraint/2], plain}], Req),\n\t{format_text(Paste, Lang), Req, Paste}.\n\n% Private\n\nlang_constraint(forward, Bin) ->\n\tcase re:run(Bin, \"^[a-z0-9_]+$\", [{capture, none}]) of\n\t\tmatch -> {ok, Bin};\n\t\tnomatch -> {error, bad_lang}\n\tend;\nlang_constraint(format_error, {bad_lang, _}) ->\n\t\"Invalid lang parameter.\".\n\nread_file(Name) ->\n\t{ok, Binary} = file:read_file(full_path(Name)),\n\tBinary.\n\nfull_path(Name) ->\n\tfilename:join([code:priv_dir(rest_pastebin), Name]).\n\nfile_exists(Name) ->\n\tcase file:read_file_info(full_path(Name)) of\n\t\t{ok, _Info} -> true;\n\t\t{error, _Reason} -> false\n\tend.\n\nvalid_path(<<>>) -> true;\nvalid_path(<<$., _T/binary>>) -> false;\nvalid_path(<<$/, _T/binary>>) -> false;\nvalid_path(<<_Char, T/binary>>) -> valid_path(T).\n\nnew_paste_id() ->\n\tInitial = rand:uniform(62) - 1,\n\tnew_paste_id(<<Initial>>, 7).\nnew_paste_id(Bin, 0) ->\n\tChars = <<\"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890\">>,\n\t<< <<(binary_part(Chars, B, 1))/binary>> || <<B>> <= Bin >>;\nnew_paste_id(Bin, Rem) ->\n\tNext = rand:uniform(62) - 1,\n\tnew_paste_id(<<Bin/binary, Next>>, Rem - 1).\n\nformat_html(Paste, plain) ->\n\tText = escape_html_chars(read_file(Paste)),\n\t<<\"<!DOCTYPE html><html>\",\n\t\"<head><title>paste</title></head>\",\n\t\"<body><pre><code>\", Text/binary, \"</code></pre></body></html>\\n\">>;\nformat_html(Paste, Lang) ->\n\thighlight(full_path(Paste), Lang, \"html\").\n\nformat_text(Paste, plain) ->\n\tread_file(Paste);\nformat_text(Paste, Lang) ->\n\thighlight(full_path(Paste), Lang, \"ansi\").\n\nhighlight(Path, Lang, Type) ->\n\tPath1 = binary_to_list(Path),\n\tLang1 = binary_to_list(Lang),\n\tos:cmd([\"highlight --syntax=\", Lang1,\n\t\t\" --doc-title=paste \",\n\t\t\" --out-format=\", Type,\n\t\t\" --include-style \", Path1]).\n\n% Escape some HTML characters that might make a fuss\nescape_html_chars(Bin) ->\n\t<< <<(escape_html_char(B))/binary>> || <<B>> <= Bin >>.\n\nescape_html_char($<) -> <<\"&lt;\">>;\nescape_html_char($>) -> <<\"&gt;\">>;\nescape_html_char($&) -> <<\"&amp;\">>;\nescape_html_char(C) -> <<C>>.\n"
  },
  {
    "path": "examples/ssl_hello_world/Makefile",
    "content": "PROJECT = ssl_hello_world\nPROJECT_DESCRIPTION = Cowboy SSL Hello World example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\nLOCAL_DEPS = ssl\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/ssl_hello_world/README.asciidoc",
    "content": "= Secure hello world example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to https://localhost:8443\n\nYou will be greeted by a security message. You can ask for more\ninformation and ultimately accept to access localhost. This is\ndue to the example using a self-signed certificate.\n\nRecent browsers will communicate using HTTP/2. Older browsers\nwill use HTTP/1.1.\n\n== HTTP/1.1 example output\n\n[source,bash]\n----\n$ curl -k -i https://localhost:8443\nHTTP/1.1 200 OK\nconnection: keep-alive\nserver: Cowboy\ndate: Fri, 28 Sep 2012 04:10:25 GMT\ncontent-length: 12\n\nHello world!\n----\n\n== HTTP/2 example output\n\n[source,bash]\n----\n$ nghttp -v https://localhost:8443\n[  0.001] Connected\nThe negotiated protocol: h2\n[  0.009] recv SETTINGS frame <length=0, flags=0x00, stream_id=0>\n          (niv=0)\n[  0.009] send SETTINGS frame <length=12, flags=0x00, stream_id=0>\n          (niv=2)\n          [SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]\n          [SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]\n[  0.009] send SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.009] send PRIORITY frame <length=5, flags=0x00, stream_id=3>\n          (dep_stream_id=0, weight=201, exclusive=0)\n[  0.009] send PRIORITY frame <length=5, flags=0x00, stream_id=5>\n          (dep_stream_id=0, weight=101, exclusive=0)\n[  0.009] send PRIORITY frame <length=5, flags=0x00, stream_id=7>\n          (dep_stream_id=0, weight=1, exclusive=0)\n[  0.009] send PRIORITY frame <length=5, flags=0x00, stream_id=9>\n          (dep_stream_id=7, weight=1, exclusive=0)\n[  0.009] send PRIORITY frame <length=5, flags=0x00, stream_id=11>\n          (dep_stream_id=3, weight=1, exclusive=0)\n[  0.009] send HEADERS frame <length=38, flags=0x25, stream_id=13>\n          ; END_STREAM | END_HEADERS | PRIORITY\n          (padlen=0, dep_stream_id=11, weight=16, exclusive=0)\n          ; Open new stream\n          :method: GET\n          :path: /\n          :scheme: https\n          :authority: localhost:8443\n          accept: */*\n          accept-encoding: gzip, deflate\n          user-agent: nghttp2/1.7.1\n[  0.010] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>\n          ; ACK\n          (niv=0)\n[  0.010] recv (stream_id=13) :status: 200\n[  0.010] recv (stream_id=13) content-length: 12\n[  0.010] recv (stream_id=13) content-type: text/plain\n[  0.010] recv (stream_id=13) date: Sat, 30 Apr 2016 12:54:32 GMT\n[  0.010] recv (stream_id=13) server: Cowboy\n[  0.010] recv HEADERS frame <length=45, flags=0x04, stream_id=13>\n          ; END_HEADERS\n          (padlen=0)\n          ; First response header\nHello world![  0.010] recv DATA frame <length=12, flags=0x01, stream_id=13>\n          ; END_STREAM\n[  0.010] send GOAWAY frame <length=8, flags=0x00, stream_id=0>\n          (last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])\n----\n"
  },
  {
    "path": "examples/ssl_hello_world/priv/ssl/cert.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIDTzCCAjegAwIBAgIUD7jNyCgABo8GlnEojOSTFWZzkJswDQYJKoZIhvcNAQEL\nBQAwNzELMAkGA1UEBhMCRlIxEzARBgNVBAgMClNvbWUtU3RhdGUxEzARBgNVBAoM\nCk5pbmUgTmluZXMwHhcNMjQwMTI2MTQyODExWhcNMzcxMDA0MTQyODExWjA3MQsw\nCQYDVQQGEwJGUjETMBEGA1UECAwKU29tZS1TdGF0ZTETMBEGA1UECgwKTmluZSBO\naW5lczCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKfNEwF0v1Gm2e6a\nM4hqI3JhmerZSNYWw8NiaUybR5hVUS9X4Chk+/y8kBLX2OYbGGlAxgbOZJa5D+kf\nH1iakoUQaILinxPx3yxtIOePS3q/Xi5/EBVTdwLOoI26oSdzY2RTKKAPO1PCcAjq\n6gDpw2u7q26sSU1kul6dD4Wle6+yNtnJdNKo9zLCLXr6TtuHdvbAU1oblLCKZ1Db\n/uLkhGaUI/EUNeU1ZJrPmnoneYkTcG5mC5PMFVhqJ3bNYez5Hgr2Ra1Fz0dVgmRM\nFpJ8NF6UQgA9dAs2Oh1uWbTjJiX0tO92RslXlhpLHS2VKZWsxiN2bniNXsNKzQ9M\nty0qnxkCAwEAAaNTMFEwHQYDVR0OBBYEFKuBPzB9rBCJNAnUyQMXjkVKIMJlMB8G\nA1UdIwQYMBaAFKuBPzB9rBCJNAnUyQMXjkVKIMJlMA8GA1UdEwEB/wQFMAMBAf8w\nDQYJKoZIhvcNAQELBQADggEBAHWXDKlY39csROTQ2Dm3CnTj14tj3cW4onsOYTKW\nFSlVdMOk3+ionB4vZA/Ino8OjrjiZ2dB3Tvl2J+AxEea3ltDbdh6qVuqSwvQZCeV\n8gWp05wzyTfIpQRD10ZwOU6dzR89T+o7oG/7D8Ydk3nzecthF1aU0YBW8OtuZFog\nlC/PIIoVEyUiTEnFJrkQge1OmZWiAuImIed+cEmkw9ZAN2/9i/OxWZKAGoKrmfPq\nkzdOoxxFRLnqHo2OYdA0IPpSuGK5ayjYrLgXW0Wa4FKzmDh7Gy+JSrvLuFur9PEi\nD0Encva2uX1hAcFQDrzICTsD6ANuIbw0cmlrCJYH6E21PrM=\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "examples/ssl_hello_world/priv/ssl/key.pem",
    "content": "-----BEGIN PRIVATE KEY-----\nMIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCnzRMBdL9Rptnu\nmjOIaiNyYZnq2UjWFsPDYmlMm0eYVVEvV+AoZPv8vJAS19jmGxhpQMYGzmSWuQ/p\nHx9YmpKFEGiC4p8T8d8sbSDnj0t6v14ufxAVU3cCzqCNuqEnc2NkUyigDztTwnAI\n6uoA6cNru6turElNZLpenQ+FpXuvsjbZyXTSqPcywi16+k7bh3b2wFNaG5SwimdQ\n2/7i5IRmlCPxFDXlNWSaz5p6J3mJE3BuZguTzBVYaid2zWHs+R4K9kWtRc9HVYJk\nTBaSfDRelEIAPXQLNjodblm04yYl9LTvdkbJV5YaSx0tlSmVrMYjdm54jV7DSs0P\nTLctKp8ZAgMBAAECggEAR5e6D6l5hUNcgS4+ZWnvhLo6utYI+vrMfFzNE3e+5LIm\nCL6D74gicRMcn0WDj62ozSNrOfUuOpZrwOlb7OhKMkataIZ7G73bG6/V1aYwLIdg\njhL9UDQDt2lkXAPwBQ54rhHC6AOHqvVu6ocb3tbd32W7P2V3gvNChuKZAEr6Chwc\n1JE5e1k7uZK4rjqZhd86pV2hks/jNknAZpEROTw80qpo3MzlMDMhXyKmyGa84t91\n1bijJ2DMPKsaxSYkWa06Zx3ymiX+qtKFRnSqZo2aEqpeTgQ0hRBSA429d7uCKO0o\nkwqOyT85qMFRA+4jfkcAwUi4DELVCFlN/QNWCMH09wKBgQDVuw/sGnjVxCQ/s7pH\nFuGA55S1qUtrcYsMHV5uZNtxLOqeAURomgiTpDVNNhLBuJwVjZrBv8Msl1/99EZ7\n8Hws+ERcjlbmyBiq6/VdRW6bJsrFnOS4qUbwWQp0Yztdeu6sTwIEI0KO/oFypf9G\nL9mwjXwTvWEFg5etW1BPq+XmMwKBgQDI/KXNul1zCnrOY6sYrbPShYLZgPQRjNi5\nHo6N5NxRc3xhyzExbjNtA/N/30d+/p7H8ND+TgpsYdjvEqqgpQQmCeg3/n6eSzb2\nhotCVBt8dU2TjD5v68DLzGv61s7PV81e4grkU5nCe+y7zJMwKGQ8BbmYTBBYEO0P\nnTHwuwHhgwKBgQCx2B8OopRro/NZwm69Wq+3+HtIkh98vxUptoJuL6RdzzdG1N0c\ngRej6t6jadw/sCLI2HSuxaddQnSQt6Oy29AoB0mzDooHLPdBumgH/Y9ksOnHd57m\nfYzWz/CgGjY6ueFCJdgSo1ht7h6+zJvWxlhIzeIx9sJ1uSMMEFCKiwoY+wKBgGb+\nkTjLt/er9yKskJEk8nF/WX58RpZ3xteWgRbVoNFcjPDQX3UlM9U5oR52HP1HHbb4\nASFQfKbtvW1F84o/BdE4YnfPQrN7d779U3+5+hvdQNPLmnNgLHxDVVJFodU++U8W\nJt66uKChQL88JnEXQcZAaMtSr01x3wmRVHY4Xs5hAoGBAMPfa+rcGukjbMF+MZ0P\nZV1Pq7AxVJ/C0XINnpZrsN+e6dO52Y2VXbnQkML7PKZXzSY88QwunBp88VoPlDux\nllmLZc54zUFlsC1iHrEzt+hoxFG0tfL83vic5kSx6u5oZdxjZ2InqTzE8TmORU3v\n6/ik7Q4VeDQ5uLnR4GiLW+qj\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "examples/ssl_hello_world/relx.config",
    "content": "{release, {ssl_hello_world_example, \"1\"}, [ssl_hello_world]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/ssl_hello_world/src/ssl_hello_world_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(ssl_hello_world_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\n\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", toppage_h, []}\n\t\t]}\n\t]),\n\tPrivDir = code:priv_dir(ssl_hello_world),\n\t{ok, _} = cowboy:start_tls(https, [\n\t\t{port, 8443},\n\t\t{certfile, PrivDir ++ \"/ssl/cert.pem\"},\n\t\t{keyfile, PrivDir ++ \"/ssl/key.pem\"}\n\t], #{env => #{dispatch => Dispatch}}),\n\tssl_hello_world_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(https).\n"
  },
  {
    "path": "examples/ssl_hello_world/src/ssl_hello_world_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(ssl_hello_world_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/ssl_hello_world/src/toppage_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc Hello world handler.\n-module(toppage_h).\n\n-export([init/2]).\n\ninit(Req0, Opts) ->\n\tReq = cowboy_req:reply(200, #{\n\t\t<<\"content-type\">> => <<\"text/plain\">>\n\t}, <<\"Hello world!\">>, Req0),\n\t{ok, Req, Opts}.\n"
  },
  {
    "path": "examples/upload/Makefile",
    "content": "PROJECT = upload\nPROJECT_DESCRIPTION = Cowboy multipart upload example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/upload/README.asciidoc",
    "content": "= Multipart upload example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\n\nThe uploaded file will be displayed in the shell directly.\n"
  },
  {
    "path": "examples/upload/priv/index.html",
    "content": "<html>\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=ISO-8859-1\">\n    <title>Multipart upload example</title>\n  </head>\n\n  <body>\n    <form method=\"post\" enctype=\"multipart/form-data\" action=\"/upload\">\n      <input type=\"file\" name=\"inputfile\"/>\n      <input type=\"submit\"/>\n    </form>\n  </body>\n</html> \n"
  },
  {
    "path": "examples/upload/relx.config",
    "content": "{release, {upload_example, \"1\"}, [upload]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/upload/src/upload_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(upload_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", cowboy_static, {priv_file, upload, \"index.html\"}},\n\t\t\t{\"/upload\", upload_h, []}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\tupload_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/upload/src/upload_h.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @doc Upload handler.\n-module(upload_h).\n\n-export([init/2]).\n\ninit(Req, Opts) ->\n\t{ok, Headers, Req2} = cowboy_req:read_part(Req),\n\t{ok, Data, Req3} = cowboy_req:read_part_body(Req2),\n\t{file, <<\"inputfile\">>, Filename, ContentType}\n\t\t= cow_multipart:form_data(Headers),\n\tio:format(\"Received file ~p of content-type ~p as follow:~n~p~n~n\",\n\t\t[Filename, ContentType, Data]),\n\t{ok, Req3, Opts}.\n"
  },
  {
    "path": "examples/upload/src/upload_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(upload_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/websocket/Makefile",
    "content": "PROJECT = websocket\nPROJECT_DESCRIPTION = Cowboy Websocket example\nPROJECT_VERSION = 1\n\nDEPS = cowboy\ndep_cowboy_commit = master\n\nREL_DEPS = relx\n\ninclude ../../erlang.mk\n"
  },
  {
    "path": "examples/websocket/README.asciidoc",
    "content": "= Websocket example\n\nTo try this example, you need GNU `make` and `git` in your PATH.\n\nTo build and run the example, use the following command:\n\n[source,bash]\n$ make run\n\nThen point your browser to http://localhost:8080\n"
  },
  {
    "path": "examples/websocket/priv/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n\t<meta charset=\"utf-8\"/>\n\t<title>Websocket client</title>\n</head>\n\n<body>\n\n<header>\n\t<h1>Websocket client</h1>\n\t<div id=\"status\"></div>\n</header>\n\n<nav>\n\t<div id=\"connecting\">\n\t\t<input type='text' id=\"server\" value=\"\"></input>\n\t\t<button type=\"button\" onclick=\"toggle_connection()\">connection</button>\n\t</div>\n\n\t<div id=\"connected\">\n\t\t<input type='text' id=\"message\" value=\"\"></input>\n\t\t<button type=\"button\" onclick=\"sendTxt();\">send</button>\n\t</div>\n</nav>\n\n<main id=\"content\">\n\t<button id=\"clear\" onclick=\"clearScreen()\" >Clear text</button>\n\t<div id=\"output\"></div>\n</main>\n\n<script type=\"text/javascript\">\n\nvar websocket;\nvar server = document.getElementById(\"server\");\nvar message = document.getElementById(\"message\");\nvar connecting = document.getElementById(\"connecting\");\nvar connected = document.getElementById(\"connected\");\nvar content = document.getElementById(\"content\");\nvar output = document.getElementById(\"output\");\n\nserver.value = \"ws://\" + window.location.host + \"/websocket\";\nconnected.style.display = \"none\";\ncontent.style.display = \"none\";\n\nfunction connect()\n{\n\twsHost = server.value;\n\twebsocket = new WebSocket(wsHost);\n\tshowScreen('<b>Connecting to: ' +  wsHost + '</b>');\n\twebsocket.onopen = function(evt) { onOpen(evt) };\n\twebsocket.onclose = function(evt) { onClose(evt) };\n\twebsocket.onmessage = function(evt) { onMessage(evt) };\n\twebsocket.onerror = function(evt) { onError(evt) };\n};\n\nfunction disconnect() {\n\twebsocket.close();\n};\n\nfunction toggle_connection(){\n\tif (websocket && websocket.readyState == websocket.OPEN) {\n\t\tdisconnect();\n\t} else {\n\t\tconnect();\n\t};\n};\n\nfunction sendTxt() {\n\tif (websocket.readyState == websocket.OPEN) {\n\t\tvar msg = message.value;\n\t\twebsocket.send(msg);\n\t\tshowScreen('sending: ' + msg);\n\t} else {\n\t\tshowScreen('websocket is not connected');\n\t};\n};\n\nfunction onOpen(evt) {\n\tshowScreen('<span style=\"color: green;\">CONNECTED </span>');\n\tconnecting.style.display = \"none\";\n\tconnected.style.display = \"\";\n\tcontent.style.display = \"\";\n};\n\nfunction onClose(evt) {\n\tshowScreen('<span style=\"color: red;\">DISCONNECTED</span>');\n};\n\nfunction onMessage(evt) {\n\tshowScreen('<span style=\"color: blue;\">RESPONSE: ' + evt.data + '</span>');\n};\n\nfunction onError(evt) {\n\tshowScreen('<span style=\"color: red;\">ERROR: ' + evt.data + '</span>');\n};\n\nfunction showScreen(html) {\n\tvar el = document.createElement(\"p\");\n\tel.innerHTML = html;\n\toutput.insertBefore(el, output.firstChild);\n};\n\nfunction clearScreen() {\n\toutput.innerHTML = \"\";\n};\n\n</script>\n</body>\n</html> \n"
  },
  {
    "path": "examples/websocket/relx.config",
    "content": "{release, {websocket_example, \"1\"}, [websocket]}.\n{extended_start_script, true}.\n"
  },
  {
    "path": "examples/websocket/src/websocket_app.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(websocket_app).\n-behaviour(application).\n\n%% API.\n-export([start/2]).\n-export([stop/1]).\n\n%% API.\nstart(_Type, _Args) ->\n\tDispatch = cowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", cowboy_static, {priv_file, websocket, \"index.html\"}},\n\t\t\t{\"/websocket\", ws_h, []},\n\t\t\t{\"/static/[...]\", cowboy_static, {priv_dir, websocket, \"static\"}}\n\t\t]}\n\t]),\n\t{ok, _} = cowboy:start_clear(http, [{port, 8080}], #{\n\t\tenv => #{dispatch => Dispatch}\n\t}),\n\twebsocket_sup:start_link().\n\nstop(_State) ->\n\tok = cowboy:stop_listener(http).\n"
  },
  {
    "path": "examples/websocket/src/websocket_sup.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n%% @private\n-module(websocket_sup).\n-behaviour(supervisor).\n\n%% API.\n-export([start_link/0]).\n\n%% supervisor.\n-export([init/1]).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n%% supervisor.\n\ninit([]) ->\n\tProcs = [],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "examples/websocket/src/ws_h.erl",
    "content": "-module(ws_h).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_websocket, Req, Opts}.\n\nwebsocket_init(State) ->\n\terlang:start_timer(1000, self(), <<\"Hello!\">>),\n\t{[], State}.\n\nwebsocket_handle({text, Msg}, State) ->\n\t{[{text, << \"That's what she said! \", Msg/binary >>}], State};\nwebsocket_handle(_Data, State) ->\n\t{[], State}.\n\nwebsocket_info({timeout, _Ref, Msg}, State) ->\n\terlang:start_timer(1000, self(), <<\"How' you doin'?\">>),\n\t{[{text, Msg}], State};\nwebsocket_info(_Info, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "plugins.mk",
    "content": "# See LICENSE for licensing information.\n\n# Plain HTTP handlers.\ndefine tpl_cowboy.http\n-module($(n)).\n-behavior(cowboy_handler).\n\n-export([init/2]).\n\ninit(Req, State) ->\n\t{ok, Req, State}.\nendef\n\n# Loop handlers.\ndefine tpl_cowboy.loop\n-module($(n)).\n-behavior(cowboy_loop).\n\n-export([init/2]).\n-export([info/3]).\n\ninit(Req, State) ->\n\t{cowboy_loop, Req, State, hibernate}.\n\ninfo(_Info, Req, State) ->\n\t{ok, Req, State, hibernate}.\nendef\n\n# REST handlers.\ndefine tpl_cowboy.rest\n-module($(n)).\n-behavior(cowboy_rest).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([to_html/2]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{{<<\"text\">>, <<\"html\">>, '*'}, to_html}\n\t], Req, State}.\n\nto_html(Req, State) ->\n\t{<<\"<html><body>This is REST!</body></html>\">>, Req, State}.\nendef\n\n# Websocket handlers.\ndefine tpl_cowboy.ws\n-module($(n)).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, State) ->\n\t{cowboy_websocket, Req, State}.\n\nwebsocket_init(State) ->\n\t{[], State}.\n\nwebsocket_handle({text, Data}, State) ->\n\t{[{text, Data}], State};\nwebsocket_handle({binary, Data}, State) ->\n\t{[{binary, Data}], State};\nwebsocket_handle(_Frame, State) ->\n\t{[], State}.\n\nwebsocket_info(_Info, State) ->\n\t{[], State}.\nendef\n"
  },
  {
    "path": "rebar.config",
    "content": "{deps, [\n{cowlib,\".*\",{git,\"https://github.com/ninenines/cowlib\",{tag,\"2.16.0\"}}},{ranch,\".*\",{git,\"https://github.com/ninenines/ranch\",{tag,\"1.8.1\"}}}\n]}.\n{erl_opts, [debug_info,warn_export_vars,warn_shadow_vars,warn_obsolete_guard,warn_missing_spec,warn_untyped_record]}.\n"
  },
  {
    "path": "src/cowboy.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy).\n\n-export([start_clear/3]).\n-export([start_tls/3]).\n-export([stop_listener/1]).\n-export([get_env/2]).\n-export([get_env/3]).\n-export([set_env/3]).\n\n-ifdef(COWBOY_QUICER).\n-export([start_quic/3]).\n\n%% Don't warn about the bad quicer specs.\n-dialyzer([{nowarn_function, start_quic/3}]).\n-endif.\n\n%% Internal.\n-export([log/2]).\n-export([log/4]).\n\n-type opts() :: cowboy_http:opts() | cowboy_http2:opts().\n-export_type([opts/0]).\n\n-type fields() :: [atom()\n\t| {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()]}\n\t| {atom(), cowboy_constraints:constraint() | [cowboy_constraints:constraint()], any()}].\n-export_type([fields/0]).\n\n-type http_headers() :: #{binary() => iodata()}.\n-export_type([http_headers/0]).\n\n-type http_status() :: non_neg_integer() | binary().\n-export_type([http_status/0]).\n\n-type http_version() :: 'HTTP/2' | 'HTTP/1.1' | 'HTTP/1.0'.\n-export_type([http_version/0]).\n\n-spec start_clear(ranch:ref(), ranch:opts(), opts())\n\t-> {ok, pid()} | {error, any()}.\n\nstart_clear(Ref, TransOpts0, ProtoOpts0) ->\n\tTransOpts1 = ranch:normalize_opts(TransOpts0),\n\t{TransOpts2, DynamicBuffer} = ensure_dynamic_buffer(TransOpts1, ProtoOpts0),\n\t{TransOpts, ConnectionType} = ensure_connection_type(TransOpts2),\n\tProtoOpts = ProtoOpts0#{\n\t\tconnection_type => ConnectionType,\n\t\tdynamic_buffer => DynamicBuffer\n\t},\n\tranch:start_listener(Ref, ranch_tcp, TransOpts, cowboy_clear, ProtoOpts).\n\n-spec start_tls(ranch:ref(), ranch:opts(), opts())\n\t-> {ok, pid()} | {error, any()}.\n\nstart_tls(Ref, TransOpts0, ProtoOpts0) ->\n\tTransOpts1 = ranch:normalize_opts(TransOpts0),\n\t{TransOpts2, DynamicBuffer} = ensure_dynamic_buffer(TransOpts1, ProtoOpts0),\n\tTransOpts3 = ensure_alpn(TransOpts2),\n\t{TransOpts, ConnectionType} = ensure_connection_type(TransOpts3),\n\tProtoOpts = ProtoOpts0#{\n\t\tconnection_type => ConnectionType,\n\t\tdynamic_buffer => DynamicBuffer\n\t},\n\tranch:start_listener(Ref, ranch_ssl, TransOpts, cowboy_tls, ProtoOpts).\n\n-ifdef(COWBOY_QUICER).\n\n%% @todo Experimental function to start a barebone QUIC listener.\n%%       This will need to be reworked to be closer to Ranch\n%%       listeners and provide equivalent features.\n%%\n%% @todo Better type for transport options. Might require fixing quicer types.\n\n-spec start_quic(ranch:ref(), #{socket_opts => [{atom(), _}]}, cowboy_http3:opts())\n\t-> {ok, pid()}.\n\n%% @todo Implement dynamic_buffer for HTTP/3 if/when it applies.\nstart_quic(Ref, TransOpts, ProtoOpts) ->\n\t{ok, _} = application:ensure_all_started(quicer),\n\tParent = self(),\n\tSocketOpts0 = maps:get(socket_opts, TransOpts, []),\n\t{Port, SocketOpts2} = case lists:keytake(port, 1, SocketOpts0) of\n\t\t{value, {port, Port0}, SocketOpts1} ->\n\t\t\t{Port0, SocketOpts1};\n\t\tfalse ->\n\t\t\t{port_0(), SocketOpts0}\n\tend,\n\tSocketOpts = [\n\t\t{alpn, [\"h3\"]}, %% @todo Why not binary?\n\t\t%% We only need 3 for control and QPACK enc/dec,\n\t\t%% but we need more for WebTransport. %% @todo Use 3 if WT is disabled.\n\t\t{peer_unidi_stream_count, 100},\n\t\t{peer_bidi_stream_count, 100},\n\t\t%% For WebTransport.\n\t\t%% @todo We probably don't want it enabled if WT isn't used.\n\t\t{datagram_send_enabled, 1},\n\t\t{datagram_receive_enabled, 1}\n\t|SocketOpts2],\n\t_ListenerPid = spawn(fun() ->\n\t\t{ok, Listener} = quicer:listen(Port, SocketOpts),\n\t\tParent ! {ok, Listener},\n\t\t_AcceptorPid = [spawn(fun AcceptLoop() ->\n\t\t\t{ok, Conn} = quicer:accept(Listener, []),\n\t\t\tPid = spawn(fun() ->\n\t\t\t\treceive go -> ok end,\n\t\t\t\t%% We have to do the handshake after handing control of\n\t\t\t\t%% the connection otherwise streams may come in before\n\t\t\t\t%% the controlling process is changed and messages will\n\t\t\t\t%% not be sent to the correct process.\n\t\t\t\t{ok, Conn} = quicer:handshake(Conn),\n\t\t\t\tprocess_flag(trap_exit, true), %% @todo Only if supervisor though.\n\t\t\t\ttry cowboy_http3:init(Parent, Ref, Conn, ProtoOpts)\n\t\t\t\tcatch\n\t\t\t\t\texit:{shutdown,_} -> ok;\n\t\t\t\t\tC:E:S -> log(error, \"CRASH ~p:~p:~p\", [C,E,S], ProtoOpts)\n\t\t\t\tend\n\t\t\tend),\n\t\t\tok = quicer:controlling_process(Conn, Pid),\n\t\t\tPid ! go,\n\t\t\tAcceptLoop()\n\t\tend) || _ <- lists:seq(1, 20)],\n\t\t%% Listener process must not terminate.\n\t\treceive after infinity -> ok end\n\tend),\n\treceive\n\t\t{ok, Listener} ->\n\t\t\t{ok, Listener}\n\tend.\n\n%% Select a random UDP port using gen_udp because quicer\n%% does not provide equivalent functionality. Taken from\n%% quicer test suites.\nport_0() ->\n\t{ok, Socket} = gen_udp:open(0, [{reuseaddr, true}]),\n\t{ok, {_, Port}} = inet:sockname(Socket),\n\tgen_udp:close(Socket),\n\tcase os:type() of\n\t\t{unix, darwin} ->\n\t\t\t%% Apparently macOS doesn't free the port immediately.\n\t\t\ttimer:sleep(500);\n\t\t_ ->\n\t\t\tok\n\tend,\n\tPort.\n\n-endif.\n\nensure_alpn(TransOpts) ->\n\tSocketOpts = maps:get(socket_opts, TransOpts, []),\n\tTransOpts#{socket_opts => [\n\t\t{alpn_preferred_protocols, [<<\"h2\">>, <<\"http/1.1\">>]}\n\t|SocketOpts]}.\n\nensure_connection_type(TransOpts=#{connection_type := ConnectionType}) ->\n\t{TransOpts, ConnectionType};\nensure_connection_type(TransOpts) ->\n\t{TransOpts#{connection_type => supervisor}, supervisor}.\n\n%% Dynamic buffer was set; accept transport options as-is.\n%% Note that initial 'buffer' size may be lower than dynamic buffer allows.\nensure_dynamic_buffer(TransOpts, #{dynamic_buffer := DynamicBuffer}) ->\n\t{TransOpts, DynamicBuffer};\n%% Dynamic buffer was not set; define default dynamic buffer\n%% only if 'buffer' size was not configured. In that case we\n%% set the 'buffer' size to the lowest value.\nensure_dynamic_buffer(TransOpts=#{socket_opts := SocketOpts}, _) ->\n\tcase proplists:get_value(buffer, SocketOpts, undefined) of\n\t\tundefined ->\n\t\t\t{TransOpts#{socket_opts => [{buffer, 512}|SocketOpts]}, {512, 131072}};\n\t\t_ ->\n\t\t\t{TransOpts, false}\n\tend.\n\n-spec stop_listener(ranch:ref()) -> ok | {error, not_found}.\n\nstop_listener(Ref) ->\n\tranch:stop_listener(Ref).\n\n-spec get_env(ranch:ref(), atom()) -> ok.\n\nget_env(Ref, Name) ->\n\tOpts = ranch:get_protocol_options(Ref),\n\tEnv = maps:get(env, Opts, #{}),\n\tmaps:get(Name, Env).\n\n-spec get_env(ranch:ref(), atom(), any()) -> ok.\n\nget_env(Ref, Name, Default) ->\n\tOpts = ranch:get_protocol_options(Ref),\n\tEnv = maps:get(env, Opts, #{}),\n\tmaps:get(Name, Env, Default).\n\n-spec set_env(ranch:ref(), atom(), any()) -> ok.\n\nset_env(Ref, Name, Value) ->\n\tOpts = ranch:get_protocol_options(Ref),\n\tEnv = maps:get(env, Opts, #{}),\n\tOpts2 = maps:put(env, maps:put(Name, Value, Env), Opts),\n\tok = ranch:set_protocol_options(Ref, Opts2).\n\n%% Internal.\n\n-spec log({log, logger:level(), io:format(), list()}, opts()) -> ok.\n\nlog({log, Level, Format, Args}, Opts) ->\n\tlog(Level, Format, Args, Opts).\n\n-spec log(logger:level(), io:format(), list(), opts()) -> ok.\n\nlog(Level, Format, Args, #{logger := Logger})\n\t\twhen Logger =/= error_logger ->\n\t_ = Logger:Level(Format, Args),\n\tok;\n%% We use error_logger by default. Because error_logger does\n%% not have all the levels we accept we have to do some\n%% mapping to error_logger functions.\nlog(Level, Format, Args, _) ->\n\tFunction = case Level of\n\t\temergency -> error_msg;\n\t\talert -> error_msg;\n\t\tcritical -> error_msg;\n\t\terror -> error_msg;\n\t\twarning -> warning_msg;\n\t\tnotice -> warning_msg;\n\t\tinfo -> info_msg;\n\t\tdebug -> info_msg\n\tend,\n\terror_logger:Function(Format, Args).\n"
  },
  {
    "path": "src/cowboy_app.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_app).\n-behaviour(application).\n\n-export([start/2]).\n-export([stop/1]).\n\n-spec start(_, _) -> {ok, pid()}.\nstart(_, _) ->\n\tcowboy_sup:start_link().\n\n-spec stop(_) -> ok.\nstop(_) ->\n\tok.\n"
  },
  {
    "path": "src/cowboy_bstr.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_bstr).\n\n%% Binary strings.\n-export([capitalize_token/1]).\n-export([to_lower/1]).\n-export([to_upper/1]).\n\n%% Characters.\n-export([char_to_lower/1]).\n-export([char_to_upper/1]).\n\n%% The first letter and all letters after a dash are capitalized.\n%% This is the form seen for header names in the HTTP/1.1 RFC and\n%% others. Note that using this form isn't required, as header names\n%% are case insensitive, and it is only provided for use with eventual\n%% badly implemented clients.\n-spec capitalize_token(B) -> B when B::binary().\ncapitalize_token(B) ->\n\tcapitalize_token(B, true, <<>>).\ncapitalize_token(<<>>, _, Acc) ->\n\tAcc;\ncapitalize_token(<< $-, Rest/bits >>, _, Acc) ->\n\tcapitalize_token(Rest, true, << Acc/binary, $- >>);\ncapitalize_token(<< C, Rest/bits >>, true, Acc) ->\n\tcapitalize_token(Rest, false, << Acc/binary, (char_to_upper(C)) >>);\ncapitalize_token(<< C, Rest/bits >>, false, Acc) ->\n\tcapitalize_token(Rest, false, << Acc/binary, (char_to_lower(C)) >>).\n\n-spec to_lower(B) -> B when B::binary().\nto_lower(B) ->\n\t<< << (char_to_lower(C)) >> || << C >> <= B >>.\n\n-spec to_upper(B) -> B when B::binary().\nto_upper(B) ->\n\t<< << (char_to_upper(C)) >> || << C >> <= B >>.\n\n-spec char_to_lower(char()) -> char().\nchar_to_lower($A) -> $a;\nchar_to_lower($B) -> $b;\nchar_to_lower($C) -> $c;\nchar_to_lower($D) -> $d;\nchar_to_lower($E) -> $e;\nchar_to_lower($F) -> $f;\nchar_to_lower($G) -> $g;\nchar_to_lower($H) -> $h;\nchar_to_lower($I) -> $i;\nchar_to_lower($J) -> $j;\nchar_to_lower($K) -> $k;\nchar_to_lower($L) -> $l;\nchar_to_lower($M) -> $m;\nchar_to_lower($N) -> $n;\nchar_to_lower($O) -> $o;\nchar_to_lower($P) -> $p;\nchar_to_lower($Q) -> $q;\nchar_to_lower($R) -> $r;\nchar_to_lower($S) -> $s;\nchar_to_lower($T) -> $t;\nchar_to_lower($U) -> $u;\nchar_to_lower($V) -> $v;\nchar_to_lower($W) -> $w;\nchar_to_lower($X) -> $x;\nchar_to_lower($Y) -> $y;\nchar_to_lower($Z) -> $z;\nchar_to_lower(Ch) -> Ch.\n\n-spec char_to_upper(char()) -> char().\nchar_to_upper($a) -> $A;\nchar_to_upper($b) -> $B;\nchar_to_upper($c) -> $C;\nchar_to_upper($d) -> $D;\nchar_to_upper($e) -> $E;\nchar_to_upper($f) -> $F;\nchar_to_upper($g) -> $G;\nchar_to_upper($h) -> $H;\nchar_to_upper($i) -> $I;\nchar_to_upper($j) -> $J;\nchar_to_upper($k) -> $K;\nchar_to_upper($l) -> $L;\nchar_to_upper($m) -> $M;\nchar_to_upper($n) -> $N;\nchar_to_upper($o) -> $O;\nchar_to_upper($p) -> $P;\nchar_to_upper($q) -> $Q;\nchar_to_upper($r) -> $R;\nchar_to_upper($s) -> $S;\nchar_to_upper($t) -> $T;\nchar_to_upper($u) -> $U;\nchar_to_upper($v) -> $V;\nchar_to_upper($w) -> $W;\nchar_to_upper($x) -> $X;\nchar_to_upper($y) -> $Y;\nchar_to_upper($z) -> $Z;\nchar_to_upper(Ch) -> Ch.\n\n%% Tests.\n\n-ifdef(TEST).\ncapitalize_token_test_() ->\n\tTests = [\n\t\t{<<\"heLLo-woRld\">>, <<\"Hello-World\">>},\n\t\t{<<\"Sec-Websocket-Version\">>, <<\"Sec-Websocket-Version\">>},\n\t\t{<<\"Sec-WebSocket-Version\">>, <<\"Sec-Websocket-Version\">>},\n\t\t{<<\"sec-websocket-version\">>, <<\"Sec-Websocket-Version\">>},\n\t\t{<<\"SEC-WEBSOCKET-VERSION\">>, <<\"Sec-Websocket-Version\">>},\n\t\t{<<\"Sec-WebSocket--Version\">>, <<\"Sec-Websocket--Version\">>},\n\t\t{<<\"Sec-WebSocket---Version\">>, <<\"Sec-Websocket---Version\">>}\n\t],\n\t[{H, fun() -> R = capitalize_token(H) end} || {H, R} <- Tests].\n-endif.\n"
  },
  {
    "path": "src/cowboy_children.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_children).\n\n-export([init/0]).\n-export([up/4]).\n-export([down/2]).\n-export([shutdown/2]).\n-export([shutdown_timeout/3]).\n-export([terminate/1]).\n-export([handle_supervisor_call/4]).\n\n-record(child, {\n\tpid :: pid(),\n\tstreamid :: cowboy_stream:streamid() | undefined,\n\tshutdown :: timeout(),\n\ttimer = undefined :: undefined | reference()\n}).\n\n-type children() :: [#child{}].\n-export_type([children/0]).\n\n-spec init() -> [].\ninit() ->\n\t[].\n\n-spec up(Children, pid(), cowboy_stream:streamid(), timeout())\n\t-> Children when Children::children().\nup(Children, Pid, StreamID, Shutdown) ->\n\t[#child{\n\t\tpid=Pid,\n\t\tstreamid=StreamID,\n\t\tshutdown=Shutdown\n\t}|Children].\n\n-spec down(Children, pid())\n\t-> {ok, cowboy_stream:streamid() | undefined, Children} | error\n\twhen Children::children().\ndown(Children0, Pid) ->\n\tcase lists:keytake(Pid, #child.pid, Children0) of\n\t\t{value, #child{streamid=StreamID, timer=Ref}, Children} ->\n\t\t\t_ = case Ref of\n\t\t\t\tundefined -> ok;\n\t\t\t\t_ -> erlang:cancel_timer(Ref, [{async, true}, {info, false}])\n\t\t\tend,\n\t\t\t{ok, StreamID, Children};\n\t\tfalse ->\n\t\t\terror\n\tend.\n\n%% We ask the processes to shutdown first. This gives\n%% a chance to processes that are trapping exits to\n%% shut down gracefully. Others will exit immediately.\n%%\n%% @todo We currently fire one timer per process being\n%% shut down. This is probably not the most efficient.\n%% A more efficient solution could be to maintain a\n%% single timer and decrease the shutdown time of all\n%% processes when it fires. This is however much more\n%% complex, and there aren't that many processes that\n%% will need to be shutdown through this function, so\n%% this is left for later.\n-spec shutdown(Children, cowboy_stream:streamid())\n\t-> Children when Children::children().\nshutdown(Children0, StreamID) ->\n\t[\n\t\tcase Child of\n\t\t\t#child{pid=Pid, streamid=StreamID, shutdown=Shutdown} ->\n\t\t\t\texit(Pid, shutdown),\n\t\t\t\tRef = erlang:start_timer(Shutdown, self(), {shutdown, Pid}),\n\t\t\t\tChild#child{streamid=undefined, timer=Ref};\n\t\t\t_ ->\n\t\t\t\tChild\n\t\tend\n\t|| Child <- Children0].\n\n-spec shutdown_timeout(children(), reference(), pid()) -> ok.\nshutdown_timeout(Children, Ref, Pid) ->\n\tcase lists:keyfind(Pid, #child.pid, Children) of\n\t\t#child{timer=Ref} ->\n\t\t\texit(Pid, kill),\n\t\t\tok;\n\t\t_ ->\n\t\t\tok\n\tend.\n\n-spec terminate(children()) -> ok.\nterminate(Children) ->\n\t%% For each child, either ask for it to shut down,\n\t%% or cancel its shutdown timer if it already is.\n\t%%\n\t%% We do not need to flush stray timeout messages out because\n\t%% we are either terminating or switching protocols,\n\t%% and in the latter case we flush all messages.\n\t_ = [case TRef of\n\t\tundefined -> exit(Pid, shutdown);\n\t\t_ -> erlang:cancel_timer(TRef, [{async, true}, {info, false}])\n\tend || #child{pid=Pid, timer=TRef} <- Children],\n\tbefore_terminate_loop(Children).\n\nbefore_terminate_loop([]) ->\n\tok;\nbefore_terminate_loop(Children) ->\n\t%% Find the longest shutdown time.\n\tTime = longest_shutdown_time(Children, 0),\n\t%% We delay the creation of the timer if one of the\n\t%% processes has an infinity shutdown value.\n\tTRef = case Time of\n\t\tinfinity -> undefined;\n\t\t_ -> erlang:start_timer(Time, self(), terminate)\n\tend,\n\t%% Loop until that time or until all children are dead.\n\tterminate_loop(Children, TRef).\n\nterminate_loop([], TRef) ->\n\t%% Don't forget to cancel the timer, if any!\n\tcase TRef of\n\t\tundefined ->\n\t\t\tok;\n\t\t_ ->\n\t\t\t_ = erlang:cancel_timer(TRef, [{async, true}, {info, false}]),\n\t\t\tok\n\tend;\nterminate_loop(Children, TRef) ->\n\treceive\n\t\t{'EXIT', Pid, _} when TRef =:= undefined ->\n\t\t\t{value, #child{shutdown=Shutdown}, Children1}\n\t\t\t\t= lists:keytake(Pid, #child.pid, Children),\n\t\t\t%% We delayed the creation of the timer. If a process with\n\t\t\t%% infinity shutdown just ended, we might have to start that timer.\n\t\t\tcase Shutdown of\n\t\t\t\tinfinity -> before_terminate_loop(Children1);\n\t\t\t\t_ -> terminate_loop(Children1, TRef)\n\t\t\tend;\n\t\t{'EXIT', Pid, _} ->\n\t\t\tterminate_loop(lists:keydelete(Pid, #child.pid, Children), TRef);\n\t\t{timeout, TRef, terminate} ->\n\t\t\t%% Brutally kill any remaining children.\n\t\t\t_ = [exit(Pid, kill) || #child{pid=Pid} <- Children],\n\t\t\tok\n\tend.\n\nlongest_shutdown_time([], Time) ->\n\tTime;\nlongest_shutdown_time([#child{shutdown=ChildTime}|Tail], Time) when ChildTime > Time ->\n\tlongest_shutdown_time(Tail, ChildTime);\nlongest_shutdown_time([_|Tail], Time) ->\n\tlongest_shutdown_time(Tail, Time).\n\n-spec handle_supervisor_call(any(), {pid(), any()}, children(), module()) -> ok.\nhandle_supervisor_call(which_children, {From, Tag}, Children, Module) ->\n\tFrom ! {Tag, which_children(Children, Module)},\n\tok;\nhandle_supervisor_call(count_children, {From, Tag}, Children, _) ->\n\tFrom ! {Tag, count_children(Children)},\n\tok;\n%% We disable start_child since only incoming requests\n%% end up creating a new process.\nhandle_supervisor_call({start_child, _}, {From, Tag}, _, _) ->\n\tFrom ! {Tag, {error, start_child_disabled}},\n\tok;\n%% All other calls refer to children. We act in a similar way\n%% to a simple_one_for_one so we never find those.\nhandle_supervisor_call(_, {From, Tag}, _, _) ->\n\tFrom ! {Tag, {error, not_found}},\n\tok.\n\n-spec which_children(children(), module()) -> [{module(), pid(), worker, [module()]}].\nwhich_children(Children, Module) ->\n\t[{Module, Pid, worker, [Module]} || #child{pid=Pid} <- Children].\n\n-spec count_children(children()) -> [{atom(), non_neg_integer()}].\ncount_children(Children) ->\n\tCount = length(Children),\n\t[\n\t\t{specs, 1},\n\t\t{active, Count},\n\t\t{supervisors, 0},\n\t\t{workers, Count}\n\t].\n"
  },
  {
    "path": "src/cowboy_clear.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_clear).\n-behavior(ranch_protocol).\n\n-export([start_link/3]).\n-export([start_link/4]).\n-export([connection_process/4]).\n\n%% Ranch 1.\n-spec start_link(ranch:ref(), inet:socket(), module(), cowboy:opts()) -> {ok, pid()}.\nstart_link(Ref, _Socket, Transport, Opts) ->\n\tstart_link(Ref, Transport, Opts).\n\n%% Ranch 2.\n-spec start_link(ranch:ref(), module(), cowboy:opts()) -> {ok, pid()}.\nstart_link(Ref, Transport, Opts) ->\n\tPid = proc_lib:spawn_link(?MODULE, connection_process,\n\t\t[self(), Ref, Transport, Opts]),\n\t{ok, Pid}.\n\n-spec connection_process(pid(), ranch:ref(), module(), cowboy:opts()) -> ok.\nconnection_process(Parent, Ref, Transport, Opts) ->\n\tProxyInfo = get_proxy_info(Ref, Opts),\n\t{ok, Socket} = ranch:handshake(Ref),\n\t%% Use cowboy_http2 directly only when 'http' is missing.\n\tProtocol = case maps:get(protocols, Opts, [http2, http]) of\n\t\t[http2] -> cowboy_http2;\n\t\t[_|_] -> cowboy_http\n\tend,\n\tinit(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol).\n\ninit(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol) ->\n\t_ = case maps:get(connection_type, Opts, supervisor) of\n\t\tworker -> ok;\n\t\tsupervisor -> process_flag(trap_exit, true)\n\tend,\n\tProtocol:init(Parent, Ref, Socket, Transport, ProxyInfo, Opts).\n\nget_proxy_info(Ref, #{proxy_header := true}) ->\n\tcase ranch:recv_proxy_header(Ref, 1000) of\n\t\t{ok, ProxyInfo} -> ProxyInfo;\n\t\t{error, closed} -> exit({shutdown, closed})\n\tend;\nget_proxy_info(_, _) ->\n\tundefined.\n"
  },
  {
    "path": "src/cowboy_clock.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% While a gen_server process runs in the background to update\n%% the cache of formatted dates every second, all API calls are\n%% local and directly read from the ETS cache table, providing\n%% fast time and date computations.\n-module(cowboy_clock).\n-behaviour(gen_server).\n\n%% API.\n-export([start_link/0]).\n-export([stop/0]).\n-export([rfc1123/0]).\n-export([rfc1123/1]).\n\n%% gen_server.\n-export([init/1]).\n-export([handle_call/3]).\n-export([handle_cast/2]).\n-export([handle_info/2]).\n-export([terminate/2]).\n-export([code_change/3]).\n\n-record(state, {\n\tuniversaltime = undefined :: undefined | calendar:datetime(),\n\trfc1123 = <<>> :: binary(),\n\ttref = undefined :: undefined | reference()\n}).\n\n%% API.\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tgen_server:start_link({local, ?MODULE}, ?MODULE, [], []).\n\n-spec stop() -> stopped.\nstop() ->\n\tgen_server:call(?MODULE, stop).\n\n%% When the ets table doesn't exist, either because of a bug\n%% or because Cowboy is being restarted, we perform in a\n%% slightly degraded state and build a new timestamp for\n%% every request.\n-spec rfc1123() -> binary().\nrfc1123() ->\n\ttry\n\t\tets:lookup_element(?MODULE, rfc1123, 2)\n\tcatch error:badarg ->\n\t\trfc1123(erlang:universaltime())\n\tend.\n\n-spec rfc1123(calendar:datetime()) -> binary().\nrfc1123(DateTime) ->\n\tupdate_rfc1123(<<>>, undefined, DateTime).\n\n%% gen_server.\n\n-spec init([]) -> {ok, #state{}}.\ninit([]) ->\n\t?MODULE = ets:new(?MODULE, [set, protected,\n\t\tnamed_table, {read_concurrency, true}]),\n\tT = erlang:universaltime(),\n\tB = update_rfc1123(<<>>, undefined, T),\n\tTRef = erlang:send_after(1000, self(), update),\n\tets:insert(?MODULE, {rfc1123, B}),\n\t{ok, #state{universaltime=T, rfc1123=B, tref=TRef}}.\n\n-type from() :: {pid(), term()}.\n-spec handle_call\n\t(stop, from(), State) -> {stop, normal, stopped, State}\n\twhen State::#state{}.\nhandle_call(stop, _From, State) ->\n\t{stop, normal, stopped, State};\nhandle_call(_Request, _From, State) ->\n\t{reply, ignored, State}.\n\n-spec handle_cast(_, State) -> {noreply, State} when State::#state{}.\nhandle_cast(_Msg, State) ->\n\t{noreply, State}.\n\n-spec handle_info(any(), State) -> {noreply, State} when State::#state{}.\nhandle_info(update, #state{universaltime=Prev, rfc1123=B1, tref=TRef0}) ->\n\t%% Cancel the timer in case an external process sent an update message.\n\t_ = erlang:cancel_timer(TRef0, [{async, true}, {info, false}]),\n\tT = erlang:universaltime(),\n\tB2 = update_rfc1123(B1, Prev, T),\n\tets:insert(?MODULE, {rfc1123, B2}),\n\tTRef = erlang:send_after(1000, self(), update),\n\t{noreply, #state{universaltime=T, rfc1123=B2, tref=TRef}};\nhandle_info(_Info, State) ->\n\t{noreply, State}.\n\n-spec terminate(_, _) -> ok.\nterminate(_Reason, _State) ->\n\tok.\n\n-spec code_change(_, State, _) -> {ok, State} when State::#state{}.\ncode_change(_OldVsn, State, _Extra) ->\n\t{ok, State}.\n\n%% Internal.\n\n-spec update_rfc1123(binary(), undefined | calendar:datetime(),\n\tcalendar:datetime()) -> binary().\nupdate_rfc1123(Bin, Now, Now) ->\n\tBin;\nupdate_rfc1123(<< Keep:23/binary, _/bits >>,\n\t\t{Date, {H, M, _}}, {Date, {H, M, S}}) ->\n\t<< Keep/binary, (pad_int(S))/binary, \" GMT\" >>;\nupdate_rfc1123(<< Keep:20/binary, _/bits >>,\n\t\t{Date, {H, _, _}}, {Date, {H, M, S}}) ->\n\t<< Keep/binary, (pad_int(M))/binary, $:, (pad_int(S))/binary, \" GMT\" >>;\nupdate_rfc1123(<< Keep:17/binary, _/bits >>, {Date, _}, {Date, {H, M, S}}) ->\n\t<< Keep/binary, (pad_int(H))/binary, $:, (pad_int(M))/binary,\n\t\t$:, (pad_int(S))/binary, \" GMT\" >>;\nupdate_rfc1123(<< _:7/binary, Keep:10/binary, _/bits >>,\n\t\t{{Y, Mo, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->\n\tWday = calendar:day_of_the_week(Date),\n\t<< (weekday(Wday))/binary, \", \", (pad_int(D))/binary, Keep/binary,\n\t\t(pad_int(H))/binary, $:, (pad_int(M))/binary,\n\t\t$:, (pad_int(S))/binary, \" GMT\" >>;\nupdate_rfc1123(<< _:11/binary, Keep:6/binary, _/bits >>,\n\t\t{{Y, _, _}, _}, {Date = {Y, Mo, D}, {H, M, S}}) ->\n\tWday = calendar:day_of_the_week(Date),\n\t<< (weekday(Wday))/binary, \", \", (pad_int(D))/binary, \" \",\n\t\t(month(Mo))/binary, Keep/binary,\n\t\t(pad_int(H))/binary, $:, (pad_int(M))/binary,\n\t\t$:, (pad_int(S))/binary, \" GMT\" >>;\nupdate_rfc1123(_, _, {Date = {Y, Mo, D}, {H, M, S}}) ->\n\tWday = calendar:day_of_the_week(Date),\n\t<< (weekday(Wday))/binary, \", \", (pad_int(D))/binary, \" \",\n\t\t(month(Mo))/binary, \" \", (integer_to_binary(Y))/binary,\n\t\t\" \", (pad_int(H))/binary, $:, (pad_int(M))/binary,\n\t\t$:, (pad_int(S))/binary, \" GMT\" >>.\n\n%% Following suggestion by MononcQc on #erlounge.\n-spec pad_int(0..59) -> binary().\npad_int(X) when X < 10 ->\n\t<< $0, ($0 + X) >>;\npad_int(X) ->\n\tinteger_to_binary(X).\n\n-spec weekday(1..7) -> <<_:24>>.\nweekday(1) -> <<\"Mon\">>;\nweekday(2) -> <<\"Tue\">>;\nweekday(3) -> <<\"Wed\">>;\nweekday(4) -> <<\"Thu\">>;\nweekday(5) -> <<\"Fri\">>;\nweekday(6) -> <<\"Sat\">>;\nweekday(7) -> <<\"Sun\">>.\n\n-spec month(1..12) -> <<_:24>>.\nmonth( 1) -> <<\"Jan\">>;\nmonth( 2) -> <<\"Feb\">>;\nmonth( 3) -> <<\"Mar\">>;\nmonth( 4) -> <<\"Apr\">>;\nmonth( 5) -> <<\"May\">>;\nmonth( 6) -> <<\"Jun\">>;\nmonth( 7) -> <<\"Jul\">>;\nmonth( 8) -> <<\"Aug\">>;\nmonth( 9) -> <<\"Sep\">>;\nmonth(10) -> <<\"Oct\">>;\nmonth(11) -> <<\"Nov\">>;\nmonth(12) -> <<\"Dec\">>.\n\n%% Tests.\n\n-ifdef(TEST).\nupdate_rfc1123_test_() ->\n\tTests = [\n\t\t{<<\"Sat, 14 May 2011 14:25:33 GMT\">>, undefined,\n\t\t\t{{2011, 5, 14}, {14, 25, 33}}, <<>>},\n\t\t{<<\"Sat, 14 May 2011 14:25:33 GMT\">>, {{2011, 5, 14}, {14, 25, 33}},\n\t\t\t{{2011, 5, 14}, {14, 25, 33}}, <<\"Sat, 14 May 2011 14:25:33 GMT\">>},\n\t\t{<<\"Sat, 14 May 2011 14:25:34 GMT\">>, {{2011, 5, 14}, {14, 25, 33}},\n\t\t\t{{2011, 5, 14}, {14, 25, 34}}, <<\"Sat, 14 May 2011 14:25:33 GMT\">>},\n\t\t{<<\"Sat, 14 May 2011 14:26:00 GMT\">>, {{2011, 5, 14}, {14, 25, 59}},\n\t\t\t{{2011, 5, 14}, {14, 26,  0}}, <<\"Sat, 14 May 2011 14:25:59 GMT\">>},\n\t\t{<<\"Sat, 14 May 2011 15:00:00 GMT\">>, {{2011, 5, 14}, {14, 59, 59}},\n\t\t\t{{2011, 5, 14}, {15,  0,  0}}, <<\"Sat, 14 May 2011 14:59:59 GMT\">>},\n\t\t{<<\"Sun, 15 May 2011 00:00:00 GMT\">>, {{2011, 5, 14}, {23, 59, 59}},\n\t\t\t{{2011, 5, 15}, { 0,  0,  0}}, <<\"Sat, 14 May 2011 23:59:59 GMT\">>},\n\t\t{<<\"Wed, 01 Jun 2011 00:00:00 GMT\">>, {{2011, 5, 31}, {23, 59, 59}},\n\t\t\t{{2011, 6,  1}, { 0,  0,  0}}, <<\"Tue, 31 May 2011 23:59:59 GMT\">>},\n\t\t{<<\"Sun, 01 Jan 2012 00:00:00 GMT\">>, {{2011, 5, 31}, {23, 59, 59}},\n\t\t\t{{2012, 1,  1}, { 0,  0,  0}}, <<\"Sat, 31 Dec 2011 23:59:59 GMT\">>}\n\t],\n\t[{R, fun() -> R = update_rfc1123(B, P, N) end} || {R, P, N, B} <- Tests].\n\npad_int_test_() ->\n\tTests = [\n\t\t{ 0, <<\"00\">>}, { 1, <<\"01\">>}, { 2, <<\"02\">>}, { 3, <<\"03\">>},\n\t\t{ 4, <<\"04\">>}, { 5, <<\"05\">>}, { 6, <<\"06\">>}, { 7, <<\"07\">>},\n\t\t{ 8, <<\"08\">>}, { 9, <<\"09\">>}, {10, <<\"10\">>}, {11, <<\"11\">>},\n\t\t{12, <<\"12\">>}, {13, <<\"13\">>}, {14, <<\"14\">>}, {15, <<\"15\">>},\n\t\t{16, <<\"16\">>}, {17, <<\"17\">>}, {18, <<\"18\">>}, {19, <<\"19\">>},\n\t\t{20, <<\"20\">>}, {21, <<\"21\">>}, {22, <<\"22\">>}, {23, <<\"23\">>},\n\t\t{24, <<\"24\">>}, {25, <<\"25\">>}, {26, <<\"26\">>}, {27, <<\"27\">>},\n\t\t{28, <<\"28\">>}, {29, <<\"29\">>}, {30, <<\"30\">>}, {31, <<\"31\">>},\n\t\t{32, <<\"32\">>}, {33, <<\"33\">>}, {34, <<\"34\">>}, {35, <<\"35\">>},\n\t\t{36, <<\"36\">>}, {37, <<\"37\">>}, {38, <<\"38\">>}, {39, <<\"39\">>},\n\t\t{40, <<\"40\">>}, {41, <<\"41\">>}, {42, <<\"42\">>}, {43, <<\"43\">>},\n\t\t{44, <<\"44\">>}, {45, <<\"45\">>}, {46, <<\"46\">>}, {47, <<\"47\">>},\n\t\t{48, <<\"48\">>}, {49, <<\"49\">>}, {50, <<\"50\">>}, {51, <<\"51\">>},\n\t\t{52, <<\"52\">>}, {53, <<\"53\">>}, {54, <<\"54\">>}, {55, <<\"55\">>},\n\t\t{56, <<\"56\">>}, {57, <<\"57\">>}, {58, <<\"58\">>}, {59, <<\"59\">>}\n\t],\n\t[{I, fun() -> O = pad_int(I) end} || {I, O} <- Tests].\n-endif.\n"
  },
  {
    "path": "src/cowboy_compress_h.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_compress_h).\n-behavior(cowboy_stream).\n\n-export([init/3]).\n-export([data/4]).\n-export([info/3]).\n-export([terminate/3]).\n-export([early_error/5]).\n\n-record(state, {\n\tnext :: any(),\n\tthreshold :: non_neg_integer() | undefined,\n\tcompress = undefined :: undefined | gzip,\n\tdeflate = undefined :: undefined | zlib:zstream(),\n\tdeflate_flush = sync :: none | sync\n}).\n\n-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())\n\t-> {cowboy_stream:commands(), #state{}}.\ninit(StreamID, Req, Opts) ->\n\tState0 = check_req(Req),\n\tCompressThreshold = maps:get(compress_threshold, Opts, 300),\n\tDeflateFlush = buffering_to_zflush(maps:get(compress_buffering, Opts, false)),\n\t{Commands0, Next} = cowboy_stream:init(StreamID, Req, Opts),\n\tfold(Commands0, State0#state{next=Next,\n\t\tthreshold=CompressThreshold,\n\t\tdeflate_flush=DeflateFlush}).\n\n-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)\n\t-> {cowboy_stream:commands(), State} when State::#state{}.\ndata(StreamID, IsFin, Data, State0=#state{next=Next0}) ->\n\t{Commands0, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),\n\tfold(Commands0, State0#state{next=Next}).\n\n-spec info(cowboy_stream:streamid(), any(), State)\n\t-> {cowboy_stream:commands(), State} when State::#state{}.\ninfo(StreamID, Info, State0=#state{next=Next0}) ->\n\t{Commands0, Next} = cowboy_stream:info(StreamID, Info, Next0),\n\tfold(Commands0, State0#state{next=Next}).\n\n-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> any().\nterminate(StreamID, Reason, #state{next=Next, deflate=Z}) ->\n\t%% Clean the zlib:stream() in case something went wrong.\n\t%% In the normal scenario the stream is already closed.\n\tcase Z of\n\t\tundefined -> ok;\n\t\t_ -> zlib:close(Z)\n\tend,\n\tcowboy_stream:terminate(StreamID, Reason, Next).\n\n-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),\n\tcowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp\n\twhen Resp::cowboy_stream:resp_command().\nearly_error(StreamID, Reason, PartialReq, Resp, Opts) ->\n\tcowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).\n\n%% Internal.\n\n%% Check if the client supports decoding of gzip responses.\n%%\n%% A malformed accept-encoding header is ignored (no compression).\ncheck_req(Req) ->\n\ttry cowboy_req:parse_header(<<\"accept-encoding\">>, Req) of\n\t\t%% Client doesn't support any compression algorithm.\n\t\tundefined ->\n\t\t\t#state{compress=undefined};\n\t\tEncodings ->\n\t\t\t%% We only support gzip so look for it specifically.\n\t\t\t%% @todo A recipient SHOULD consider \"x-gzip\" to be\n\t\t\t%% equivalent to \"gzip\". (RFC7230 4.2.3)\n\t\t\tcase [E || E={<<\"gzip\">>, Q} <- Encodings, Q =/= 0] of\n\t\t\t\t[] ->\n\t\t\t\t\t#state{compress=undefined};\n\t\t\t\t_ ->\n\t\t\t\t\t#state{compress=gzip}\n\t\t\tend\n\tcatch\n\t\t_:_ ->\n\t\t\t#state{compress=undefined}\n\tend.\n\n%% Do not compress responses that contain the content-encoding header.\ncheck_resp_headers(#{<<\"content-encoding\">> := _}, State) ->\n\tState#state{compress=undefined};\n%% Do not compress responses that contain the etag header.\ncheck_resp_headers(#{<<\"etag\">> := _}, State) ->\n\tState#state{compress=undefined};\ncheck_resp_headers(_, State) ->\n\tState.\n\nfold(Commands, State=#state{compress=undefined}) ->\n\tfold_vary_only(Commands, State, []);\nfold(Commands, State) ->\n\tfold(Commands, State, []).\n\nfold([], State, Acc) ->\n\t{lists:reverse(Acc), State};\n%% We do not compress full sendfile bodies.\nfold([Response={response, _, _, {sendfile, _, _, _}}|Tail], State, Acc) ->\n\tfold(Tail, State, [vary_response(Response)|Acc]);\n%% We compress full responses directly, unless they are lower than\n%% the configured threshold or we find we are not able to by looking at the headers.\nfold([Response0={response, _, Headers, Body}|Tail],\n\t\tState0=#state{threshold=CompressThreshold}, Acc) ->\n\tcase check_resp_headers(Headers, State0) of\n\t\tState=#state{compress=undefined} ->\n\t\t\tfold(Tail, State, [vary_response(Response0)|Acc]);\n\t\tState1 ->\n\t\t\tBodyLength = iolist_size(Body),\n\t\t\tif\n\t\t\t\tBodyLength =< CompressThreshold ->\n\t\t\t\t\tfold(Tail, State1, [vary_response(Response0)|Acc]);\n\t\t\t\ttrue ->\n\t\t\t\t\t{Response, State} = gzip_response(Response0, State1),\n\t\t\t\t\tfold(Tail, State, [vary_response(Response)|Acc])\n\t\t\tend\n\tend;\n%% Check headers and initiate compression...\nfold([Response0={headers, _, Headers}|Tail], State0, Acc) ->\n\tcase check_resp_headers(Headers, State0) of\n\t\tState=#state{compress=undefined} ->\n\t\t\tfold(Tail, State, [vary_headers(Response0)|Acc]);\n\t\tState1 ->\n\t\t\t{Response, State} = gzip_headers(Response0, State1),\n\t\t\tfold(Tail, State, [vary_headers(Response)|Acc])\n\tend;\n%% then compress each data commands individually.\nfold([Data0={data, _, _}|Tail], State0=#state{compress=gzip}, Acc) ->\n\t{Data, State} = gzip_data(Data0, State0),\n\tfold(Tail, State, [Data|Acc]);\n%% When trailers are sent we need to end the compression.\n%% This results in an extra data command being sent.\nfold([Trailers={trailers, _}|Tail], State0=#state{compress=gzip}, Acc) ->\n\t{{data, fin, Data}, State} = gzip_data({data, fin, <<>>}, State0),\n\tfold(Tail, State, [Trailers, {data, nofin, Data}|Acc]);\n%% All the options from this handler can be updated for the current stream.\n%% The set_options command must be propagated as-is regardless.\nfold([SetOptions={set_options, Opts}|Tail], State=#state{\n\t\tthreshold=CompressThreshold0, deflate_flush=DeflateFlush0}, Acc) ->\n\tCompressThreshold = maps:get(compress_threshold, Opts, CompressThreshold0),\n\tDeflateFlush = case Opts of\n\t\t#{compress_buffering := CompressBuffering} ->\n\t\t\tbuffering_to_zflush(CompressBuffering);\n\t\t_ ->\n\t\t\tDeflateFlush0\n\tend,\n\tfold(Tail, State#state{threshold=CompressThreshold, deflate_flush=DeflateFlush},\n\t\t[SetOptions|Acc]);\n%% Otherwise, we have an unrelated command or compression is disabled.\nfold([Command|Tail], State, Acc) ->\n\tfold(Tail, State, [Command|Acc]).\n\nfold_vary_only([], State, Acc) ->\n\t{lists:reverse(Acc), State};\nfold_vary_only([Response={response, _, _, _}|Tail], State, Acc) ->\n\tfold_vary_only(Tail, State, [vary_response(Response)|Acc]);\nfold_vary_only([Response={headers, _, _}|Tail], State, Acc) ->\n\tfold_vary_only(Tail, State, [vary_headers(Response)|Acc]);\nfold_vary_only([Command|Tail], State, Acc) ->\n\tfold_vary_only(Tail, State, [Command|Acc]).\n\nbuffering_to_zflush(true) -> none;\nbuffering_to_zflush(false) -> sync.\n\ngzip_response({response, Status, Headers, Body}, State) ->\n\t%% We can't call zlib:gzip/1 because it does an\n\t%% iolist_to_binary(GzBody) at the end to return\n\t%% a binary(). Therefore the code here is largely\n\t%% a duplicate of the code of that function.\n\tZ = zlib:open(),\n\tGzBody = try\n\t\t%% 31 = 16+?MAX_WBITS from zlib.erl\n\t\t%% @todo It might be good to allow them to be configured?\n\t\tzlib:deflateInit(Z, default, deflated, 31, 8, default),\n\t\tGz = zlib:deflate(Z, Body, finish),\n\t\tzlib:deflateEnd(Z),\n\t\tGz\n\tafter\n\t\tzlib:close(Z)\n\tend,\n\t{{response, Status, Headers#{\n\t\t<<\"content-length\">> => integer_to_binary(iolist_size(GzBody)),\n\t\t<<\"content-encoding\">> => <<\"gzip\">>\n\t}, GzBody}, State}.\n\ngzip_headers({headers, Status, Headers0}, State) ->\n\tZ = zlib:open(),\n\t%% We use the same arguments as when compressing the body fully.\n\t%% @todo It might be good to allow them to be configured?\n\tzlib:deflateInit(Z, default, deflated, 31, 8, default),\n\tHeaders = maps:remove(<<\"content-length\">>, Headers0),\n\t{{headers, Status, Headers#{\n\t\t<<\"content-encoding\">> => <<\"gzip\">>\n\t}}, State#state{deflate=Z}}.\n\nvary_response({response, Status, Headers, Body}) ->\n\t{response, Status, vary(Headers), Body}.\n\nvary_headers({headers, Status, Headers}) ->\n\t{headers, Status, vary(Headers)}.\n\n%% We must add content-encoding to vary if it's not already there.\nvary(Headers=#{<<\"vary\">> := Vary}) ->\n\ttry cow_http_hd:parse_vary(iolist_to_binary(Vary)) of\n\t\t'*' -> Headers;\n\t\tList ->\n\t\t\tcase lists:member(<<\"accept-encoding\">>, List) of\n\t\t\t\ttrue -> Headers;\n\t\t\t\tfalse -> Headers#{<<\"vary\">> => [Vary, <<\", accept-encoding\">>]}\n\t\t\tend\n\tcatch _:_ ->\n\t\t%% The vary header is invalid. Probably empty. We replace it with ours.\n\t\tHeaders#{<<\"vary\">> => <<\"accept-encoding\">>}\n\tend;\nvary(Headers) ->\n\tHeaders#{<<\"vary\">> => <<\"accept-encoding\">>}.\n\n%% It is not possible to combine zlib and the sendfile\n%% syscall as far as I can tell, because the zlib format\n%% includes a checksum at the end of the stream. We have\n%% to read the file in memory, making this not suitable for\n%% large files.\ngzip_data({data, nofin, Sendfile={sendfile, _, _, _}},\n\t\tState=#state{deflate=Z, deflate_flush=Flush}) ->\n\t{ok, Data0} = read_file(Sendfile),\n\tData = zlib:deflate(Z, Data0, Flush),\n\t{{data, nofin, Data}, State};\ngzip_data({data, fin, Sendfile={sendfile, _, _, _}}, State=#state{deflate=Z}) ->\n\t{ok, Data0} = read_file(Sendfile),\n\tData = zlib:deflate(Z, Data0, finish),\n\tzlib:deflateEnd(Z),\n\tzlib:close(Z),\n\t{{data, fin, Data}, State#state{deflate=undefined}};\ngzip_data({data, nofin, Data0}, State=#state{deflate=Z, deflate_flush=Flush}) ->\n\tData = zlib:deflate(Z, Data0, Flush),\n\t{{data, nofin, Data}, State};\ngzip_data({data, fin, Data0}, State=#state{deflate=Z}) ->\n\tData = zlib:deflate(Z, Data0, finish),\n\tzlib:deflateEnd(Z),\n\tzlib:close(Z),\n\t{{data, fin, Data}, State#state{deflate=undefined}}.\n\nread_file({sendfile, Offset, Bytes, Path}) ->\n\t{ok, IoDevice} = file:open(Path, [read, raw, binary]),\n\ttry\n\t\t_ = case Offset of\n\t\t\t0 -> ok;\n\t\t\t_ -> file:position(IoDevice, {bof, Offset})\n\t\tend,\n\t\tfile:read(IoDevice, Bytes)\n\tafter\n\t\tfile:close(IoDevice)\n\tend.\n"
  },
  {
    "path": "src/cowboy_constraints.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_constraints).\n\n-export([validate/2]).\n-export([reverse/2]).\n-export([format_error/1]).\n\n-type constraint() :: int | nonempty | fun().\n-export_type([constraint/0]).\n\n-type reason() :: {constraint(), any(), any()}.\n-export_type([reason/0]).\n\n-spec validate(binary(), constraint() | [constraint()])\n\t-> {ok, any()} | {error, reason()}.\nvalidate(Value, Constraints) when is_list(Constraints) ->\n\tapply_list(forward, Value, Constraints);\nvalidate(Value, Constraint) ->\n\tapply_list(forward, Value, [Constraint]).\n\n-spec reverse(any(), constraint() | [constraint()])\n\t-> {ok, binary()} | {error, reason()}.\nreverse(Value, Constraints) when is_list(Constraints) ->\n\tapply_list(reverse, Value, Constraints);\nreverse(Value, Constraint) ->\n\tapply_list(reverse, Value, [Constraint]).\n\n-spec format_error(reason()) -> iodata().\nformat_error({Constraint, Reason, Value}) ->\n\tapply_constraint(format_error, {Reason, Value}, Constraint).\n\napply_list(_, Value, []) ->\n\t{ok, Value};\napply_list(Type, Value0, [Constraint|Tail]) ->\n\tcase apply_constraint(Type, Value0, Constraint) of\n\t\t{ok, Value} ->\n\t\t\tapply_list(Type, Value, Tail);\n\t\t{error, Reason} ->\n\t\t\t{error, {Constraint, Reason, Value0}}\n\tend.\n\n%% @todo {int, From, To}, etc.\napply_constraint(Type, Value, int) ->\n\tint(Type, Value);\napply_constraint(Type, Value, nonempty) ->\n\tnonempty(Type, Value);\napply_constraint(Type, Value, F) when is_function(F) ->\n\tF(Type, Value).\n\n%% Constraint functions.\n\nint(forward, Value) ->\n\ttry\n\t\t{ok, binary_to_integer(Value)}\n\tcatch _:_ ->\n\t\t{error, not_an_integer}\n\tend;\nint(reverse, Value) ->\n\ttry\n\t\t{ok, integer_to_binary(Value)}\n\tcatch _:_ ->\n\t\t{error, not_an_integer}\n\tend;\nint(format_error, {not_an_integer, Value}) ->\n\tio_lib:format(\"The value ~p is not an integer.\", [Value]).\n\nnonempty(Type, <<>>) when Type =/= format_error ->\n\t{error, empty};\nnonempty(Type, Value) when Type =/= format_error, is_binary(Value) ->\n\t{ok, Value};\nnonempty(format_error, {empty, Value}) ->\n\tio_lib:format(\"The value ~p is empty.\", [Value]).\n\n-ifdef(TEST).\n\nvalidate_test() ->\n\tF = fun(_, Value) ->\n\t\ttry\n\t\t\t{ok, binary_to_atom(Value, latin1)}\n\t\tcatch _:_ ->\n\t\t\t{error, not_a_binary}\n\t\tend\n\tend,\n\t%% Value, Constraints, Result.\n\tTests = [\n\t\t{<<>>, [], <<>>},\n\t\t{<<\"123\">>, int, 123},\n\t\t{<<\"123\">>, [int], 123},\n\t\t{<<\"123\">>, [nonempty, int], 123},\n\t\t{<<\"123\">>, [int, nonempty], 123},\n\t\t{<<>>, nonempty, error},\n\t\t{<<>>, [nonempty], error},\n\t\t{<<\"hello\">>, F, hello},\n\t\t{<<\"hello\">>, [F], hello},\n\t\t{<<\"123\">>, [F, int], error},\n\t\t{<<\"123\">>, [int, F], error},\n\t\t{<<\"hello\">>, [nonempty, F], hello},\n\t\t{<<\"hello\">>, [F, nonempty], hello}\n\t],\n\t[{lists:flatten(io_lib:format(\"~p, ~p\", [V, C])), fun() ->\n\t\tcase R of\n\t\t\terror -> {error, _} = validate(V, C);\n\t\t\t_ -> {ok, R} = validate(V, C)\n\t\tend\n\tend} || {V, C, R} <- Tests].\n\nreverse_test() ->\n\tF = fun(_, Value) ->\n\t\ttry\n\t\t\t{ok, atom_to_binary(Value, latin1)}\n\t\tcatch _:_ ->\n\t\t\t{error, not_an_atom}\n\t\tend\n\tend,\n\t%% Value, Constraints, Result.\n\tTests = [\n\t\t{<<>>, [], <<>>},\n\t\t{123, int, <<\"123\">>},\n\t\t{123, [int], <<\"123\">>},\n\t\t{123, [nonempty, int], <<\"123\">>},\n\t\t{123, [int, nonempty], <<\"123\">>},\n\t\t{<<>>, nonempty, error},\n\t\t{<<>>, [nonempty], error},\n\t\t{hello, F, <<\"hello\">>},\n\t\t{hello, [F], <<\"hello\">>},\n\t\t{123, [F, int], error},\n\t\t{123, [int, F], error},\n\t\t{hello, [nonempty, F], <<\"hello\">>},\n\t\t{hello, [F, nonempty], <<\"hello\">>}\n\t],\n\t[{lists:flatten(io_lib:format(\"~p, ~p\", [V, C])), fun() ->\n\t\tcase R of\n\t\t\terror -> {error, _} = reverse(V, C);\n\t\t\t_ -> {ok, R} = reverse(V, C)\n\t\tend\n\tend} || {V, C, R} <- Tests].\n\nint_format_error_test() ->\n\t{error, Reason} = validate(<<\"string\">>, int),\n\tBin = iolist_to_binary(format_error(Reason)),\n\ttrue = is_binary(Bin),\n\tok.\n\nnonempty_format_error_test() ->\n\t{error, Reason} = validate(<<>>, nonempty),\n\tBin = iolist_to_binary(format_error(Reason)),\n\ttrue = is_binary(Bin),\n\tok.\n\nfun_format_error_test() ->\n\tF = fun\n\t\t(format_error, {test, <<\"value\">>}) ->\n\t\t\tformatted;\n\t\t(_, _) ->\n\t\t\t{error, test}\n\tend,\n\t{error, Reason} = validate(<<\"value\">>, F),\n\tformatted = format_error(Reason),\n\tok.\n\n-endif.\n"
  },
  {
    "path": "src/cowboy_decompress_h.erl",
    "content": "%% Copyright (c) jdamanalo <joshuadavid.agustin@manalo.ph>\n%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_decompress_h).\n-behavior(cowboy_stream).\n\n-export([init/3]).\n-export([data/4]).\n-export([info/3]).\n-export([terminate/3]).\n-export([early_error/5]).\n\n-record(state, {\n\tnext :: any(),\n\tenabled = true :: boolean(),\n\tratio_limit :: non_neg_integer() | undefined,\n\tcompress = undefined :: undefined | gzip,\n\tinflate = undefined :: undefined | zlib:zstream(),\n\tis_reading = false :: boolean(),\n\n\t%% We use a list of binaries to avoid doing unnecessary\n\t%% memory allocations when inflating. We convert to binary\n\t%% when we propagate the data. The data must be reversed\n\t%% before converting to binary or inflating: this is done\n\t%% via the buffer_to_binary/buffer_to_iovec functions.\n\tread_body_buffer = [] :: [binary()],\n\tread_body_is_fin = nofin :: nofin | {fin, non_neg_integer()}\n}).\n\n-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())\n\t-> {cowboy_stream:commands(), #state{}}.\ninit(StreamID, Req0, Opts) ->\n\tEnabled = maps:get(decompress_enabled, Opts, true),\n\tRatioLimit = maps:get(decompress_ratio_limit, Opts, 20),\n\t{Req, State} = check_and_update_req(Req0),\n\tInflate = case State#state.compress of\n\t\tundefined ->\n\t\t\tundefined;\n\t\tgzip ->\n\t\t\tZ = zlib:open(),\n\t\t\tzlib:inflateInit(Z, 31),\n\t\t\tZ\n\tend,\n\t{Commands, Next} = cowboy_stream:init(StreamID, Req, Opts),\n\tfold(Commands, State#state{next=Next, enabled=Enabled,\n\t\tratio_limit=RatioLimit, inflate=Inflate}).\n\n-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)\n\t-> {cowboy_stream:commands(), State} when State::#state{}.\ndata(StreamID, IsFin, Data, State=#state{next=Next0, inflate=undefined}) ->\n\t{Commands, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),\n\tfold(Commands, State#state{next=Next, read_body_is_fin=IsFin});\ndata(StreamID, IsFin, Data, State=#state{next=Next0, enabled=false, read_body_buffer=Buffer}) ->\n\t{Commands, Next} = cowboy_stream:data(StreamID, IsFin,\n\t\tbuffer_to_binary([Data|Buffer]), Next0),\n\tfold(Commands, State#state{next=Next, read_body_is_fin=IsFin});\ndata(StreamID, IsFin, Data0, State0=#state{next=Next0, ratio_limit=RatioLimit,\n\t\tinflate=Z, is_reading=true, read_body_buffer=Buffer}) ->\n\tData = buffer_to_iovec([Data0|Buffer]),\n\tLimit = iolist_size(Data) * RatioLimit,\n\tcase cow_deflate:inflate(Z, Data, Limit) of\n\t\t{error, ErrorType} ->\n\t\t\tzlib:close(Z),\n\t\t\tStatus = case ErrorType of\n\t\t\t\tdata_error -> 400;\n\t\t\t\tsize_error -> 413\n\t\t\tend,\n\t\t\tCommands = [\n\t\t\t\t{error_response, Status, #{<<\"content-length\">> => <<\"0\">>}, <<>>},\n\t\t\t\tstop\n\t\t\t],\n\t\t\tfold(Commands, State0#state{inflate=undefined, read_body_buffer=[]});\n\t\t{ok, Inflated} ->\n\t\t\tState = case IsFin of\n\t\t\t\tnofin ->\n\t\t\t\t\tState0;\n\t\t\t\tfin ->\n\t\t\t\t\tzlib:close(Z),\n\t\t\t\t\tState0#state{inflate=undefined}\n\t\t\tend,\n\t\t\t{Commands, Next} = cowboy_stream:data(StreamID, IsFin, Inflated, Next0),\n\t\t\tfold(Commands, State#state{next=Next, read_body_buffer=[],\n\t\t\t\tread_body_is_fin=IsFin})\n\tend;\ndata(_, IsFin, Data, State=#state{read_body_buffer=Buffer}) ->\n\t{[], State#state{read_body_buffer=[Data|Buffer], read_body_is_fin=IsFin}}.\n\n-spec info(cowboy_stream:streamid(), any(), State)\n\t-> {cowboy_stream:commands(), State} when State::#state{}.\ninfo(StreamID, Info, State=#state{next=Next0, inflate=undefined}) ->\n\t{Commands, Next} = cowboy_stream:info(StreamID, Info, Next0),\n\tfold(Commands, State#state{next=Next});\ninfo(StreamID, Info={CommandTag, _, _, _, _}, State=#state{next=Next0, read_body_is_fin=IsFin})\n\t\twhen CommandTag =:= read_body; CommandTag =:= read_body_timeout ->\n\t{Commands0, Next1} = cowboy_stream:info(StreamID, Info, Next0),\n\t{Commands, Next} = data(StreamID, IsFin, <<>>, State#state{next=Next1, is_reading=true}),\n\tfold(Commands ++ Commands0, Next);\ninfo(StreamID, Info={set_options, Opts}, State0=#state{next=Next0,\n\t\tenabled=Enabled0, ratio_limit=RatioLimit0, is_reading=IsReading}) ->\n\tEnabled = maps:get(decompress_enabled, Opts, Enabled0),\n\tRatioLimit = maps:get(decompress_ratio_limit, Opts, RatioLimit0),\n\t{Commands, Next} = cowboy_stream:info(StreamID, Info, Next0),\n\t%% We can't change the enabled setting after we start reading,\n\t%% otherwise the data becomes garbage. Changing the setting\n\t%% is not treated as an error, it is just ignored.\n\tState = case IsReading of\n\t\ttrue -> State0;\n\t\tfalse -> State0#state{enabled=Enabled}\n\tend,\n\tfold(Commands, State#state{next=Next, ratio_limit=RatioLimit});\ninfo(StreamID, Info, State=#state{next=Next0}) ->\n\t{Commands, Next} = cowboy_stream:info(StreamID, Info, Next0),\n\tfold(Commands, State#state{next=Next}).\n\n-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> any().\nterminate(StreamID, Reason, #state{next=Next, inflate=Z}) ->\n\tcase Z of\n\t\tundefined -> ok;\n\t\t_ -> zlib:close(Z)\n\tend,\n\tcowboy_stream:terminate(StreamID, Reason, Next).\n\n-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),\n\tcowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp\n\twhen Resp::cowboy_stream:resp_command().\nearly_error(StreamID, Reason, PartialReq, Resp, Opts) ->\n\tcowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).\n\n%% Internal.\n\n%% Check whether the request needs content decoding, and if it does\n%% whether it fits our criteria for decoding. We also update the\n%% Req to indicate whether content was decoded.\n%%\n%% We always set the content_decoded value in the Req because it\n%% indicates whether content decoding was attempted.\n%%\n%% A malformed content-encoding header results in no decoding.\ncheck_and_update_req(Req=#{headers := Headers}) ->\n\tContentDecoded = maps:get(content_decoded, Req, []),\n\ttry cowboy_req:parse_header(<<\"content-encoding\">>, Req) of\n\t\t%% We only automatically decompress when gzip is the only\n\t\t%% encoding used. Since it's the only encoding used, we\n\t\t%% can remove the header entirely before passing the Req\n\t\t%% forward.\n\t\t[<<\"gzip\">>] ->\n\t\t\t{Req#{\n\t\t\t\theaders => maps:remove(<<\"content-encoding\">>, Headers),\n\t\t\t\tcontent_decoded => [<<\"gzip\">>|ContentDecoded]\n\t\t\t}, #state{compress=gzip}};\n\t\t_ ->\n\t\t\t{Req#{content_decoded => ContentDecoded},\n\t\t\t\t#state{compress=undefined}}\n\tcatch _:_ ->\n\t\t{Req#{content_decoded => ContentDecoded},\n\t\t\t#state{compress=undefined}}\n\tend.\n\nbuffer_to_iovec(Buffer) ->\n\tlists:reverse(Buffer).\n\nbuffer_to_binary(Buffer) ->\n\tiolist_to_binary(lists:reverse(Buffer)).\n\nfold(Commands, State) ->\n\tfold(Commands, State, []).\n\nfold([], State, Acc) ->\n\t{lists:reverse(Acc), State};\nfold([{response, Status, Headers0, Body}|Tail], State=#state{enabled=true}, Acc) ->\n\tHeaders = add_accept_encoding(Headers0),\n\tfold(Tail, State, [{response, Status, Headers, Body}|Acc]);\nfold([{headers, Status, Headers0} | Tail], State=#state{enabled=true}, Acc) ->\n\tHeaders = add_accept_encoding(Headers0),\n\tfold(Tail, State, [{headers, Status, Headers}|Acc]);\nfold([Command|Tail], State, Acc) ->\n\tfold(Tail, State, [Command|Acc]).\n\nadd_accept_encoding(Headers=#{<<\"accept-encoding\">> := AcceptEncoding}) ->\n\ttry cow_http_hd:parse_accept_encoding(iolist_to_binary(AcceptEncoding)) of\n\t\tList ->\n\t\t\tcase lists:keyfind(<<\"gzip\">>, 1, List) of\n\t\t\t\t%% gzip is excluded but this handler is enabled; we replace.\n\t\t\t\t{_, 0} ->\n\t\t\t\t\tReplaced = lists:keyreplace(<<\"gzip\">>, 1, List, {<<\"gzip\">>, 1000}),\n\t\t\t\t\tCodings = build_accept_encoding(Replaced),\n\t\t\t\t\tHeaders#{<<\"accept-encoding\">> => Codings};\n\t\t\t\t{_, _} ->\n\t\t\t\t\tHeaders;\n\t\t\t\tfalse ->\n\t\t\t\t\tcase lists:keyfind(<<\"*\">>, 1, List) of\n\t\t\t\t\t\t%% Others are excluded along with gzip; we add.\n\t\t\t\t\t\t{_, 0} ->\n\t\t\t\t\t\t\tWithGzip = [{<<\"gzip\">>, 1000} | List],\n\t\t\t\t\t\t\tCodings = build_accept_encoding(WithGzip),\n\t\t\t\t\t\t\tHeaders#{<<\"accept-encoding\">> => Codings};\n\t\t\t\t\t\t{_, _} ->\n\t\t\t\t\t\t\tHeaders;\n\t\t\t\t\t\tfalse ->\n\t\t\t\t\t\t\tHeaders#{<<\"accept-encoding\">> => [AcceptEncoding, <<\", gzip\">>]}\n\t\t\t\t\tend\n\t\t\tend\n\tcatch _:_ ->\n\t\t%% The accept-encoding header is invalid. Probably empty. We replace it with ours.\n\t\tHeaders#{<<\"accept-encoding\">> => <<\"gzip\">>}\n\tend;\nadd_accept_encoding(Headers) ->\n\tHeaders#{<<\"accept-encoding\">> => <<\"gzip\">>}.\n\n%% @todo From cowlib, maybe expose?\nqvalue_to_iodata(0) -> <<\"0\">>;\nqvalue_to_iodata(Q) when Q < 10 -> [<<\"0.00\">>, integer_to_binary(Q)];\nqvalue_to_iodata(Q) when Q < 100 -> [<<\"0.0\">>, integer_to_binary(Q)];\nqvalue_to_iodata(Q) when Q < 1000 -> [<<\"0.\">>, integer_to_binary(Q)];\nqvalue_to_iodata(1000) -> <<\"1\">>.\n\n%% @todo Should be added to Cowlib.\nbuild_accept_encoding([{ContentCoding, Q}|Tail]) ->\n\tWeight = iolist_to_binary(qvalue_to_iodata(Q)),\n\tAcc = <<ContentCoding/binary, \";q=\", Weight/binary>>,\n\tdo_build_accept_encoding(Tail, Acc).\n\ndo_build_accept_encoding([{ContentCoding, Q}|Tail], Acc0) ->\n\tWeight = iolist_to_binary(qvalue_to_iodata(Q)),\n\tAcc = <<Acc0/binary, \", \", ContentCoding/binary, \";q=\", Weight/binary>>,\n\tdo_build_accept_encoding(Tail, Acc);\ndo_build_accept_encoding([], Acc) ->\n\tAcc.\n"
  },
  {
    "path": "src/cowboy_dynamic_buffer.hrl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% These functions are common to cowboy_http, cowboy_http2 and\n%% cowboy_websocket. It requires the options and the state\n%% to use the same field names.\n\n%% Experiments have shown that the size of the 'buffer' can greatly\n%% impact performance: a buffer too small leads to more messages\n%% being handled and typically more binary appends; and a buffer\n%% too large results in inefficient use of memory which in turn\n%% reduces the throughput, presumably because large binary appends\n%% are not as efficient as smaller ones, and because while the\n%% buffer gets allocated only when there is data, the allocated\n%% size remains until the binary is GC and so under-use hurts.\n%%\n%% The performance of a given 'buffer' size will also depend on\n%% how the client is sending data, and on the protocol. For example,\n%% HTTP/1.1 doesn't need a very large 'buffer' size for reading\n%% request headers, but it does need one for reading large request\n%% bodies. At the same time, HTTP/2 performs best reading large\n%% request bodies when the 'buffer' size is about half that of\n%% HTTP/1.1.\n%%\n%% It therefore becomes important to resize the buffer dynamically\n%% depending on what is currently going on. We do this based on\n%% the size of data packets we received from the transport. We\n%% maintain a moving average and when that moving average is\n%% 90% of the current 'buffer' size, we double the 'buffer' size.\n%% When things slow down and the moving average falls below\n%% 40% of the current 'buffer' size, we halve the 'buffer' size.\n%%\n%% To calculate the moving average we do (MovAvg + DataLen) div 2.\n%% This means that the moving average will change very quickly when\n%% DataLen increases or decreases rapidly. That's OK, we want to\n%% be reactive, but also setting the buffer size is a pretty fast\n%% operation. The formula could be changed to the following if it\n%% became a problem: (MovAvg * N + DataLen) div (N + 1).\n%%\n%% Note that this works best when active,N uses low values of N.\n%% We don't want to accumulate too much data because we resize\n%% the buffer.\n\ninit_dynamic_buffer_size(#{dynamic_buffer_initial_size := DynamicBuffer}) ->\n\tDynamicBuffer;\ninit_dynamic_buffer_size(#{dynamic_buffer := {LowDynamicBuffer, _}}) ->\n\tLowDynamicBuffer;\ninit_dynamic_buffer_size(_) ->\n\tfalse.\n\nmaybe_resize_buffer(State=#state{dynamic_buffer_size=false}, _) ->\n\tState;\nmaybe_resize_buffer(State=#state{transport=Transport, socket=Socket,\n\t\topts=#{dynamic_buffer := {LowDynamicBuffer, HighDynamicBuffer}},\n\t\tdynamic_buffer_size=BufferSize0, dynamic_buffer_moving_average=MovingAvg0}, Data) ->\n\tDataLen = byte_size(Data),\n\tMovingAvg = (MovingAvg0 * 7 + DataLen) / 8,\n\tif\n\t\tBufferSize0 < HighDynamicBuffer andalso MovingAvg > BufferSize0 * 0.9 ->\n\t\t\tBufferSize = min(BufferSize0 * 2, HighDynamicBuffer),\n\t\t\tok = maybe_socket_error(State, Transport:setopts(Socket, [{buffer, BufferSize}])),\n\t\t\tState#state{dynamic_buffer_moving_average=MovingAvg, dynamic_buffer_size=BufferSize};\n\t\tBufferSize0 > LowDynamicBuffer andalso MovingAvg < BufferSize0 * 0.4 ->\n\t\t\tBufferSize = max(BufferSize0 div 2, LowDynamicBuffer),\n\t\t\tok = maybe_socket_error(State, Transport:setopts(Socket, [{buffer, BufferSize}])),\n\t\t\tState#state{dynamic_buffer_moving_average=MovingAvg, dynamic_buffer_size=BufferSize};\n\t\ttrue ->\n\t\t\tState#state{dynamic_buffer_moving_average=MovingAvg}\n\tend.\n"
  },
  {
    "path": "src/cowboy_handler.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% Handler middleware.\n%%\n%% Execute the handler given by the <em>handler</em> and <em>handler_opts</em>\n%% environment values. The result of this execution is added to the\n%% environment under the <em>result</em> value.\n-module(cowboy_handler).\n-behaviour(cowboy_middleware).\n\n-export([execute/2]).\n-export([terminate/4]).\n\n-callback init(Req, any())\n\t-> {ok | module(), Req, any()}\n\t| {module(), Req, any(), any()}\n\twhen Req::cowboy_req:req().\n\n-callback terminate(any(), map(), any()) -> ok.\n-optional_callbacks([terminate/3]).\n\n-spec execute(Req, Env) -> {ok, Req, Env}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\nexecute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) ->\n\ttry Handler:init(Req, HandlerOpts) of\n\t\t{ok, Req2, State} ->\n\t\t\tResult = terminate(normal, Req2, State, Handler),\n\t\t\t{ok, Req2, Env#{result => Result}};\n\t\t{Mod, Req2, State} ->\n\t\t\tMod:upgrade(Req2, Env, Handler, State);\n\t\t{Mod, Req2, State, Opts} ->\n\t\t\tMod:upgrade(Req2, Env, Handler, State, Opts)\n\tcatch Class:Reason:Stacktrace ->\n\t\tterminate({crash, Class, Reason}, Req, HandlerOpts, Handler),\n\t\terlang:raise(Class, Reason, Stacktrace)\n\tend.\n\n-spec terminate(any(), Req | undefined, any(), module()) -> ok when Req::cowboy_req:req().\nterminate(Reason, Req, State, Handler) ->\n\tcase erlang:function_exported(Handler, terminate, 3) of\n\t\ttrue ->\n\t\t\tHandler:terminate(Reason, Req, State);\n\t\tfalse ->\n\t\t\tok\n\tend.\n"
  },
  {
    "path": "src/cowboy_http.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% @todo Worth renaming to cowboy_http1.\n%% @todo Change use of cow_http to cow_http1 where appropriate.\n-module(cowboy_http).\n\n-export([init/6]).\n-export([loop/1]).\n\n-export([system_continue/3]).\n-export([system_terminate/4]).\n-export([system_code_change/4]).\n\n-type opts() :: #{\n\tactive_n => pos_integer(),\n\talpn_default_protocol => http | http2,\n\tchunked => boolean(),\n\tcompress_buffering => boolean(),\n\tcompress_threshold => non_neg_integer(),\n\tconnection_type => worker | supervisor,\n\tdynamic_buffer => false | {pos_integer(), pos_integer()},\n\tdynamic_buffer_initial_average => non_neg_integer(),\n\tdynamic_buffer_initial_size => pos_integer(),\n\tenv => cowboy_middleware:env(),\n\thibernate => boolean(),\n\thttp10_keepalive => boolean(),\n\tidle_timeout => timeout(),\n\tinactivity_timeout => timeout(),\n\tinitial_stream_flow_size => non_neg_integer(),\n\tlinger_timeout => timeout(),\n\tlogger => module(),\n\tmax_authority_length => non_neg_integer(),\n\tmax_empty_lines => non_neg_integer(),\n\tmax_header_name_length => non_neg_integer(),\n\tmax_header_value_length => non_neg_integer(),\n\tmax_headers => non_neg_integer(),\n\tmax_keepalive => non_neg_integer(),\n\tmax_method_length => non_neg_integer(),\n\tmax_request_line_length => non_neg_integer(),\n\tmetrics_callback => cowboy_metrics_h:metrics_callback(),\n\tmetrics_req_filter => fun((cowboy_req:req()) -> map()),\n\tmetrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),\n\tmiddlewares => [module()],\n\tprotocols => [http | http2],\n\tproxy_header => boolean(),\n\trequest_timeout => timeout(),\n\treset_idle_timeout_on_send => boolean(),\n\tsendfile => boolean(),\n\tshutdown_timeout => timeout(),\n\tstream_handlers => [module()],\n\ttracer_callback => cowboy_tracer_h:tracer_callback(),\n\ttracer_flags => [atom()],\n\ttracer_match_specs => cowboy_tracer_h:tracer_match_specs(),\n\t%% Open ended because configured stream handlers might add options.\n\t_ => _\n}.\n-export_type([opts/0]).\n\n-record(ps_request_line, {\n\tempty_lines = 0 :: non_neg_integer()\n}).\n\n-record(ps_header, {\n\tmethod = undefined :: binary(),\n\tauthority = undefined :: binary() | undefined,\n\tpath = undefined :: binary(),\n\tqs = undefined :: binary(),\n\tversion = undefined :: cowboy:http_version(),\n\theaders = undefined :: cowboy:http_headers() | undefined,\n\tname = undefined :: binary() | undefined\n}).\n\n-record(ps_body, {\n\tlength :: non_neg_integer() | undefined,\n\treceived = 0 :: non_neg_integer(),\n\ttransfer_decode_fun :: fun((binary(), cow_http_te:state()) -> cow_http_te:decode_ret()),\n\ttransfer_decode_state :: cow_http_te:state()\n}).\n\n-record(stream, {\n\tid = undefined :: cowboy_stream:streamid(),\n\t%% Stream handlers and their state.\n\tstate = undefined :: {module(), any()},\n\t%% Request method.\n\tmethod = undefined :: binary(),\n\t%% Client HTTP version for this stream.\n\tversion = undefined :: cowboy:http_version(),\n\t%% Unparsed te header. Used to know if we can send trailers.\n\tte :: undefined | binary(),\n\t%% Expected body size.\n\tlocal_expected_size = undefined :: undefined | non_neg_integer(),\n\t%% Sent body size.\n\tlocal_sent_size = 0 :: non_neg_integer(),\n\t%% Commands queued.\n\tqueue = [] :: cowboy_stream:commands()\n}).\n\n-type stream() :: #stream{}.\n\n-record(state, {\n\tparent :: pid(),\n\tref :: ranch:ref(),\n\tsocket :: inet:socket(),\n\ttransport :: module(),\n\tproxy_header :: undefined | ranch_proxy_header:proxy_info(),\n\topts = #{} :: cowboy:opts(),\n\tbuffer = <<>> :: binary(),\n\n\t%% Some options may be overriden for the current stream.\n\toverriden_opts = #{} :: cowboy:opts(),\n\n\t%% Remote address and port for the connection.\n\tpeer = undefined :: {inet:ip_address(), inet:port_number()},\n\n\t%% Local address and port for the connection.\n\tsock = undefined :: {inet:ip_address(), inet:port_number()},\n\n\t%% Client certificate (TLS only).\n\tcert :: undefined | binary(),\n\n\ttimer = undefined :: undefined | reference(),\n\n\t%% Whether we are currently receiving data from the socket.\n\tactive = true :: boolean(),\n\n\t%% Identifier for the stream currently being read (or waiting to be received).\n\tin_streamid = 1 :: pos_integer(),\n\n\t%% Parsing state for the current stream or stream-to-be.\n\tin_state = #ps_request_line{} :: #ps_request_line{} | #ps_header{} | #ps_body{},\n\n\t%% Flow requested for the current stream.\n\tflow = infinity :: non_neg_integer() | infinity,\n\n\t%% Dynamic buffer moving average and current buffer size.\n\tdynamic_buffer_size :: pos_integer() | false,\n\tdynamic_buffer_moving_average :: float(),\n\n\t%% Identifier for the stream currently being written.\n\t%% Note that out_streamid =< in_streamid.\n\tout_streamid = 1 :: pos_integer(),\n\n\t%% Whether we finished writing data for the current stream.\n\tout_state = wait :: wait | chunked | streaming | done,\n\n\t%% The connection will be closed after this stream.\n\tlast_streamid = undefined :: pos_integer(),\n\n\t%% Currently active HTTP/1.1 streams.\n\tstreams = [] :: [stream()],\n\n\t%% Children processes created by streams.\n\tchildren = cowboy_children:init() :: cowboy_children:children()\n}).\n\n-include_lib(\"cowlib/include/cow_inline.hrl\").\n-include_lib(\"cowlib/include/cow_parse.hrl\").\n\n-spec init(pid(), ranch:ref(), inet:socket(), module(),\n\tranch_proxy_header:proxy_info(), cowboy:opts()) -> ok.\ninit(Parent, Ref, Socket, Transport, ProxyHeader, Opts) ->\n\t{ok, Peer} = maybe_socket_error(undefined, Transport:peername(Socket),\n\t\t'A socket error occurred when retrieving the peer name.'),\n\t{ok, Sock} = maybe_socket_error(undefined, Transport:sockname(Socket),\n\t\t'A socket error occurred when retrieving the sock name.'),\n\tCertResult = case Transport:name() of\n\t\tssl ->\n\t\t\tcase ssl:peercert(Socket) of\n\t\t\t\t{error, no_peercert} ->\n\t\t\t\t\t{ok, undefined};\n\t\t\t\tCert0 ->\n\t\t\t\t\tCert0\n\t\t\tend;\n\t\t_ ->\n\t\t\t{ok, undefined}\n\tend,\n\t{ok, Cert} = maybe_socket_error(undefined, CertResult,\n\t\t'A socket error occurred when retrieving the client TLS certificate.'),\n\tState = #state{\n\t\tparent=Parent, ref=Ref, socket=Socket,\n\t\ttransport=Transport, proxy_header=ProxyHeader, opts=Opts,\n\t\tpeer=Peer, sock=Sock, cert=Cert,\n\t\tdynamic_buffer_size=init_dynamic_buffer_size(Opts),\n\t\tdynamic_buffer_moving_average=maps:get(dynamic_buffer_initial_average, Opts, 0.0),\n\t\tlast_streamid=maps:get(max_keepalive, Opts, 1000)},\n\tsafe_setopts_active(State),\n\tbefore_loop(set_timeout(State, request_timeout)).\n\n-include(\"cowboy_dynamic_buffer.hrl\").\n\nsetopts_active(#state{socket=Socket, transport=Transport, opts=Opts}) ->\n\tN = maps:get(active_n, Opts, 1),\n\tTransport:setopts(Socket, [{active, N}]).\n\nsafe_setopts_active(State) ->\n\tok = maybe_socket_error(State, setopts_active(State)).\n\nactive(State) ->\n\tsafe_setopts_active(State),\n\tState#state{active=true}.\n\npassive(State=#state{socket=Socket, transport=Transport}) ->\n\tok = maybe_socket_error(State, Transport:setopts(Socket, [{active, false}])),\n\tMessages = Transport:messages(),\n\tflush_passive(Socket, Messages),\n\tState#state{active=false}.\n\nflush_passive(Socket, Messages) ->\n\treceive\n\t\t{Passive, Socket} when Passive =:= element(4, Messages);\n\t\t\t\t%% Hardcoded for compatibility with Ranch 1.x.\n\t\t\t\tPassive =:= tcp_passive; Passive =:= ssl_passive ->\n\t\t\tflush_passive(Socket, Messages)\n\tafter 0 ->\n\t\tok\n\tend.\n\nbefore_loop(State=#state{opts=#{hibernate := true}}) ->\n\tproc_lib:hibernate(?MODULE, loop, [State]);\nbefore_loop(State) ->\n\tloop(State).\n\n-spec loop(#state{}) -> ok.\n\nloop(State=#state{parent=Parent, socket=Socket, transport=Transport, opts=Opts,\n\t\tbuffer=Buffer, timer=TimerRef, children=Children, in_streamid=InStreamID,\n\t\tlast_streamid=LastStreamID}) ->\n\tMessages = Transport:messages(),\n\tInactivityTimeout = maps:get(inactivity_timeout, Opts, 300000),\n\treceive\n\t\t%% Discard data coming in after the last request\n\t\t%% we want to process was received fully.\n\t\t{OK, Socket, Data} when OK =:= element(1, Messages), InStreamID > LastStreamID ->\n\t\t\tState1 = maybe_resize_buffer(State, Data),\n\t\t\tbefore_loop(State1);\n\t\t%% Socket messages.\n\t\t{OK, Socket, Data} when OK =:= element(1, Messages) ->\n\t\t\tState1 = maybe_resize_buffer(State, Data),\n\t\t\tparse(<< Buffer/binary, Data/binary >>, State1);\n\t\t{Closed, Socket} when Closed =:= element(2, Messages) ->\n\t\t\tterminate(State, {socket_error, closed, 'The socket has been closed.'});\n\t\t{Error, Socket, Reason} when Error =:= element(3, Messages) ->\n\t\t\tterminate(State, {socket_error, Reason, 'An error has occurred on the socket.'});\n\t\t{Passive, Socket} when Passive =:= element(4, Messages);\n\t\t\t\t%% Hardcoded for compatibility with Ranch 1.x.\n\t\t\t\tPassive =:= tcp_passive; Passive =:= ssl_passive ->\n\t\t\tsafe_setopts_active(State),\n\t\t\tbefore_loop(State);\n\t\t%% Timeouts.\n\t\t{timeout, Ref, {shutdown, Pid}} ->\n\t\t\tcowboy_children:shutdown_timeout(Children, Ref, Pid),\n\t\t\tbefore_loop(State);\n\t\t{timeout, TimerRef, Reason} ->\n\t\t\ttimeout(State, Reason);\n\t\t{timeout, _, _} ->\n\t\t\tbefore_loop(State);\n\t\t%% System messages.\n\t\t{'EXIT', Parent, shutdown} ->\n\t\t\tReason = {stop, {exit, shutdown}, 'Parent process requested shutdown.'},\n\t\t\tbefore_loop(initiate_closing(State, Reason));\n\t\t{'EXIT', Parent, Reason} ->\n\t\t\tterminate(State, {stop, {exit, Reason}, 'Parent process terminated.'});\n\t\t{system, From, Request} ->\n\t\t\tsys:handle_system_msg(Request, From, Parent, ?MODULE, [], State);\n\t\t%% Messages pertaining to a stream.\n\t\t{{Pid, StreamID}, Msg} when Pid =:= self() ->\n\t\t\tbefore_loop(info(State, StreamID, Msg));\n\t\t%% Exit signal from children.\n\t\tMsg = {'EXIT', Pid, _} ->\n\t\t\tbefore_loop(down(State, Pid, Msg));\n\t\t%% Calls from supervisor module.\n\t\t{'$gen_call', From, Call} ->\n\t\t\tcowboy_children:handle_supervisor_call(Call, From, Children, ?MODULE),\n\t\t\tbefore_loop(State);\n\t\t%% Unknown messages.\n\t\tMsg ->\n\t\t\tcowboy:log(warning, \"Received stray message ~p.~n\", [Msg], Opts),\n\t\t\tbefore_loop(State)\n\tafter InactivityTimeout ->\n\t\tterminate(State, {internal_error, timeout, 'No message or data received before timeout.'})\n\tend.\n\n%% For HTTP/1.1 we have two types of timeouts: the request_timeout\n%% is used when there is no currently ongoing request. This means\n%% that we are not currently sending or receiving data and that\n%% the next data to be received will be a new request. The\n%% request_timeout is set once when we no longer have ongoing\n%% requests, and runs until the full set of request headers\n%% is received. It is not reset.\n%%\n%% After that point we use the idle_timeout. We continue using\n%% the idle_timeout if pipelined requests come in: we are doing\n%% work and just want to ensure the socket is not half-closed.\n%% We continue using the idle_timeout up until there is no\n%% ongoing request. This includes requests that were processed\n%% and for which we only want to skip the body. Once the body\n%% has been read fully we can go back to request_timeout. The\n%% idle_timeout is reset every time we receive data and,\n%% optionally, every time we send data.\n\n%% We do not set request_timeout if we are skipping a body.\nset_timeout(State=#state{in_state=#ps_body{}}, request_timeout) ->\n\tState;\n%% We do not set idle_timeout if there are no active streams,\n%% unless when we are skipping a body.\nset_timeout(State=#state{streams=[], in_state=InState}, idle_timeout)\n\t\twhen element(1, InState) =/= ps_body ->\n\tState;\n%% Otherwise we can set the timeout.\n%% @todo Don't do this so often, use a strategy similar to Websocket/H2 if possible.\nset_timeout(State0=#state{opts=Opts, overriden_opts=Override}, Name) ->\n\tState = cancel_timeout(State0),\n\tDefault = case Name of\n\t\trequest_timeout -> 5000;\n\t\tidle_timeout -> 60000\n\tend,\n\tTimeout = case Override of\n\t\t%% The timeout may have been overriden for the current stream.\n\t\t#{Name := Timeout0} -> Timeout0;\n\t\t_ -> maps:get(Name, Opts, Default)\n\tend,\n\tTimerRef = case Timeout of\n\t\tinfinity -> undefined;\n\t\tTimeout -> erlang:start_timer(Timeout, self(), Name)\n\tend,\n\tState#state{timer=TimerRef}.\n\nmaybe_reset_idle_timeout(State=#state{opts=Opts}) ->\n\tcase maps:get(reset_idle_timeout_on_send, Opts, false) of\n\t\ttrue ->\n\t\t\tset_timeout(State, idle_timeout);\n\t\tfalse ->\n\t\t\tState\n\tend.\n\ncancel_timeout(State=#state{timer=TimerRef}) ->\n\tok = case TimerRef of\n\t\tundefined ->\n\t\t\tok;\n\t\t_ ->\n\t\t\t%% Do a synchronous cancel and remove the message if any\n\t\t\t%% to avoid receiving stray messages.\n\t\t\t_ = erlang:cancel_timer(TimerRef, [{async, false}, {info, false}]),\n\t\t\treceive\n\t\t\t\t{timeout, TimerRef, _} -> ok\n\t\t\tafter 0 ->\n\t\t\t\tok\n\t\t\tend\n\tend,\n\tState#state{timer=undefined}.\n\n-spec timeout(_, _) -> no_return().\ntimeout(State=#state{in_state=#ps_request_line{}}, request_timeout) ->\n\tterminate(State, {connection_error, timeout,\n\t\t'No request-line received before timeout.'});\ntimeout(State=#state{in_state=#ps_header{}}, request_timeout) ->\n\terror_terminate(408, State, {connection_error, timeout,\n\t\t'Request headers not received before timeout.'});\ntimeout(State, idle_timeout) ->\n\tterminate(State, {connection_error, timeout,\n\t\t'Connection idle longer than configuration allows.'}).\n\nparse(<<>>, State) ->\n\tbefore_loop(State#state{buffer= <<>>});\n%% Do not process requests that come in after the last request\n%% and discard the buffer if any to save memory.\nparse(_, State=#state{in_streamid=InStreamID, in_state=#ps_request_line{},\n\t\tlast_streamid=LastStreamID}) when InStreamID > LastStreamID ->\n\tbefore_loop(State#state{buffer= <<>>});\nparse(Buffer, State=#state{in_state=#ps_request_line{empty_lines=EmptyLines}}) ->\n\tafter_parse(parse_request(Buffer, State, EmptyLines));\nparse(Buffer, State=#state{in_state=PS=#ps_header{headers=Headers, name=undefined}}) ->\n\tafter_parse(parse_header(Buffer,\n\t\tState#state{in_state=PS#ps_header{headers=undefined}},\n\t\tHeaders));\nparse(Buffer, State=#state{in_state=PS=#ps_header{headers=Headers, name=Name}}) ->\n\tafter_parse(parse_hd_before_value(Buffer,\n\t\tState#state{in_state=PS#ps_header{headers=undefined, name=undefined}},\n\t\tHeaders, Name));\nparse(Buffer, State=#state{in_state=#ps_body{}}) ->\n\tafter_parse(parse_body(Buffer, State)).\n\nafter_parse({request, Req=#{streamid := StreamID, method := Method,\n\t\theaders := Headers, version := Version},\n\t\tState0=#state{opts=Opts, buffer=Buffer, streams=Streams0}}) ->\n\ttry cowboy_stream:init(StreamID, Req, Opts) of\n\t\t{Commands, StreamState} ->\n\t\t\tFlow = maps:get(initial_stream_flow_size, Opts, 65535),\n\t\t\tTE = maps:get(<<\"te\">>, Headers, undefined),\n\t\t\tStreams = [#stream{id=StreamID, state=StreamState,\n\t\t\t\tmethod=Method, version=Version, te=TE}|Streams0],\n\t\t\tState1 = State0#state{streams=Streams, flow=Flow},\n\t\t\tState2 = case maybe_req_close(State1, Headers, Version) of\n\t\t\t\tclose ->\n\t\t\t\t\tState1#state{last_streamid=StreamID};\n\t\t\t\tkeepalive ->\n\t\t\t\t\tState1;\n\t\t\t\tbad_connection_header ->\n\t\t\t\t\terror_terminate(400, State1, {connection_error, protocol_error,\n\t\t\t\t\t\t'The Connection header is invalid. (RFC7230 6.1)'})\n\t\t\tend,\n\t\t\tState = set_timeout(State2, idle_timeout),\n\t\t\tparse(Buffer, commands(State, StreamID, Commands))\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(init,\n\t\t\t[StreamID, Req, Opts],\n\t\t\tClass, Exception, Stacktrace), Opts),\n\t\t%% We do not reset the idle timeout on send here\n\t\t%% because an error occurred in the application. While we\n\t\t%% are keeping the connection open for further requests we\n\t\t%% do not want to keep the connection up too long if no\n\t\t%% additional requests come in.\n\t\tearly_error(500, State0, {internal_error, {Class, Exception},\n\t\t\t'Unhandled exception in cowboy_stream:init/3.'}, Req),\n\t\tparse(Buffer, State0)\n\tend;\n%% Streams are sequential so the body is always about the last stream created\n%% unless that stream has terminated.\nafter_parse({data, StreamID, IsFin, Data, State0=#state{opts=Opts, buffer=Buffer,\n\t\tstreams=Streams0=[Stream=#stream{id=StreamID, state=StreamState0}|_]}}) ->\n\ttry cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of\n\t\t{Commands, StreamState} ->\n\t\t\tStreams = lists:keyreplace(StreamID, #stream.id, Streams0,\n\t\t\t\tStream#stream{state=StreamState}),\n\t\t\tState1 = set_timeout(State0, idle_timeout),\n\t\t\tState = update_flow(IsFin, Data, State1#state{streams=Streams}),\n\t\t\tparse(Buffer, commands(State, StreamID, Commands))\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(data,\n\t\t\t[StreamID, IsFin, Data, StreamState0],\n\t\t\tClass, Exception, Stacktrace), Opts),\n\t\t%% @todo Should call parse after this.\n\t\tstream_terminate(State0, StreamID, {internal_error, {Class, Exception},\n\t\t\t'Unhandled exception in cowboy_stream:data/4.'})\n\tend;\n%% No corresponding stream. We must skip the body of the previous request\n%% in order to process the next one.\nafter_parse({data, _, IsFin, _, State=#state{buffer=Buffer}}) ->\n\tparse(Buffer, set_timeout(State, case IsFin of\n\t\tfin -> request_timeout;\n\t\tnofin -> idle_timeout\n\tend));\nafter_parse({more, State}) ->\n\tbefore_loop(set_timeout(State, idle_timeout)).\n\nupdate_flow(fin, _, State) ->\n\t%% This function is only called after parsing, therefore we\n\t%% are expecting to be in active mode already.\n\tState#state{flow=infinity};\nupdate_flow(nofin, Data, State0=#state{flow=Flow0}) ->\n\tFlow = Flow0 - byte_size(Data),\n\tState = State0#state{flow=Flow},\n\tif\n\t\tFlow0 > 0, Flow =< 0 ->\n\t\t\tpassive(State);\n\t\ttrue ->\n\t\t\tState\n\tend.\n\n%% Request-line.\n\n-spec parse_request(Buffer, State, non_neg_integer())\n\t-> {request, cowboy_req:req(), State}\n\t| {data, cowboy_stream:streamid(), cowboy_stream:fin(), binary(), State}\n\t| {more, State}\n\twhen Buffer::binary(), State::#state{}.\n%% Empty lines must be using \\r\\n.\nparse_request(<< $\\n, _/bits >>, State, _) ->\n\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t'Empty lines between requests must use the CRLF line terminator. (RFC7230 3.5)'});\nparse_request(<< $\\s, _/bits >>, State, _) ->\n\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t'The request-line must not begin with a space. (RFC7230 3.1.1, RFC7230 3.5)'});\n%% We limit the length of the Request-line to MaxLength to avoid endlessly\n%% reading from the socket and eventually crashing.\nparse_request(Buffer, State=#state{opts=Opts, in_streamid=InStreamID}, EmptyLines) ->\n\tMaxLength = maps:get(max_request_line_length, Opts, 8000),\n\tMaxEmptyLines = maps:get(max_empty_lines, Opts, 5),\n\tcase match_eol(Buffer, 0) of\n\t\tnomatch when byte_size(Buffer) > MaxLength ->\n\t\t\terror_terminate(414, State, {connection_error, limit_reached,\n\t\t\t\t'The request-line length is larger than configuration allows. (RFC7230 3.1.1)'});\n\t\tnomatch ->\n\t\t\t{more, State#state{buffer=Buffer, in_state=#ps_request_line{empty_lines=EmptyLines}}};\n\t\t1 when EmptyLines =:= MaxEmptyLines ->\n\t\t\terror_terminate(400, State, {connection_error, limit_reached,\n\t\t\t\t'More empty lines were received than configuration allows. (RFC7230 3.5)'});\n\t\t1 ->\n\t\t\t<< _:16, Rest/bits >> = Buffer,\n\t\t\tparse_request(Rest, State, EmptyLines + 1);\n\t\t_ ->\n\t\t\tcase Buffer of\n\t\t\t\t%% @todo * is only for server-wide OPTIONS request (RFC7230 5.3.4); tests\n\t\t\t\t<< \"OPTIONS * \", Rest/bits >> ->\n\t\t\t\t\tparse_version(Rest, State, <<\"OPTIONS\">>, undefined, <<\"*\">>, <<>>);\n\t\t\t\t<<\"CONNECT \", _/bits>> ->\n\t\t\t\t\terror_terminate(501, State, {connection_error, no_error,\n\t\t\t\t\t\t'The CONNECT method is currently not implemented. (RFC7231 4.3.6)'});\n\t\t\t\t<<\"TRACE \", _/bits>> ->\n\t\t\t\t\terror_terminate(501, State, {connection_error, no_error,\n\t\t\t\t\t\t'The TRACE method is currently not implemented. (RFC7231 4.3.8)'});\n\t\t\t\t%% Accept direct HTTP/2 only at the beginning of the connection.\n\t\t\t\t<< \"PRI * HTTP/2.0\\r\\n\", _/bits >> when InStreamID =:= 1 ->\n\t\t\t\t\tcase lists:member(http2, maps:get(protocols, Opts, [http2, http])) of\n\t\t\t\t\t\ttrue ->\n\t\t\t\t\t\t\thttp2_upgrade(State, Buffer);\n\t\t\t\t\t\tfalse ->\n\t\t\t\t\t\t\terror_terminate(501, State, {connection_error, no_error,\n\t\t\t\t\t\t\t\t'Prior knowledge upgrade to HTTP/2 is disabled by configuration.'})\n\t\t\t\t\tend;\n\t\t\t\t_ ->\n\t\t\t\t\tparse_method(Buffer, State, <<>>,\n\t\t\t\t\t\tmaps:get(max_method_length, Opts, 32))\n\t\t\tend\n\tend.\n\nmatch_eol(<< $\\n, _/bits >>, N) ->\n\tN;\nmatch_eol(<< _, Rest/bits >>, N) ->\n\tmatch_eol(Rest, N + 1);\nmatch_eol(_, _) ->\n\tnomatch.\n\nparse_method(_, State, _, 0) ->\n\terror_terminate(501, State, {connection_error, limit_reached,\n\t\t'The method name is longer than configuration allows. (RFC7230 3.1.1)'});\nparse_method(<< C, Rest/bits >>, State, SoFar, Remaining) ->\n\tcase C of\n\t\t$\\r -> error_terminate(400, State, {connection_error, protocol_error,\n\t\t\t'The method name must not be followed with a line break. (RFC7230 3.1.1)'});\n\t\t$\\s -> parse_uri(Rest, State, SoFar);\n\t\t_ when ?IS_TOKEN(C) -> parse_method(Rest, State, << SoFar/binary, C >>, Remaining - 1);\n\t\t_ -> error_terminate(400, State, {connection_error, protocol_error,\n\t\t\t'The method name must contain only valid token characters. (RFC7230 3.1.1)'})\n\tend.\n\nparse_uri(<< H, T, T, P, \"://\", Rest/bits >>, State, Method)\n\t\twhen H =:= $h orelse H =:= $H, T =:= $t orelse T =:= $T;\n\t\t\tP =:= $p orelse P =:= $P ->\n\tparse_uri_authority(Rest, State, Method);\nparse_uri(<< H, T, T, P, S, \"://\", Rest/bits >>, State, Method)\n\t\twhen H =:= $h orelse H =:= $H, T =:= $t orelse T =:= $T;\n\t\t\tP =:= $p orelse P =:= $P; S =:= $s orelse S =:= $S ->\n\tparse_uri_authority(Rest, State, Method);\nparse_uri(<< $/, Rest/bits >>, State, Method) ->\n\tparse_uri_path(Rest, State, Method, undefined, <<$/>>);\nparse_uri(_, State, _) ->\n\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t'Invalid request-line or request-target. (RFC7230 3.1.1, RFC7230 5.3)'}).\n\n%% @todo We probably want to apply max_authority_length also\n%% to the host header and to document this option. It might\n%% also be useful for HTTP/2 requests.\nparse_uri_authority(Rest, State=#state{opts=Opts}, Method) ->\n\tparse_uri_authority(Rest, State, Method, <<>>,\n\t\tmaps:get(max_authority_length, Opts, 255)).\n\nparse_uri_authority(_, State, _, _, 0) ->\n\terror_terminate(414, State, {connection_error, limit_reached,\n\t\t'The authority component of the absolute URI is longer than configuration allows. (RFC7230 2.7.1)'});\nparse_uri_authority(<<C, Rest/bits>>, State, Method, SoFar, Remaining) ->\n\tcase C of\n\t\t$\\r ->\n\t\t\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t\t\t'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});\n\t\t$@ ->\n\t\t\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t\t\t'Absolute URIs must not include a userinfo component. (RFC7230 2.7.1)'});\n\t\tC when SoFar =:= <<>> andalso\n\t\t\t\t((C =:= $/) orelse (C =:= $\\s) orelse (C =:= $?) orelse (C =:= $#)) ->\n\t\t\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t\t\t'Absolute URIs must include a non-empty host component. (RFC7230 2.7.1)'});\n\t\t$: when SoFar =:= <<>> ->\n\t\t\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t\t\t'Absolute URIs must include a non-empty host component. (RFC7230 2.7.1)'});\n\t\t$/ -> parse_uri_path(Rest, State, Method, SoFar, <<\"/\">>);\n\t\t$\\s -> parse_version(Rest, State, Method, SoFar, <<\"/\">>, <<>>);\n\t\t$? -> parse_uri_query(Rest, State, Method, SoFar, <<\"/\">>, <<>>);\n\t\t$# -> skip_uri_fragment(Rest, State, Method, SoFar, <<\"/\">>, <<>>);\n\t\tC -> parse_uri_authority(Rest, State, Method, <<SoFar/binary, C>>, Remaining - 1)\n\tend.\n\nparse_uri_path(<<C, Rest/bits>>, State, Method, Authority, SoFar) ->\n\tcase C of\n\t\t$\\r -> error_terminate(400, State, {connection_error, protocol_error,\n\t\t\t'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});\n\t\t$\\s -> parse_version(Rest, State, Method, Authority, SoFar, <<>>);\n\t\t$? -> parse_uri_query(Rest, State, Method, Authority, SoFar, <<>>);\n\t\t$# -> skip_uri_fragment(Rest, State, Method, Authority, SoFar, <<>>);\n\t\t_ -> parse_uri_path(Rest, State, Method, Authority, <<SoFar/binary, C>>)\n\tend.\n\nparse_uri_query(<<C, Rest/bits>>, State, M, A, P, SoFar) ->\n\tcase C of\n\t\t$\\r -> error_terminate(400, State, {connection_error, protocol_error,\n\t\t\t'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});\n\t\t$\\s -> parse_version(Rest, State, M, A, P, SoFar);\n\t\t$# -> skip_uri_fragment(Rest, State, M, A, P, SoFar);\n\t\t_ -> parse_uri_query(Rest, State, M, A, P, <<SoFar/binary, C>>)\n\tend.\n\nskip_uri_fragment(<<C, Rest/bits>>, State, M, A, P, Q) ->\n\tcase C of\n\t\t$\\r -> error_terminate(400, State, {connection_error, protocol_error,\n\t\t\t'The request-target must not be followed by a line break. (RFC7230 3.1.1)'});\n\t\t$\\s -> parse_version(Rest, State, M, A, P, Q);\n\t\t_ -> skip_uri_fragment(Rest, State, M, A, P, Q)\n\tend.\n\nparse_version(<< \"HTTP/1.1\\r\\n\", Rest/bits >>, State, M, A, P, Q) ->\n\tbefore_parse_headers(Rest, State, M, A, P, Q, 'HTTP/1.1');\nparse_version(<< \"HTTP/1.0\\r\\n\", Rest/bits >>, State, M, A, P, Q) ->\n\tbefore_parse_headers(Rest, State, M, A, P, Q, 'HTTP/1.0');\nparse_version(<< \"HTTP/1.\", _, C, _/bits >>, State, _, _, _, _) when C =:= $\\s; C =:= $\\t ->\n\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t'Whitespace is not allowed after the HTTP version. (RFC7230 3.1.1)'});\nparse_version(<< C, _/bits >>, State, _, _, _, _) when C =:= $\\s; C =:= $\\t ->\n\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t'The separator between request target and version must be a single SP. (RFC7230 3.1.1)'});\nparse_version(_, State, _, _, _, _) ->\n\terror_terminate(505, State, {connection_error, protocol_error,\n\t\t'Unsupported HTTP version. (RFC7230 2.6)'}).\n\nbefore_parse_headers(Rest, State, M, A, P, Q, V) ->\n\tparse_header(Rest, State#state{in_state=#ps_header{\n\t\tmethod=M, authority=A, path=P, qs=Q, version=V}}, #{}).\n\n%% Headers.\n\n%% We need two or more bytes in the buffer to continue.\nparse_header(Rest, State=#state{in_state=PS}, Headers) when byte_size(Rest) < 2 ->\n\t{more, State#state{buffer=Rest, in_state=PS#ps_header{headers=Headers}}};\nparse_header(<< $\\r, $\\n, Rest/bits >>, S, Headers) ->\n\trequest(Rest, S, Headers);\nparse_header(Buffer, State=#state{opts=Opts, in_state=PS}, Headers) ->\n\tMaxHeaders = maps:get(max_headers, Opts, 100),\n\tNumHeaders = maps:size(Headers),\n\tif\n\t\tNumHeaders >= MaxHeaders ->\n\t\t\terror_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t\t{connection_error, limit_reached,\n\t\t\t\t\t'The number of headers is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});\n\t\ttrue ->\n\t\t\tparse_header_colon(Buffer, State, Headers)\n\tend.\n\nparse_header_colon(Buffer, State=#state{opts=Opts, in_state=PS}, Headers) ->\n\tMaxLength = maps:get(max_header_name_length, Opts, 64),\n\tcase match_colon(Buffer, 0) of\n\t\tnomatch when byte_size(Buffer) > MaxLength ->\n\t\t\terror_terminate(431, State#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t\t{connection_error, limit_reached,\n\t\t\t\t\t'A header name is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});\n\t\tnomatch ->\n\t\t\t%% We don't have a colon but we might have an invalid header line,\n\t\t\t%% so check if we have an LF and abort with an error if we do.\n\t\t\tcase match_eol(Buffer, 0) of\n\t\t\t\tnomatch ->\n\t\t\t\t\t{more, State#state{buffer=Buffer, in_state=PS#ps_header{headers=Headers}}};\n\t\t\t\t_ ->\n\t\t\t\t\terror_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t\t\t\t{connection_error, protocol_error,\n\t\t\t\t\t\t\t'A header line is missing a colon separator. (RFC7230 3.2.4)'})\n\t\t\tend;\n\t\t_ ->\n\t\t\tparse_hd_name(Buffer, State, Headers, <<>>)\n\tend.\n\nmatch_colon(<< $:, _/bits >>, N) ->\n\tN;\nmatch_colon(<< _, Rest/bits >>, N) ->\n\tmatch_colon(Rest, N + 1);\nmatch_colon(_, _) ->\n\tnomatch.\n\nparse_hd_name(<< $:, Rest/bits >>, State, H, SoFar) ->\n\tparse_hd_before_value(Rest, State, H, SoFar);\nparse_hd_name(<< C, _/bits >>, State=#state{in_state=PS}, H, <<>>) when ?IS_WS(C) ->\n\terror_terminate(400, State#state{in_state=PS#ps_header{headers=H}},\n\t\t{connection_error, protocol_error,\n\t\t\t'Whitespace is not allowed before the header name. (RFC7230 3.2)'});\nparse_hd_name(<< C, _/bits >>, State=#state{in_state=PS}, H, _) when ?IS_WS(C) ->\n\terror_terminate(400, State#state{in_state=PS#ps_header{headers=H}},\n\t\t{connection_error, protocol_error,\n\t\t\t'Whitespace is not allowed between the header name and the colon. (RFC7230 3.2.4)'});\nparse_hd_name(<< C, Rest/bits >>, State, H, SoFar) ->\n\t?LOWER(parse_hd_name, Rest, State, H, SoFar).\n\nparse_hd_before_value(<< $\\s, Rest/bits >>, S, H, N) ->\n\tparse_hd_before_value(Rest, S, H, N);\nparse_hd_before_value(<< $\\t, Rest/bits >>, S, H, N) ->\n\tparse_hd_before_value(Rest, S, H, N);\nparse_hd_before_value(Buffer, State=#state{opts=Opts, in_state=PS}, H, N) ->\n\tMaxLength = max_header_value_length(N, Opts),\n\tcase match_eol(Buffer, 0) of\n\t\tnomatch when byte_size(Buffer) > MaxLength ->\n\t\t\terror_terminate(431, State#state{in_state=PS#ps_header{headers=H}},\n\t\t\t\t{connection_error, limit_reached,\n\t\t\t\t\t'A header value is larger than configuration allows. (RFC7230 3.2.5, RFC6585 5)'});\n\t\tnomatch ->\n\t\t\t{more, State#state{buffer=Buffer, in_state=PS#ps_header{headers=H, name=N}}};\n\t\t_ ->\n\t\t\tparse_hd_value(Buffer, State, H, N, <<>>)\n\tend.\n\nmax_header_value_length(<<\"authorization\">>, #{max_authorization_header_value_length := Max}) ->\n\tMax;\nmax_header_value_length(<<\"cookie\">>, #{max_cookie_header_value_length := Max}) ->\n\tMax;\nmax_header_value_length(_, #{max_header_value_length := Max}) ->\n\tMax;\nmax_header_value_length(_, _) ->\n\t4096.\n\nparse_hd_value(<< $\\r, $\\n, Rest/bits >>, S, Headers0, Name, SoFar) ->\n\tValue = clean_value_ws_end(SoFar, byte_size(SoFar) - 1),\n\tHeaders = case maps:get(Name, Headers0, undefined) of\n\t\tundefined -> Headers0#{Name => Value};\n\t\t%% The cookie header does not use proper HTTP header lists.\n\t\tValue0 when Name =:= <<\"cookie\">> -> Headers0#{Name => << Value0/binary, \"; \", Value/binary >>};\n\t\tValue0 -> Headers0#{Name => << Value0/binary, \", \", Value/binary >>}\n\tend,\n\tparse_header(Rest, S, Headers);\nparse_hd_value(<< C, Rest/bits >>, S, H, N, SoFar) ->\n\tparse_hd_value(Rest, S, H, N, << SoFar/binary, C >>).\n\nclean_value_ws_end(_, -1) ->\n\t<<>>;\nclean_value_ws_end(Value, N) ->\n\tcase binary:at(Value, N) of\n\t\t$\\s -> clean_value_ws_end(Value, N - 1);\n\t\t$\\t -> clean_value_ws_end(Value, N - 1);\n\t\t_ ->\n\t\t\tS = N + 1,\n\t\t\t<< Value2:S/binary, _/bits >> = Value,\n\t\t\tValue2\n\tend.\n\n-ifdef(TEST).\nclean_value_ws_end_test_() ->\n\tTests = [\n\t\t{<<>>, <<>>},\n\t\t{<<\"     \">>, <<>>},\n\t\t{<<\"text/*;q=0.3, text/html;q=0.7, text/html;level=1, \"\n\t\t\t\"text/html;level=2;q=0.4, */*;q=0.5   \\t   \\t    \">>,\n\t\t\t<<\"text/*;q=0.3, text/html;q=0.7, text/html;level=1, \"\n\t\t\t\t\"text/html;level=2;q=0.4, */*;q=0.5\">>}\n\t],\n\t[{V, fun() -> R = clean_value_ws_end(V, byte_size(V) - 1) end} || {V, R} <- Tests].\n\nhorse_clean_value_ws_end() ->\n\thorse:repeat(200000,\n\t\tclean_value_ws_end(\n\t\t\t<<\"text/*;q=0.3, text/html;q=0.7, text/html;level=1, \"\n\t\t\t\t\"text/html;level=2;q=0.4, */*;q=0.5          \">>,\n\t\t\tbyte_size(<<\"text/*;q=0.3, text/html;q=0.7, text/html;level=1, \"\n\t\t\t\t\"text/html;level=2;q=0.4, */*;q=0.5          \">>) - 1)\n\t).\n-endif.\n\nrequest(Buffer, State=#state{transport=Transport,\n\t\tin_state=PS=#ps_header{authority=Authority, version=Version}}, Headers) ->\n\tcase maps:get(<<\"host\">>, Headers, undefined) of\n\t\tundefined when Version =:= 'HTTP/1.1' ->\n\t\t\t%% @todo Might want to not close the connection on this and next one.\n\t\t\terror_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t\t{stream_error, protocol_error,\n\t\t\t\t\t'HTTP/1.1 requests must include a host header. (RFC7230 5.4)'});\n\t\tundefined ->\n\t\t\trequest(Buffer, State, Headers, <<>>, default_port(Transport:secure()));\n\t\t%% @todo When CONNECT requests come in we need to ignore the RawHost\n\t\t%% and instead use the Authority as the source of host.\n\t\tRawHost when Authority =:= undefined; Authority =:= RawHost ->\n\t\t\trequest_parse_host(Buffer, State, Headers, RawHost);\n\t\t%% RFC7230 does not explicitly ask us to reject requests\n\t\t%% that have a different authority component and host header.\n\t\t%% However it DOES ask clients to set them to the same value,\n\t\t%% so we enforce that.\n\t\t_ ->\n\t\t\terror_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t\t{stream_error, protocol_error,\n\t\t\t\t\t'The host header is different than the absolute-form authority component. (RFC7230 5.4)'})\n\tend.\n\nrequest_parse_host(Buffer, State=#state{transport=Transport, in_state=PS}, Headers, RawHost) ->\n\ttry cow_http_hd:parse_host(RawHost) of\n\t\t{Host, undefined} ->\n\t\t\trequest(Buffer, State, Headers, Host, default_port(Transport:secure()));\n\t\t{Host, Port} when Port > 0, Port =< 65535 ->\n\t\t\trequest(Buffer, State, Headers, Host, Port);\n\t\t_ ->\n\t\t\terror_terminate(400, State, {stream_error, protocol_error,\n\t\t\t\t'The port component of the absolute-form is not in the range 0..65535. (RFC7230 2.7.1)'})\n\tcatch _:_ ->\n\t\terror_terminate(400, State#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t{stream_error, protocol_error,\n\t\t\t\t'The host header is invalid. (RFC7230 5.4)'})\n\tend.\n\n-spec default_port(boolean()) -> 80 | 443.\ndefault_port(true) -> 443;\ndefault_port(_) -> 80.\n\n%% End of request parsing.\n\nrequest(Buffer, State0=#state{ref=Ref, transport=Transport, peer=Peer, sock=Sock, cert=Cert,\n\t\topts=Opts, proxy_header=ProxyHeader, in_streamid=StreamID, in_state=\n\t\t\tPS=#ps_header{method=Method, path=Path, qs=Qs, version=Version}},\n\t\tHeaders, Host, Port) ->\n\tScheme = case Transport:secure() of\n\t\ttrue -> <<\"https\">>;\n\t\tfalse -> <<\"http\">>\n\tend,\n\t{HasBody, BodyLength, TDecodeFun, TDecodeState} = case Headers of\n\t\t#{<<\"transfer-encoding\">> := _, <<\"content-length\">> := _} ->\n\t\t\terror_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t\t{stream_error, protocol_error,\n\t\t\t\t\t'The request had both transfer-encoding and content-length headers. (RFC7230 3.3.3)'});\n\t\t#{<<\"transfer-encoding\">> := TransferEncoding0} ->\n\t\t\ttry cow_http_hd:parse_transfer_encoding(TransferEncoding0) of\n\t\t\t\t[<<\"chunked\">>] ->\n\t\t\t\t\t{true, undefined, fun cow_http_te:stream_chunked/2, {0, 0}};\n\t\t\t\t_ ->\n\t\t\t\t\terror_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t\t\t\t{stream_error, protocol_error,\n\t\t\t\t\t\t\t'Cowboy only supports transfer-encoding: chunked. (RFC7230 3.3.1)'})\n\t\t\tcatch _:_ ->\n\t\t\t\terror_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t\t\t{stream_error, protocol_error,\n\t\t\t\t\t\t'The transfer-encoding header is invalid. (RFC7230 3.3.1)'})\n\t\t\tend;\n\t\t#{<<\"content-length\">> := <<\"0\">>} ->\n\t\t\t{false, 0, undefined, undefined};\n\t\t#{<<\"content-length\">> := BinLength} ->\n\t\t\tLength = try\n\t\t\t\tcow_http_hd:parse_content_length(BinLength)\n\t\t\tcatch _:_ ->\n\t\t\t\terror_terminate(400, State0#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t\t\t{stream_error, protocol_error,\n\t\t\t\t\t\t'The content-length header is invalid. (RFC7230 3.3.2)'})\n\t\t\tend,\n\t\t\t{true, Length, fun cow_http_te:stream_identity/2, {0, Length}};\n\t\t_ ->\n\t\t\t{false, 0, undefined, undefined}\n\tend,\n\tReq0 = #{\n\t\tref => Ref,\n\t\tpid => self(),\n\t\tstreamid => StreamID,\n\t\tpeer => Peer,\n\t\tsock => Sock,\n\t\tcert => Cert,\n\t\tmethod => Method,\n\t\tscheme => Scheme,\n\t\thost => Host,\n\t\tport => Port,\n\t\tpath => Path,\n\t\tqs => Qs,\n\t\tversion => Version,\n\t\t%% We are transparently taking care of transfer-encodings so\n\t\t%% the user code has no need to know about it.\n\t\theaders => maps:remove(<<\"transfer-encoding\">>, Headers),\n\t\thas_body => HasBody,\n\t\tbody_length => BodyLength\n\t},\n\t%% We add the PROXY header information if any.\n\tReq = case ProxyHeader of\n\t\tundefined -> Req0;\n\t\t_ -> Req0#{proxy_header => ProxyHeader}\n\tend,\n\tcase is_http2_upgrade(Headers, Version, Opts) of\n\t\tfalse ->\n\t\t\tState = case HasBody of\n\t\t\t\ttrue ->\n\t\t\t\t\tState0#state{in_state=#ps_body{\n\t\t\t\t\t\tlength = BodyLength,\n\t\t\t\t\t\ttransfer_decode_fun = TDecodeFun,\n\t\t\t\t\t\ttransfer_decode_state = TDecodeState\n\t\t\t\t\t}};\n\t\t\t\tfalse ->\n\t\t\t\t\tState0#state{in_streamid=StreamID + 1, in_state=#ps_request_line{}}\n\t\t\tend,\n\t\t\t{request, Req, State#state{buffer=Buffer}};\n\t\t{true, HTTP2Settings} ->\n\t\t\t%% We save the headers in case the upgrade will fail\n\t\t\t%% and we need to pass them to cowboy_stream:early_error.\n\t\t\thttp2_upgrade(State0#state{in_state=PS#ps_header{headers=Headers}},\n\t\t\t\tBuffer, HTTP2Settings, Req)\n\tend.\n\n%% HTTP/2 upgrade.\n\nis_http2_upgrade(#{<<\"connection\">> := Conn, <<\"upgrade\">> := Upgrade,\n\t\t<<\"http2-settings\">> := HTTP2Settings}, 'HTTP/1.1', Opts) ->\n\tConns = cow_http_hd:parse_connection(Conn),\n\tcase lists:member(<<\"upgrade\">>, Conns)\n\t\t\tandalso lists:member(<<\"http2-settings\">>, Conns)\n\t\t\tandalso lists:member(http2, maps:get(protocols, Opts, [http2, http])) of\n\t\ttrue ->\n\t\t\tProtocols = cow_http_hd:parse_upgrade(Upgrade),\n\t\t\tcase lists:member(<<\"h2c\">>, Protocols) of\n\t\t\t\ttrue ->\n\t\t\t\t\t{true, HTTP2Settings};\n\t\t\t\tfalse ->\n\t\t\t\t\tfalse\n\t\t\tend;\n\t\t_ ->\n\t\t\tfalse\n\tend;\nis_http2_upgrade(_, _, _) ->\n\tfalse.\n\n%% Prior knowledge upgrade, without an HTTP/1.1 request.\nhttp2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,\n\t\tproxy_header=ProxyHeader, peer=Peer, sock=Sock, cert=Cert}, Buffer) ->\n\tcase Transport:secure() of\n\t\tfalse ->\n\t\t\t_ = cancel_timeout(State),\n\t\t\tcowboy_http2:init(Parent, Ref, Socket, Transport, ProxyHeader,\n\t\t\t\topts_for_upgrade(State), Peer, Sock, Cert, Buffer);\n\t\ttrue ->\n\t\t\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t\t\t'Clients that support HTTP/2 over TLS MUST use ALPN. (RFC7540 3.4)'})\n\tend.\n\n%% Upgrade via an HTTP/1.1 request.\nhttp2_upgrade(State=#state{parent=Parent, ref=Ref, socket=Socket, transport=Transport,\n\t\tproxy_header=ProxyHeader, peer=Peer, sock=Sock, cert=Cert},\n\t\tBuffer, HTTP2Settings, Req) ->\n\tcase Transport:secure() of\n\t\tfalse ->\n\t\t\t%% @todo\n\t\t\t%% However if the client sent a body, we need to read the body in full\n\t\t\t%% and if we can't do that, return a 413 response. Some options are in order.\n\t\t\t%% Always half-closed stream coming from this side.\n\t\t\ttry cow_http_hd:parse_http2_settings(HTTP2Settings) of\n\t\t\t\tSettings ->\n\t\t\t\t\t_ = cancel_timeout(State),\n\t\t\t\t\tcowboy_http2:init(Parent, Ref, Socket, Transport, ProxyHeader,\n\t\t\t\t\t\topts_for_upgrade(State), Peer, Sock, Cert, Buffer, Settings, Req)\n\t\t\tcatch _:_ ->\n\t\t\t\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t\t\t\t'The HTTP2-Settings header must contain a base64 SETTINGS payload. (RFC7540 3.2, RFC7540 3.2.1)'})\n\t\t\tend;\n\t\ttrue ->\n\t\t\terror_terminate(400, State, {connection_error, protocol_error,\n\t\t\t\t'Clients that support HTTP/2 over TLS MUST use ALPN. (RFC7540 3.4)'})\n\tend.\n\nopts_for_upgrade(#state{opts=Opts, dynamic_buffer_size=false}) ->\n\tOpts;\nopts_for_upgrade(#state{opts=Opts, dynamic_buffer_size=Size,\n\t\tdynamic_buffer_moving_average=MovingAvg}) ->\n\tOpts#{\n\t\tdynamic_buffer_initial_average => MovingAvg,\n\t\tdynamic_buffer_initial_size => Size\n\t}.\n\n%% Request body parsing.\n\nparse_body(Buffer, State=#state{in_streamid=StreamID, in_state=\n\t\tPS=#ps_body{received=Received, transfer_decode_fun=TDecode,\n\t\t\ttransfer_decode_state=TState0}}) ->\n\t%% @todo Proper trailers.\n\ttry TDecode(Buffer, TState0) of\n\t\tmore ->\n\t\t\t{more, State#state{buffer=Buffer}};\n\t\t{more, Data, TState} ->\n\t\t\t{data, StreamID, nofin, Data, State#state{buffer= <<>>,\n\t\t\t\tin_state=PS#ps_body{received=Received + byte_size(Data),\n\t\t\t\t\ttransfer_decode_state=TState}}};\n\t\t{more, Data, _Length, TState} when is_integer(_Length) ->\n\t\t\t{data, StreamID, nofin, Data, State#state{buffer= <<>>,\n\t\t\t\tin_state=PS#ps_body{received=Received + byte_size(Data),\n\t\t\t\t\ttransfer_decode_state=TState}}};\n\t\t{more, Data, Rest, TState} ->\n\t\t\t{data, StreamID, nofin, Data, State#state{buffer=Rest,\n\t\t\t\tin_state=PS#ps_body{received=Received + byte_size(Data),\n\t\t\t\t\ttransfer_decode_state=TState}}};\n\t\t{done, _HasTrailers, Rest} ->\n\t\t\t{data, StreamID, fin, <<>>,\n\t\t\t\tState#state{buffer=Rest, in_streamid=StreamID + 1, in_state=#ps_request_line{}}};\n\t\t{done, Data, _HasTrailers, Rest} ->\n\t\t\t{data, StreamID, fin, Data,\n\t\t\t\tState#state{buffer=Rest, in_streamid=StreamID + 1, in_state=#ps_request_line{}}}\n\tcatch _:_ ->\n\t\tReason = {connection_error, protocol_error,\n\t\t\t'Failure to decode the content. (RFC7230 4)'},\n\t\tterminate(stream_terminate(State, StreamID, Reason), Reason)\n\tend.\n\n%% Message handling.\n\ndown(State=#state{opts=Opts, children=Children0}, Pid, Msg) ->\n\tcase cowboy_children:down(Children0, Pid) of\n\t\t%% The stream was terminated already.\n\t\t{ok, undefined, Children} ->\n\t\t\tState#state{children=Children};\n\t\t%% The stream is still running.\n\t\t{ok, StreamID, Children} ->\n\t\t\tinfo(State#state{children=Children}, StreamID, Msg);\n\t\t%% The process was unknown.\n\t\terror ->\n\t\t\tcowboy:log(warning, \"Received EXIT signal ~p for unknown process ~p.~n\",\n\t\t\t\t[Msg, Pid], Opts),\n\t\t\tState\n\tend.\n\ninfo(State=#state{opts=Opts, streams=Streams0}, StreamID, Msg) ->\n\tcase lists:keyfind(StreamID, #stream.id, Streams0) of\n\t\tStream = #stream{state=StreamState0} ->\n\t\t\ttry cowboy_stream:info(StreamID, Msg, StreamState0) of\n\t\t\t\t{Commands, StreamState} ->\n\t\t\t\t\tStreams = lists:keyreplace(StreamID, #stream.id, Streams0,\n\t\t\t\t\t\tStream#stream{state=StreamState}),\n\t\t\t\t\tcommands(State#state{streams=Streams}, StreamID, Commands)\n\t\t\tcatch Class:Exception:Stacktrace ->\n\t\t\t\tcowboy:log(cowboy_stream:make_error_log(info,\n\t\t\t\t\t[StreamID, Msg, StreamState0],\n\t\t\t\t\tClass, Exception, Stacktrace), Opts),\n\t\t\t\tstream_terminate(State, StreamID, {internal_error, {Class, Exception},\n\t\t\t\t\t'Unhandled exception in cowboy_stream:info/3.'})\n\t\t\tend;\n\t\tfalse ->\n\t\t\tcowboy:log(warning, \"Received message ~p for unknown stream ~p.~n\",\n\t\t\t\t[Msg, StreamID], Opts),\n\t\t\tState\n\tend.\n\n%% Commands.\n%%\n%% The order in which the commands are given matters. Cowboy may\n%% stop processing commands after the 'stop' command or when an\n%% error occurred, such as a socket error. Critical commands such\n%% as 'spawn' should always be given first.\n\ncommands(State, _, []) ->\n\tState;\n%% Supervise a child process.\ncommands(State=#state{children=Children}, StreamID, [{spawn, Pid, Shutdown}|Tail]) ->\n\tcommands(State#state{children=cowboy_children:up(Children, Pid, StreamID, Shutdown)},\n\t\tStreamID, Tail);\n%% Error handling.\ncommands(State, StreamID, [Error = {internal_error, _, _}|Tail]) ->\n\tcommands(stream_terminate(State, StreamID, Error), StreamID, Tail);\n%% Commands for a stream currently inactive.\ncommands(State=#state{out_streamid=Current, streams=Streams0}, StreamID, Commands)\n\t\twhen Current =/= StreamID ->\n\n\t%% @todo We still want to handle some commands...\n\n\tStream = #stream{queue=Queue} = lists:keyfind(StreamID, #stream.id, Streams0),\n\tStreams = lists:keyreplace(StreamID, #stream.id, Streams0,\n\t\tStream#stream{queue=Queue ++ Commands}),\n\tState#state{streams=Streams};\n%% When we have finished reading the request body, do nothing.\ncommands(State=#state{flow=infinity}, StreamID, [{flow, _}|Tail]) ->\n\tcommands(State, StreamID, Tail);\n%% Read the request body.\ncommands(State0=#state{flow=Flow0}, StreamID, [{flow, Size}|Tail]) ->\n\t%% We must read *at least* Size of data otherwise functions\n\t%% like cowboy_req:read_body/1,2 will wait indefinitely.\n\tFlow = if\n\t\tFlow0 < 0 -> Size;\n\t\ttrue -> Flow0 + Size\n\tend,\n\t%% Reenable active mode if necessary.\n\tState = if\n\t\tFlow0 =< 0, Flow > 0 ->\n\t\t\tactive(State0);\n\t\ttrue ->\n\t\t\tState0\n\tend,\n\tcommands(State#state{flow=Flow}, StreamID, Tail);\n%% Error responses are sent only if a response wasn't sent already.\ncommands(State=#state{out_state=wait, out_streamid=StreamID}, StreamID,\n\t\t[{error_response, Status, Headers0, Body}|Tail]) ->\n\t%% We close the connection when the error response is 408, as it\n\t%% indicates a timeout and the RFC recommends that we stop here. (RFC7231 6.5.7)\n\tHeaders = case Status of\n\t\t408 -> Headers0#{<<\"connection\">> => <<\"close\">>};\n\t\t<<\"408\", _/bits>> -> Headers0#{<<\"connection\">> => <<\"close\">>};\n\t\t_ -> Headers0\n\tend,\n\tcommands(State, StreamID, [{response, Status, Headers, Body}|Tail]);\ncommands(State, StreamID, [{error_response, _, _, _}|Tail]) ->\n\tcommands(State, StreamID, Tail);\n%% Send an informational response.\ncommands(State0=#state{socket=Socket, transport=Transport, out_state=wait, streams=Streams},\n\t\tStreamID, [{inform, StatusCode, Headers}|Tail]) ->\n\t%% @todo I'm pretty sure the last stream in the list is the one we want\n\t%% considering all others are queued.\n\t#stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams),\n\t_ = case Version of\n\t\t'HTTP/1.1' ->\n\t\t\tok = maybe_socket_error(State0, Transport:send(Socket,\n\t\t\t\tcow_http:response(StatusCode, 'HTTP/1.1', headers_to_list(Headers))));\n\t\t%% Do not send informational responses to HTTP/1.0 clients. (RFC7231 6.2)\n\t\t'HTTP/1.0' ->\n\t\t\tok\n\tend,\n\tState = maybe_reset_idle_timeout(State0),\n\tcommands(State, StreamID, Tail);\n%% Send a full response.\n%%\n%% @todo Kill the stream if it sent a response when one has already been sent.\n%% @todo Keep IsFin in the state.\n%% @todo Same two things above apply to DATA, possibly promise too.\ncommands(State0=#state{socket=Socket, transport=Transport, out_state=wait, streams=Streams}, StreamID,\n\t\t[{response, StatusCode, Headers0, Body}|Tail]) ->\n\t%% @todo I'm pretty sure the last stream in the list is the one we want\n\t%% considering all others are queued.\n\t#stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams),\n\t{State1, Headers} = connection(State0, Headers0, StreamID, Version),\n\tState2 = State1#state{out_state=done},\n\t%% @todo Ensure content-length is set. 204 must never have content-length set.\n\tResponse = cow_http:response(StatusCode, 'HTTP/1.1', headers_to_list(Headers)),\n\t%% @todo 204 and 304 responses must not include a response body. (RFC7230 3.3.1, RFC7230 3.3.2)\n\tcase Body of\n\t\t{sendfile, _, _, _} ->\n\t\t\tok = maybe_socket_error(State2, Transport:send(Socket, Response)),\n\t\t\tsendfile(State2, Body);\n\t\t_ ->\n\t\t\tok = maybe_socket_error(State2, Transport:send(Socket, [Response, Body]))\n\tend,\n\tState = maybe_reset_idle_timeout(State2),\n\tcommands(State, StreamID, Tail);\n%% Send response headers and initiate chunked encoding or streaming.\ncommands(State0=#state{socket=Socket, transport=Transport,\n\t\topts=Opts, overriden_opts=Override, streams=Streams0, out_state=OutState},\n\t\tStreamID, [{headers, StatusCode, Headers0}|Tail]) ->\n\t%% @todo Same as above (about the last stream in the list).\n\tStream = #stream{version=Version} = lists:keyfind(StreamID, #stream.id, Streams0),\n\tStatus = cow_http:status_to_integer(StatusCode),\n\tContentLength = maps:get(<<\"content-length\">>, Headers0, undefined),\n\t%% Chunked transfer-encoding can be disabled on a per-request basis.\n\tChunked = case Override of\n\t\t#{chunked := Chunked0} -> Chunked0;\n\t\t_ -> maps:get(chunked, Opts, true)\n\tend,\n\t{State1, Headers1} = case {Status, ContentLength, Version} of\n\t\t{204, _, 'HTTP/1.1'} ->\n\t\t\t{State0#state{out_state=done}, Headers0};\n\t\t{304, _, 'HTTP/1.1'} ->\n\t\t\t{State0#state{out_state=done}, Headers0};\n\t\t{_, undefined, 'HTTP/1.1'} when Chunked ->\n\t\t\t{State0#state{out_state=chunked}, Headers0#{<<\"transfer-encoding\">> => <<\"chunked\">>}};\n\t\t%% Close the connection after streaming without content-length\n\t\t%% to all HTTP/1.0 clients and to HTTP/1.1 clients when chunked is disabled.\n\t\t{_, undefined, _} ->\n\t\t\t{State0#state{out_state=streaming, last_streamid=StreamID}, Headers0};\n\t\t%% Stream the response body without chunked transfer-encoding.\n\t\t_ ->\n\t\t\tExpectedSize = cow_http_hd:parse_content_length(ContentLength),\n\t\t\tStreams = lists:keyreplace(StreamID, #stream.id, Streams0,\n\t\t\t\tStream#stream{local_expected_size=ExpectedSize}),\n\t\t\t{State0#state{out_state=streaming, streams=Streams}, Headers0}\n\tend,\n\tHeaders2 = case stream_te(OutState, Stream) of\n\t\ttrailers -> Headers1;\n\t\t_ -> maps:remove(<<\"trailer\">>, Headers1)\n\tend,\n\t{State2, Headers} = connection(State1, Headers2, StreamID, Version),\n\tok = maybe_socket_error(State2, Transport:send(Socket,\n\t\tcow_http:response(StatusCode, 'HTTP/1.1', headers_to_list(Headers)))),\n\tState = maybe_reset_idle_timeout(State2),\n\tcommands(State, StreamID, Tail);\n%% Send a response body chunk.\n%% @todo We need to kill the stream if it tries to send data before headers.\ncommands(State0=#state{socket=Socket, transport=Transport, streams=Streams0, out_state=OutState},\n\t\tStreamID, [{data, IsFin, Data}|Tail]) ->\n\t%% Do not send anything when the user asks to send an empty\n\t%% data frame, as that would break the protocol.\n\tSize = case Data of\n\t\t{sendfile, _, B, _} -> B;\n\t\t_ -> iolist_size(Data)\n\tend,\n\t%% Depending on the current state we may need to send nothing,\n\t%% the last chunk, chunked data with/without the last chunk,\n\t%% or just the data as-is.\n\tStream = case lists:keyfind(StreamID, #stream.id, Streams0) of\n\t\tStream0=#stream{method= <<\"HEAD\">>} ->\n\t\t\tStream0;\n\t\tStream0 when Size =:= 0, IsFin =:= fin, OutState =:= chunked ->\n\t\t\tok = maybe_socket_error(State0,\n\t\t\t\tTransport:send(Socket, <<\"0\\r\\n\\r\\n\">>)),\n\t\t\tStream0;\n\t\tStream0 when Size =:= 0 ->\n\t\t\tStream0;\n\t\tStream0 when is_tuple(Data), OutState =:= chunked ->\n\t\t\tok = maybe_socket_error(State0,\n\t\t\t\tTransport:send(Socket, [integer_to_binary(Size, 16), <<\"\\r\\n\">>])),\n\t\t\tsendfile(State0, Data),\n\t\t\tok = maybe_socket_error(State0,\n\t\t\t\tTransport:send(Socket,\n\t\t\t\t\tcase IsFin of\n\t\t\t\t\t\tfin -> <<\"\\r\\n0\\r\\n\\r\\n\">>;\n\t\t\t\t\t\tnofin -> <<\"\\r\\n\">>\n\t\t\t\t\tend)\n\t\t\t),\n\t\t\tStream0;\n\t\tStream0 when OutState =:= chunked ->\n\t\t\tok = maybe_socket_error(State0,\n\t\t\t\tTransport:send(Socket, [\n\t\t\t\t\tinteger_to_binary(Size, 16), <<\"\\r\\n\">>, Data,\n\t\t\t\t\tcase IsFin of\n\t\t\t\t\t\tfin -> <<\"\\r\\n0\\r\\n\\r\\n\">>;\n\t\t\t\t\t\tnofin -> <<\"\\r\\n\">>\n\t\t\t\t\tend\n\t\t\t\t])\n\t\t\t),\n\t\t\tStream0;\n\t\tStream0 when OutState =:= streaming ->\n\t\t\t#stream{local_sent_size=SentSize0, local_expected_size=ExpectedSize} = Stream0,\n\t\t\tSentSize = SentSize0 + Size,\n\t\t\tif\n\t\t\t\t%% ExpectedSize may be undefined, which is > any integer value.\n\t\t\t\tSentSize > ExpectedSize ->\n\t\t\t\t\tterminate(State0, response_body_too_large);\n\t\t\t\tis_tuple(Data) ->\n\t\t\t\t\tsendfile(State0, Data);\n\t\t\t\ttrue ->\n\t\t\t\t\tok = maybe_socket_error(State0, Transport:send(Socket, Data))\n\t\t\tend,\n\t\t\tStream0#stream{local_sent_size=SentSize}\n\tend,\n\tState1 = case IsFin of\n\t\tfin -> State0#state{out_state=done};\n\t\tnofin -> State0\n\tend,\n\tState = maybe_reset_idle_timeout(State1),\n\tStreams = lists:keyreplace(StreamID, #stream.id, Streams0, Stream),\n\tcommands(State#state{streams=Streams}, StreamID, Tail);\ncommands(State0=#state{socket=Socket, transport=Transport, streams=Streams, out_state=OutState},\n\t\tStreamID, [{trailers, Trailers}|Tail]) ->\n\tcase stream_te(OutState, lists:keyfind(StreamID, #stream.id, Streams)) of\n\t\ttrailers ->\n\t\t\tok = maybe_socket_error(State0,\n\t\t\t\tTransport:send(Socket, [\n\t\t\t\t\t<<\"0\\r\\n\">>,\n\t\t\t\t\tcow_http:headers(maps:to_list(Trailers)),\n\t\t\t\t\t<<\"\\r\\n\">>\n\t\t\t\t])\n\t\t\t);\n\t\tno_trailers ->\n\t\t\tok = maybe_socket_error(State0,\n\t\t\t\tTransport:send(Socket, <<\"0\\r\\n\\r\\n\">>));\n\t\tnot_chunked ->\n\t\t\tok\n\tend,\n\tState = maybe_reset_idle_timeout(State0#state{out_state=done}),\n\tcommands(State, StreamID, Tail);\n%% Protocol takeover.\ncommands(State0=#state{ref=Ref, parent=Parent, socket=Socket, transport=Transport,\n\t\tout_state=OutState, buffer=Buffer, children=Children}, StreamID,\n\t\t[{switch_protocol, Headers, Protocol, InitialState}|_Tail]) ->\n\t%% @todo If there's streams opened after this one, fail instead of 101.\n\tState1 = cancel_timeout(State0),\n\t%% Before we send the 101 response we need to stop receiving data\n\t%% from the socket, otherwise the data might be receive before the\n\t%% call to flush/0 and we end up inadvertently dropping a packet.\n\t%%\n\t%% @todo Handle cases where the request came with a body. We need\n\t%% to process or skip the body before the upgrade can be completed.\n\tState = passive(State1),\n\t%% Send a 101 response if necessary, then terminate the stream.\n\t#state{streams=Streams} = case OutState of\n\t\twait -> info(State, StreamID, {inform, 101, Headers});\n\t\t_ -> State\n\tend,\n\t#stream{state=StreamState} = lists:keyfind(StreamID, #stream.id, Streams),\n\tstream_call_terminate(StreamID, switch_protocol, StreamState, State),\n\t%% Terminate children processes and flush any remaining messages from the mailbox.\n\tcowboy_children:terminate(Children),\n\tflush(Parent),\n\t%% Turn off the trap_exit process flag\n\t%% since this process will no longer be a supervisor.\n\tprocess_flag(trap_exit, false),\n\tProtocol:takeover(Parent, Ref, Socket, Transport,\n\t\topts_for_upgrade(State), Buffer, InitialState);\n%% Set options dynamically.\ncommands(State0, StreamID, [{set_options, SetOpts}|Tail]) ->\n\tState = maps:fold(fun\n\t\t(chunked, Chunked, StateF=#state{overriden_opts=Opts}) ->\n\t\t\tStateF#state{overriden_opts=Opts#{chunked => Chunked}};\n\t\t(idle_timeout, IdleTimeout, StateF=#state{overriden_opts=Opts}) ->\n\t\t\tset_timeout(StateF#state{overriden_opts=Opts#{idle_timeout => IdleTimeout}},\n\t\t\t\tidle_timeout);\n\t\t(_, _, StateF) ->\n\t\t\tStateF\n\tend, State0, SetOpts),\n\tcommands(State, StreamID, Tail);\n%% Stream shutdown.\ncommands(State, StreamID, [stop|Tail]) ->\n\t%% @todo Do we want to run the commands after a stop?\n\t%% @todo We currently wait for the stop command before we\n\t%% continue with the next request/response. In theory, if\n\t%% the request body was read fully and the response body\n\t%% was sent fully we should be able to start working on\n\t%% the next request concurrently. This can be done as a\n\t%% future optimization.\n\tmaybe_terminate(State, StreamID, Tail);\n%% Log event.\ncommands(State=#state{opts=Opts}, StreamID, [Log={log, _, _, _}|Tail]) ->\n\tcowboy:log(Log, Opts),\n\tcommands(State, StreamID, Tail);\n%% HTTP/1.1 does not support push; ignore.\ncommands(State, StreamID, [{push, _, _, _, _, _, _, _}|Tail]) ->\n\tcommands(State, StreamID, Tail).\n\n%% The set-cookie header is special; we can only send one cookie per header.\nheaders_to_list(Headers0=#{<<\"set-cookie\">> := SetCookies}) ->\n\tHeaders1 = maps:to_list(maps:remove(<<\"set-cookie\">>, Headers0)),\n\tHeaders1 ++ [{<<\"set-cookie\">>, Value} || Value <- SetCookies];\nheaders_to_list(Headers) ->\n\tmaps:to_list(Headers).\n\n%% We wrap the sendfile call into a try/catch because on OTP-20\n%% and earlier a few different crashes could occur for sockets\n%% that were closing or closed. For example a badarg in\n%% erlang:port_get_data(#Port<...>) or a badmatch like\n%% {{badmatch,{error,einval}},[{prim_file,sendfile,8,[]}...\n%%\n%% OTP-21 uses a NIF instead of a port so the implementation\n%% and behavior has dramatically changed and it is unclear\n%% whether it will be necessary in the future.\n%%\n%% This try/catch prevents some noisy logs to be written\n%% when these errors occur.\nsendfile(State=#state{socket=Socket, transport=Transport, opts=Opts},\n\t\t{sendfile, Offset, Bytes, Path}) ->\n\ttry\n\t\t%% When sendfile is disabled we explicitly use the fallback.\n\t\t{ok, _} = maybe_socket_error(State,\n\t\t\tcase maps:get(sendfile, Opts, true) of\n\t\t\t\ttrue -> Transport:sendfile(Socket, Path, Offset, Bytes);\n\t\t\t\tfalse -> ranch_transport:sendfile(Transport, Socket, Path, Offset, Bytes, [])\n\t\t\tend\n\t\t),\n\t\tok\n\tcatch _:_ ->\n\t\tterminate(State, {socket_error, sendfile_crash,\n\t\t\t'An error occurred when using the sendfile function.'})\n\tend.\n\n%% Flush messages specific to cowboy_http before handing over the\n%% connection to another protocol.\nflush(Parent) ->\n\treceive\n\t\t{timeout, _, _} ->\n\t\t\tflush(Parent);\n\t\t{{Pid, _}, _} when Pid =:= self() ->\n\t\t\tflush(Parent);\n\t\t{'EXIT', Pid, _} when Pid =/= Parent ->\n\t\t\tflush(Parent)\n\tafter 0 ->\n\t\tok\n\tend.\n\n%% @todo In these cases I'm not sure if we should continue processing commands.\nmaybe_terminate(State=#state{last_streamid=StreamID}, StreamID, _Tail) ->\n\tterminate(stream_terminate(State, StreamID, normal), normal); %% @todo Reason ok?\nmaybe_terminate(State, StreamID, _Tail) ->\n\tstream_terminate(State, StreamID, normal).\n\nstream_terminate(State0=#state{opts=Opts, in_streamid=InStreamID, in_state=InState,\n\t\tout_streamid=OutStreamID, out_state=OutState, streams=Streams0,\n\t\tchildren=Children0}, StreamID, Reason) ->\n\t#stream{version=Version, local_expected_size=ExpectedSize, local_sent_size=SentSize}\n\t\t= lists:keyfind(StreamID, #stream.id, Streams0),\n\t%% Send a response or terminate chunks depending on the current output state.\n\tState1 = #state{streams=Streams1} = case OutState of\n\t\twait when element(1, Reason) =:= internal_error ->\n\t\t\tinfo(State0, StreamID, {response, 500, #{<<\"content-length\">> => <<\"0\">>}, <<>>});\n\t\twait when element(1, Reason) =:= connection_error ->\n\t\t\tinfo(State0, StreamID, {response, 400, #{<<\"content-length\">> => <<\"0\">>}, <<>>});\n\t\twait ->\n\t\t\tinfo(State0, StreamID, {response, 204, #{}, <<>>});\n\t\tchunked when Version =:= 'HTTP/1.1' ->\n\t\t\tinfo(State0, StreamID, {data, fin, <<>>});\n\t\tstreaming when SentSize < ExpectedSize ->\n\t\t\tterminate(State0, response_body_too_small);\n\t\t_ -> %% done or Version =:= 'HTTP/1.0'\n\t\t\tState0\n\tend,\n\t%% Stop the stream, shutdown children and reset overriden options.\n\t{value, #stream{state=StreamState}, Streams}\n\t\t= lists:keytake(StreamID, #stream.id, Streams1),\n\tstream_call_terminate(StreamID, Reason, StreamState, State1),\n\tChildren = cowboy_children:shutdown(Children0, StreamID),\n\tState = State1#state{overriden_opts=#{}, streams=Streams, children=Children},\n\t%% We want to drop the connection if the body was not read fully\n\t%% and we don't know its length or more remains to be read than\n\t%% configuration allows.\n\tMaxSkipBodyLength = maps:get(max_skip_body_length, Opts, 1000000),\n\tcase InState of\n\t\t#ps_body{length=undefined}\n\t\t\t\twhen InStreamID =:= OutStreamID ->\n\t\t\tterminate(State, skip_body_unknown_length);\n\t\t#ps_body{length=Len, received=Received}\n\t\t\t\twhen InStreamID =:= OutStreamID, Received + MaxSkipBodyLength < Len ->\n\t\t\tterminate(State, skip_body_too_large);\n\t\t#ps_body{} when InStreamID =:= OutStreamID ->\n\t\t\tstream_next(State#state{flow=infinity});\n\t\t_ ->\n\t\t\tstream_next(State)\n\tend.\n\nstream_next(State0=#state{opts=Opts, active=Active, out_streamid=OutStreamID, streams=Streams}) ->\n\t%% Enable active mode again if it was disabled.\n\tState1 = case Active of\n\t\ttrue -> State0;\n\t\tfalse -> active(State0)\n\tend,\n\tNextOutStreamID = OutStreamID + 1,\n\tcase lists:keyfind(NextOutStreamID, #stream.id, Streams) of\n\t\tfalse ->\n\t\t\tState = State1#state{out_streamid=NextOutStreamID, out_state=wait},\n\t\t\t%% There are no streams remaining. We therefore can\n\t\t\t%% and want to switch back to the request_timeout.\n\t\t\tset_timeout(State, request_timeout);\n\t\t#stream{queue=Commands} ->\n\t\t\t%% @todo Remove queue from the stream.\n\t\t\t%% We set the flow to the initial flow size even though\n\t\t\t%% we might have sent some data through already due to pipelining.\n\t\t\tFlow = maps:get(initial_stream_flow_size, Opts, 65535),\n\t\t\tcommands(State1#state{flow=Flow, out_streamid=NextOutStreamID, out_state=wait},\n\t\t\t\tNextOutStreamID, Commands)\n\tend.\n\nstream_call_terminate(StreamID, Reason, StreamState, #state{opts=Opts}) ->\n\ttry\n\t\tcowboy_stream:terminate(StreamID, Reason, StreamState)\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(terminate,\n\t\t\t[StreamID, Reason, StreamState],\n\t\t\tClass, Exception, Stacktrace), Opts)\n\tend.\n\nmaybe_req_close(#state{opts=#{http10_keepalive := false}}, _, 'HTTP/1.0') ->\n\tclose;\nmaybe_req_close(_, #{<<\"connection\">> := Conn}, 'HTTP/1.0') ->\n\ttry cow_http_hd:parse_connection(Conn) of\n\t\tConns ->\n\t\t\tcase lists:member(<<\"keep-alive\">>, Conns) of\n\t\t\t\ttrue -> keepalive;\n\t\t\t\tfalse -> close\n\t\t\tend\n\tcatch _:_ ->\n\t\tbad_connection_header\n\tend;\nmaybe_req_close(_, _, 'HTTP/1.0') ->\n\tclose;\nmaybe_req_close(_, #{<<\"connection\">> := Conn}, 'HTTP/1.1') ->\n\ttry connection_hd_is_close(Conn) of\n\t\ttrue -> close;\n\t\tfalse -> keepalive\n\tcatch _:_ ->\n\t\tbad_connection_header\n\tend;\nmaybe_req_close(_, _, _) ->\n\tkeepalive.\n\nconnection(State=#state{last_streamid=StreamID}, Headers=#{<<\"connection\">> := Conn}, StreamID, _) ->\n\tcase connection_hd_is_close(Conn) of\n\t\ttrue -> {State, Headers};\n\t\t%% @todo Here we need to remove keep-alive and add close, not just add close.\n\t\tfalse -> {State, Headers#{<<\"connection\">> => [<<\"close, \">>, Conn]}}\n\tend;\nconnection(State=#state{last_streamid=StreamID}, Headers, StreamID, _) ->\n\t{State, Headers#{<<\"connection\">> => <<\"close\">>}};\nconnection(State, Headers=#{<<\"connection\">> := Conn}, StreamID, _) ->\n\tcase connection_hd_is_close(Conn) of\n\t\ttrue -> {State#state{last_streamid=StreamID}, Headers};\n\t\t%% @todo Here we need to set keep-alive only if it wasn't set before.\n\t\tfalse -> {State, Headers}\n\tend;\nconnection(State, Headers, _, 'HTTP/1.0') ->\n\t{State, Headers#{<<\"connection\">> => <<\"keep-alive\">>}};\nconnection(State, Headers, _, _) ->\n\t{State, Headers}.\n\nconnection_hd_is_close(Conn) ->\n\tConns = cow_http_hd:parse_connection(iolist_to_binary(Conn)),\n\tlists:member(<<\"close\">>, Conns).\n\nstream_te(streaming, _) ->\n\tnot_chunked;\n%% No TE header was sent.\nstream_te(_, #stream{te=undefined}) ->\n\tno_trailers;\nstream_te(_, #stream{te=TE0}) ->\n\ttry cow_http_hd:parse_te(TE0) of\n\t\t{TE1, _} -> TE1\n\tcatch _:_ ->\n\t\t%% If we can't parse the TE header, assume we can't send trailers.\n\t\tno_trailers\n\tend.\n\n%% This function is only called when an error occurs on a new stream.\n-spec error_terminate(cowboy:http_status(), #state{}, _) -> no_return().\nerror_terminate(StatusCode, State=#state{ref=Ref, peer=Peer, in_state=StreamState}, Reason) ->\n\tPartialReq = case StreamState of\n\t\t#ps_request_line{} -> #{\n\t\t\tref => Ref,\n\t\t\tpeer => Peer\n\t\t};\n\t\t#ps_header{method=Method, path=Path, qs=Qs,\n\t\t\t\tversion=Version, headers=ReqHeaders} -> #{\n\t\t\tref => Ref,\n\t\t\tpeer => Peer,\n\t\t\tmethod => Method,\n\t\t\tpath => Path,\n\t\t\tqs => Qs,\n\t\t\tversion => Version,\n\t\t\theaders => case ReqHeaders of\n\t\t\t\tundefined -> #{};\n\t\t\t\t_ -> ReqHeaders\n\t\t\tend\n\t\t}\n\tend,\n\tearly_error(StatusCode, State, Reason, PartialReq, #{<<\"connection\">> => <<\"close\">>}),\n\tterminate(State, Reason).\n\nearly_error(StatusCode, State, Reason, PartialReq) ->\n\tearly_error(StatusCode, State, Reason, PartialReq, #{}).\n\nearly_error(StatusCode0, State=#state{socket=Socket, transport=Transport,\n\t\topts=Opts, in_streamid=StreamID}, Reason, PartialReq, RespHeaders0) ->\n\tRespHeaders1 = RespHeaders0#{<<\"content-length\">> => <<\"0\">>},\n\tResp = {response, StatusCode0, RespHeaders1, <<>>},\n\ttry cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts) of\n\t\t{response, StatusCode, RespHeaders, RespBody} ->\n\t\t\tok = maybe_socket_error(State,\n\t\t\t\tTransport:send(Socket, [\n\t\t\t\t\tcow_http:response(StatusCode, 'HTTP/1.1', maps:to_list(RespHeaders)),\n\t\t\t\t\t%% @todo We shouldn't send the body when the method is HEAD.\n\t\t\t\t\t%% @todo Technically we allow the sendfile tuple.\n\t\t\t\t\tRespBody\n\t\t\t\t])\n\t\t\t)\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(early_error,\n\t\t\t[StreamID, Reason, PartialReq, Resp, Opts],\n\t\t\tClass, Exception, Stacktrace), Opts),\n\t\t%% We still need to send an error response, so send what we initially\n\t\t%% wanted to send. It's better than nothing.\n\t\tok = maybe_socket_error(State,\n\t\t\tTransport:send(Socket, cow_http:response(StatusCode0,\n\t\t\t\t'HTTP/1.1', maps:to_list(RespHeaders1)))\n\t\t)\n\tend.\n\ninitiate_closing(State=#state{streams=[]}, Reason) ->\n\tterminate(State, Reason);\ninitiate_closing(State=#state{streams=Streams,\n\t\tout_streamid=OutStreamID}, Reason) ->\n\t{value, LastStream, TerminatedStreams}\n\t\t= lists:keytake(OutStreamID, #stream.id, Streams),\n\tterminate_all_streams(State, TerminatedStreams, Reason),\n\tState#state{streams=[LastStream], last_streamid=OutStreamID}.\n\n%% Function replicated in cowboy_http2.\nmaybe_socket_error(State, {error, closed}) ->\n\tterminate(State, {socket_error, closed, 'The socket has been closed.'});\nmaybe_socket_error(State, Reason) ->\n\tmaybe_socket_error(State, Reason, 'An error has occurred on the socket.').\n\nmaybe_socket_error(_, Result = ok, _) ->\n\tResult;\nmaybe_socket_error(_, Result = {ok, _}, _) ->\n\tResult;\nmaybe_socket_error(State, {error, Reason}, Human) ->\n\tterminate(State, {socket_error, Reason, Human}).\n\n-spec terminate(#state{} | undefined, _) -> no_return().\nterminate(undefined, Reason) ->\n\texit({shutdown, Reason});\nterminate(State=#state{streams=Streams, children=Children}, Reason) ->\n\tterminate_all_streams(State, Streams, Reason),\n\tcowboy_children:terminate(Children),\n\tterminate_linger(State),\n\texit({shutdown, Reason}).\n\nterminate_all_streams(_, [], _) ->\n\tok;\nterminate_all_streams(State, [#stream{id=StreamID, state=StreamState}|Tail], Reason) ->\n\tstream_call_terminate(StreamID, Reason, StreamState, State),\n\tterminate_all_streams(State, Tail, Reason).\n\nterminate_linger(State=#state{socket=Socket, transport=Transport, opts=Opts}) ->\n\tcase Transport:shutdown(Socket, write) of\n\t\tok ->\n\t\t\tcase maps:get(linger_timeout, Opts, 1000) of\n\t\t\t\t0 ->\n\t\t\t\t\tok;\n\t\t\t\tinfinity ->\n\t\t\t\t\tterminate_linger_before_loop(State, undefined, Transport:messages());\n\t\t\t\tTimeout ->\n\t\t\t\t\tTimerRef = erlang:start_timer(Timeout, self(), linger_timeout),\n\t\t\t\t\tterminate_linger_before_loop(State, TimerRef, Transport:messages())\n\t\t\tend;\n\t\t{error, _} ->\n\t\t\tok\n\tend.\n\nterminate_linger_before_loop(State, TimerRef, Messages) ->\n\t%% We may already be in active mode when we do this\n\t%% but it's OK because we are shutting down anyway.\n\t%%\n\t%% We specially handle the socket error to terminate\n\t%% when an error occurs.\n\tcase setopts_active(State) of\n\t\tok ->\n\t\t\tterminate_linger_loop(State, TimerRef, Messages);\n\t\t{error, _} ->\n\t\t\tok\n\tend.\n\nterminate_linger_loop(State=#state{socket=Socket}, TimerRef, Messages) ->\n\treceive\n\t\t{OK, Socket, _} when OK =:= element(1, Messages) ->\n\t\t\tterminate_linger_loop(State, TimerRef, Messages);\n\t\t{Closed, Socket} when Closed =:= element(2, Messages) ->\n\t\t\tok;\n\t\t{Error, Socket, _} when Error =:= element(3, Messages) ->\n\t\t\tok;\n\t\t{Passive, Socket} when Passive =:= tcp_passive; Passive =:= ssl_passive ->\n\t\t\tterminate_linger_before_loop(State, TimerRef, Messages);\n\t\t{timeout, TimerRef, linger_timeout} ->\n\t\t\tok;\n\t\t_ ->\n\t\t\tterminate_linger_loop(State, TimerRef, Messages)\n\tend.\n\n%% System callbacks.\n\n-spec system_continue(_, _, #state{}) -> ok.\nsystem_continue(_, _, State) ->\n\tbefore_loop(State).\n\n-spec system_terminate(any(), _, _, #state{}) -> no_return().\nsystem_terminate(Reason0, _, _, State) ->\n\tReason = {stop, {exit, Reason0}, 'sys:terminate/2,3 was called.'},\n\tbefore_loop(initiate_closing(State, Reason)).\n\n-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::{#state{}, binary()}.\nsystem_code_change(Misc, _, _, _) ->\n\t{ok, Misc}.\n"
  },
  {
    "path": "src/cowboy_http2.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_http2).\n\n-export([init/6]).\n-export([init/10]).\n-export([init/12]).\n-export([loop/2]).\n\n-export([system_continue/3]).\n-export([system_terminate/4]).\n-export([system_code_change/4]).\n\n-type opts() :: #{\n\tactive_n => pos_integer(),\n\talpn_default_protocol => http | http2,\n\tcompress_buffering => boolean(),\n\tcompress_threshold => non_neg_integer(),\n\tconnection_type => worker | supervisor,\n\tconnection_window_margin_size => 0..16#7fffffff,\n\tconnection_window_update_threshold => 0..16#7fffffff,\n\tdynamic_buffer => false | {pos_integer(), pos_integer()},\n\tdynamic_buffer_initial_average => non_neg_integer(),\n\tdynamic_buffer_initial_size => pos_integer(),\n\tenable_connect_protocol => boolean(),\n\tenv => cowboy_middleware:env(),\n\tgoaway_initial_timeout => timeout(),\n\tgoaway_complete_timeout => timeout(),\n\thibernate => boolean(),\n\tidle_timeout => timeout(),\n\tinactivity_timeout => timeout(),\n\tinitial_connection_window_size => 65535..16#7fffffff,\n\tinitial_stream_window_size => 0..16#7fffffff,\n\tlinger_timeout => timeout(),\n\tlogger => module(),\n\tmax_concurrent_streams => non_neg_integer() | infinity,\n\tmax_connection_buffer_size => non_neg_integer(),\n\tmax_connection_window_size => 0..16#7fffffff,\n\tmax_decode_table_size => non_neg_integer(),\n\tmax_encode_table_size => non_neg_integer(),\n\tmax_fragmented_header_block_size => 16384..16#7fffffff,\n\tmax_frame_size_received => 16384..16777215,\n\tmax_frame_size_sent => 16384..16777215 | infinity,\n\tmax_received_frame_rate => {pos_integer(), timeout()},\n\tmax_reset_stream_rate => {pos_integer(), timeout()},\n\tmax_cancel_stream_rate => {pos_integer(), timeout()},\n\tmax_stream_buffer_size => non_neg_integer(),\n\tmax_stream_window_size => 0..16#7fffffff,\n\tmetrics_callback => cowboy_metrics_h:metrics_callback(),\n\tmetrics_req_filter => fun((cowboy_req:req()) -> map()),\n\tmetrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),\n\tmiddlewares => [module()],\n\tpreface_timeout => timeout(),\n\tprotocols => [http | http2],\n\tproxy_header => boolean(),\n\treset_idle_timeout_on_send => boolean(),\n\tsendfile => boolean(),\n\tsettings_timeout => timeout(),\n\tshutdown_timeout => timeout(),\n\tstream_handlers => [module()],\n\tstream_window_data_threshold => 0..16#7fffffff,\n\tstream_window_margin_size => 0..16#7fffffff,\n\tstream_window_update_threshold => 0..16#7fffffff,\n\ttracer_callback => cowboy_tracer_h:tracer_callback(),\n\ttracer_flags => [atom()],\n\ttracer_match_specs => cowboy_tracer_h:tracer_match_specs(),\n\t%% Open ended because configured stream handlers might add options.\n\t_ => _\n}.\n-export_type([opts/0]).\n\n-record(stream, {\n\t%% Whether the stream is currently in a special state.\n\t%%\n\t%% - The running state is the normal state of a stream.\n\t%% - The relaying state is used by extended CONNECT protocols to\n\t%%   use a 'relay' data_delivery method.\n\t%% - The stopping state indicates the stream used the 'stop' command.\n\tstatus = running :: running | {relaying, non_neg_integer(), pid()} | stopping,\n\n\t%% Flow requested for this stream.\n\tflow = 0 :: non_neg_integer(),\n\n\t%% Stream state.\n\tstate :: {module, any()}\n}).\n\n%% We don't want to reset the idle timeout too often,\n%% so we don't reset it on data. Instead we reset the\n%% number of ticks we have observed. We divide the\n%% timeout value by a value and that value becomes\n%% the number of ticks at which point we can drop\n%% the connection. This value is the number of ticks.\n-define(IDLE_TIMEOUT_TICKS, 10).\n\n-record(state, {\n\tparent = undefined :: pid(),\n\tref :: ranch:ref(),\n\tsocket = undefined :: inet:socket(),\n\ttransport :: module(),\n\tproxy_header :: undefined | ranch_proxy_header:proxy_info(),\n\topts = #{} :: opts(),\n\n\t%% Timer for idle_timeout; also used for goaway timers.\n\ttimer = undefined :: undefined | reference(),\n\tidle_timeout_num = 0 :: 0..?IDLE_TIMEOUT_TICKS,\n\n\t%% Remote address and port for the connection.\n\tpeer = undefined :: {inet:ip_address(), inet:port_number()},\n\n\t%% Local address and port for the connection.\n\tsock = undefined :: {inet:ip_address(), inet:port_number()},\n\n\t%% Client certificate (TLS only).\n\tcert :: undefined | binary(),\n\n\t%% HTTP/2 state machine.\n\thttp2_status :: sequence | settings | upgrade | connected | closing_initiated | closing,\n\thttp2_machine :: cow_http2_machine:http2_machine(),\n\n\t%% HTTP/2 frame rate flood protection.\n\tframe_rate_num :: undefined | pos_integer(),\n\tframe_rate_time :: undefined | integer(),\n\n\t%% HTTP/2 reset stream flood protection.\n\treset_rate_num :: undefined | pos_integer(),\n\treset_rate_time :: undefined | integer(),\n\n\t%% HTTP/2 rapid reset attack protection.\n\tcancel_rate_num :: undefined | pos_integer(),\n\tcancel_rate_time :: undefined | integer(),\n\n\t%% Flow requested for all streams.\n\tflow = 0 :: non_neg_integer(),\n\n\t%% Dynamic buffer moving average and current buffer size.\n\tdynamic_buffer_size :: pos_integer() | false,\n\tdynamic_buffer_moving_average :: float(),\n\n\t%% Currently active HTTP/2 streams. Streams may be initiated either\n\t%% by the client or by the server through PUSH_PROMISE frames.\n\tstreams = #{} :: #{cow_http2:streamid() => #stream{}},\n\n\t%% Streams can spawn zero or more children which are then managed\n\t%% by this module if operating as a supervisor.\n\tchildren = cowboy_children:init() :: cowboy_children:children()\n}).\n\n-spec init(pid(), ranch:ref(), inet:socket(), module(),\n\tranch_proxy_header:proxy_info() | undefined, cowboy:opts()) -> no_return().\n\ninit(Parent, Ref, Socket, Transport, ProxyHeader, Opts) ->\n\t{ok, Peer} = maybe_socket_error(undefined, Transport:peername(Socket),\n\t\t'A socket error occurred when retrieving the peer name.'),\n\t{ok, Sock} = maybe_socket_error(undefined, Transport:sockname(Socket),\n\t\t'A socket error occurred when retrieving the sock name.'),\n\tCertResult = case Transport:name() of\n\t\tssl ->\n\t\t\tcase ssl:peercert(Socket) of\n\t\t\t\t{error, no_peercert} ->\n\t\t\t\t\t{ok, undefined};\n\t\t\t\tCert0 ->\n\t\t\t\t\tCert0\n\t\t\tend;\n\t\t_ ->\n\t\t\t{ok, undefined}\n\tend,\n\t{ok, Cert} = maybe_socket_error(undefined, CertResult,\n\t\t'A socket error occurred when retrieving the client TLS certificate.'),\n\tinit(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, <<>>).\n\n-spec init(pid(), ranch:ref(), inet:socket(), module(),\n\tranch_proxy_header:proxy_info() | undefined, cowboy:opts(),\n\t{inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},\n\tbinary() | undefined, binary()) -> no_return().\n\ninit(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer) ->\n\tDynamicBuffer = init_dynamic_buffer_size(Opts),\n\t{ok, Preface, HTTP2Machine} = cow_http2_machine:init(server, Opts),\n\t%% Send the preface before doing all the init in case we get a socket error.\n\tok = maybe_socket_error(undefined, Transport:send(Socket, Preface)),\n\tState = set_idle_timeout(init_rate_limiting(#state{parent=Parent, ref=Ref, socket=Socket,\n\t\ttransport=Transport, proxy_header=ProxyHeader,\n\t\topts=Opts, peer=Peer, sock=Sock, cert=Cert,\n\t\tdynamic_buffer_size=DynamicBuffer,\n\t\tdynamic_buffer_moving_average=maps:get(dynamic_buffer_initial_average, Opts, 0.0),\n\t\thttp2_status=sequence, http2_machine=HTTP2Machine}), 0),\n\tsafe_setopts_active(State),\n\tcase Buffer of\n\t\t<<>> -> before_loop(State, Buffer);\n\t\t_ -> parse(State, Buffer)\n\tend.\n\ninit_rate_limiting(State0) ->\n\tCurrentTime = erlang:monotonic_time(millisecond),\n\tState1 = init_frame_rate_limiting(State0, CurrentTime),\n\tState2 = init_reset_rate_limiting(State1, CurrentTime),\n\tinit_cancel_rate_limiting(State2, CurrentTime).\n\ninit_frame_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->\n\t{FrameRateNum, FrameRatePeriod} = maps:get(max_received_frame_rate, Opts, {10000, 10000}),\n\tState#state{\n\t\tframe_rate_num=FrameRateNum, frame_rate_time=add_period(CurrentTime, FrameRatePeriod)\n\t}.\n\ninit_reset_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->\n\t{ResetRateNum, ResetRatePeriod} = maps:get(max_reset_stream_rate, Opts, {10, 10000}),\n\tState#state{\n\t\treset_rate_num=ResetRateNum, reset_rate_time=add_period(CurrentTime, ResetRatePeriod)\n\t}.\n\ninit_cancel_rate_limiting(State=#state{opts=Opts}, CurrentTime) ->\n\t{CancelRateNum, CancelRatePeriod} = maps:get(max_cancel_stream_rate, Opts, {500, 10000}),\n\tState#state{\n\t\tcancel_rate_num=CancelRateNum, cancel_rate_time=add_period(CurrentTime, CancelRatePeriod)\n\t}.\n\nadd_period(_, infinity) -> infinity;\nadd_period(Time, Period) -> Time + Period.\n\n%% @todo Add an argument for the request body.\n-spec init(pid(), ranch:ref(), inet:socket(), module(),\n\tranch_proxy_header:proxy_info() | undefined, cowboy:opts(),\n\t{inet:ip_address(), inet:port_number()}, {inet:ip_address(), inet:port_number()},\n\tbinary() | undefined, binary(), map() | undefined, cowboy_req:req()) -> no_return().\n\ninit(Parent, Ref, Socket, Transport, ProxyHeader, Opts, Peer, Sock, Cert, Buffer,\n\t\t_Settings, Req=#{method := Method}) ->\n\tDynamicBuffer = init_dynamic_buffer_size(Opts),\n\t{ok, Preface, HTTP2Machine0} = cow_http2_machine:init(server, Opts),\n\t{ok, StreamID, HTTP2Machine}\n\t\t= cow_http2_machine:init_upgrade_stream(Method, HTTP2Machine0),\n\tState0 = #state{parent=Parent, ref=Ref, socket=Socket,\n\t\ttransport=Transport, proxy_header=ProxyHeader,\n\t\topts=Opts, peer=Peer, sock=Sock, cert=Cert,\n\t\tdynamic_buffer_size=DynamicBuffer,\n\t\tdynamic_buffer_moving_average=maps:get(dynamic_buffer_initial_average, Opts, 0.0),\n\t\thttp2_status=upgrade, http2_machine=HTTP2Machine},\n\tState1 = headers_frame(State0#state{\n\t\thttp2_machine=HTTP2Machine}, StreamID, Req),\n\t%% We assume that the upgrade will be applied. A stream handler\n\t%% must not prevent the normal operations of the server.\n\tState2 = info(State1, 1, {switch_protocol, #{\n\t\t<<\"connection\">> => <<\"Upgrade\">>,\n\t\t<<\"upgrade\">> => <<\"h2c\">>\n\t}, ?MODULE, undefined}), %% @todo undefined or #{}?\n\tState = set_idle_timeout(init_rate_limiting(State2#state{http2_status=sequence}), 0),\n\t%% In the case of HTTP/1.1 Upgrade we cannot send the Preface\n\t%% until we send the 101 response.\n\tok = maybe_socket_error(State, Transport:send(Socket, Preface)),\n\tsafe_setopts_active(State),\n\tcase Buffer of\n\t\t<<>> -> before_loop(State, Buffer);\n\t\t_ -> parse(State, Buffer)\n\tend.\n\n-include(\"cowboy_dynamic_buffer.hrl\").\n\n%% Because HTTP/2 has flow control and Cowboy has other rate limiting\n%% mechanisms implemented, a very large active_n value should be fine,\n%% as long as the stream handlers do their work in a timely manner.\n%% However large active_n values reduce the impact of dynamic_buffer.\nsetopts_active(#state{socket=Socket, transport=Transport, opts=Opts}) ->\n\tN = maps:get(active_n, Opts, 1),\n\tTransport:setopts(Socket, [{active, N}]).\n\nsafe_setopts_active(State) ->\n\tok = maybe_socket_error(State, setopts_active(State)).\n\nbefore_loop(State=#state{opts=#{hibernate := true}}, Buffer) ->\n\tproc_lib:hibernate(?MODULE, loop, [State, Buffer]);\nbefore_loop(State, Buffer) ->\n\tloop(State, Buffer).\n\n-spec loop(#state{}, binary()) -> no_return().\n\nloop(State=#state{parent=Parent, socket=Socket, transport=Transport,\n\t\topts=Opts, timer=TimerRef, children=Children}, Buffer) ->\n\tMessages = Transport:messages(),\n\tInactivityTimeout = maps:get(inactivity_timeout, Opts, 300000),\n\treceive\n\t\t%% Socket messages.\n\t\t{OK, Socket, Data} when OK =:= element(1, Messages) ->\n\t\t\tState1 = maybe_resize_buffer(State, Data),\n\t\t\tparse(State1#state{idle_timeout_num=0}, << Buffer/binary, Data/binary >>);\n\t\t{Closed, Socket} when Closed =:= element(2, Messages) ->\n\t\t\tReason = case State#state.http2_status of\n\t\t\t\tclosing -> {stop, closed, 'The client is going away.'};\n\t\t\t\t_ -> {socket_error, closed, 'The socket has been closed.'}\n\t\t\tend,\n\t\t\tterminate(State, Reason);\n\t\t{Error, Socket, Reason} when Error =:= element(3, Messages) ->\n\t\t\tterminate(State, {socket_error, Reason, 'An error has occurred on the socket.'});\n\t\t{Passive, Socket} when Passive =:= element(4, Messages);\n\t\t\t\t%% Hardcoded for compatibility with Ranch 1.x.\n\t\t\t\tPassive =:= tcp_passive; Passive =:= ssl_passive ->\n\t\t\tsafe_setopts_active(State),\n\t\t\tbefore_loop(State, Buffer);\n\t\t%% System messages.\n\t\t{'EXIT', Parent, shutdown} ->\n\t\t\tReason = {stop, {exit, shutdown}, 'Parent process requested shutdown.'},\n\t\t\tbefore_loop(initiate_closing(State, Reason), Buffer);\n\t\t{'EXIT', Parent, Reason} ->\n\t\t\tterminate(State, {stop, {exit, Reason}, 'Parent process terminated.'});\n\t\t{system, From, Request} ->\n\t\t\tsys:handle_system_msg(Request, From, Parent, ?MODULE, [], {State, Buffer});\n\t\t%% Timeouts.\n\t\t{timeout, TimerRef, idle_timeout} ->\n\t\t\ttick_idle_timeout(State, Buffer);\n\t\t{timeout, Ref, {shutdown, Pid}} ->\n\t\t\tcowboy_children:shutdown_timeout(Children, Ref, Pid),\n\t\t\tbefore_loop(State, Buffer);\n\t\t{timeout, TRef, {cow_http2_machine, Name}} ->\n\t\t\tbefore_loop(timeout(State, Name, TRef), Buffer);\n\t\t{timeout, TimerRef, {goaway_initial_timeout, Reason}} ->\n\t\t\tbefore_loop(closing(State, Reason), Buffer);\n\t\t{timeout, TimerRef, {goaway_complete_timeout, Reason}} ->\n\t\t\tterminate(State, {stop, stop_reason(Reason),\n\t\t\t\t'Graceful shutdown timed out.'});\n\t\t%% Messages pertaining to a stream.\n\t\t{{Pid, StreamID}, Msg} when Pid =:= self() ->\n\t\t\tbefore_loop(info(State, StreamID, Msg), Buffer);\n\t\t{'$cowboy_relay_command', {Pid, StreamID}, RelayCommand} when Pid =:= self() ->\n\t\t\tbefore_loop(relay_command(State, StreamID, RelayCommand), Buffer);\n\t\t%% Exit signal from children.\n\t\tMsg = {'EXIT', Pid, _} ->\n\t\t\tbefore_loop(down(State, Pid, Msg), Buffer);\n\t\t%% Calls from supervisor module.\n\t\t{'$gen_call', From, Call} ->\n\t\t\tcowboy_children:handle_supervisor_call(Call, From, Children, ?MODULE),\n\t\t\tbefore_loop(State, Buffer);\n\t\tMsg ->\n\t\t\tcowboy:log(warning, \"Received stray message ~p.\", [Msg], Opts),\n\t\t\tbefore_loop(State, Buffer)\n\tafter InactivityTimeout ->\n\t\tterminate(State, {internal_error, timeout, 'No message or data received before timeout.'})\n\tend.\n\ntick_idle_timeout(State=#state{idle_timeout_num=?IDLE_TIMEOUT_TICKS}, _) ->\n\tterminate(State, {stop, timeout,\n\t\t'Connection idle longer than configuration allows.'});\ntick_idle_timeout(State=#state{idle_timeout_num=TimeoutNum}, Buffer) ->\n\tbefore_loop(set_idle_timeout(State, TimeoutNum + 1), Buffer).\n\nset_idle_timeout(State=#state{http2_status=Status, timer=TimerRef}, _)\n\t\twhen Status =:= closing_initiated orelse Status =:= closing,\n\t\t\tTimerRef =/= undefined ->\n\tState;\nset_idle_timeout(State=#state{opts=Opts}, TimeoutNum) ->\n\tcase maps:get(idle_timeout, Opts, 60000) of\n\t\tinfinity ->\n\t\t\tState#state{timer=undefined};\n\t\tTimeout ->\n\t\t\tset_timeout(State#state{idle_timeout_num=TimeoutNum},\n\t\t\t\tTimeout div ?IDLE_TIMEOUT_TICKS, idle_timeout)\n\tend.\n\nset_timeout(State=#state{timer=TimerRef0}, Timeout, Message) ->\n\tok = case TimerRef0 of\n\t\tundefined -> ok;\n\t\t_ -> erlang:cancel_timer(TimerRef0, [{async, true}, {info, false}])\n\tend,\n\tTimerRef = case Timeout of\n\t\tinfinity -> undefined;\n\t\tTimeout -> erlang:start_timer(Timeout, self(), Message)\n\tend,\n\tState#state{timer=TimerRef}.\n\nmaybe_reset_idle_timeout(State=#state{opts=Opts}) ->\n\tcase maps:get(reset_idle_timeout_on_send, Opts, false) of\n\t\ttrue ->\n\t\t\tState#state{idle_timeout_num=0};\n\t\tfalse ->\n\t\t\tState\n\tend.\n\n%% HTTP/2 protocol parsing.\n\nparse(State=#state{http2_status=sequence}, Data) ->\n\tcase cow_http2:parse_sequence(Data) of\n\t\t{ok, Rest} ->\n\t\t\tparse(State#state{http2_status=settings}, Rest);\n\t\tmore ->\n\t\t\tbefore_loop(State, Data);\n\t\tError = {connection_error, _, _} ->\n\t\t\tterminate(State, Error)\n\tend;\nparse(State=#state{http2_status=Status, http2_machine=HTTP2Machine, streams=Streams}, Data) ->\n\tMaxFrameSize = cow_http2_machine:get_local_setting(max_frame_size, HTTP2Machine),\n\tcase cow_http2:parse(Data, MaxFrameSize) of\n\t\t{ok, Frame, Rest} ->\n\t\t\tparse(frame_rate(State, Frame), Rest);\n\t\t{ignore, Rest} ->\n\t\t\tparse(frame_rate(State, ignore), Rest);\n\t\t{stream_error, StreamID, Reason, Human, Rest} ->\n\t\t\tparse(reset_stream(State, StreamID, {stream_error, Reason, Human}), Rest);\n\t\tError = {connection_error, _, _} ->\n\t\t\tterminate(State, Error);\n\t\t%% Terminate the connection if we are closing and all streams have completed.\n\t\tmore when Status =:= closing, Streams =:= #{} ->\n\t\t\tterminate(State, {stop, normal, 'The connection is going away.'});\n\t\tmore ->\n\t\t\tbefore_loop(State, Data)\n\tend.\n\n%% Frame rate flood protection.\n\nframe_rate(State0=#state{frame_rate_num=Num0, frame_rate_time=Time}, Frame) ->\n\t{Result, State} = case Num0 - 1 of\n\t\t0 ->\n\t\t\tCurrentTime = erlang:monotonic_time(millisecond),\n\t\t\tif\n\t\t\t\tCurrentTime < Time ->\n\t\t\t\t\t{error, State0};\n\t\t\t\ttrue ->\n\t\t\t\t\t%% When the option has a period of infinity we cannot reach this clause.\n\t\t\t\t\t{ok, init_frame_rate_limiting(State0, CurrentTime)}\n\t\t\tend;\n\t\tNum ->\n\t\t\t{ok, State0#state{frame_rate_num=Num}}\n\tend,\n\tcase {Result, Frame} of\n\t\t{ok, ignore} -> ignored_frame(State);\n\t\t{ok, _} -> frame(State, Frame);\n\t\t{error, _} -> terminate(State, {connection_error, enhance_your_calm,\n\t\t\t'Frame rate larger than configuration allows. Flood? (CVE-2019-9512, CVE-2019-9515, CVE-2019-9518)'})\n\tend.\n\n%% Frames received.\n\n%% We do nothing when receiving a lingering DATA frame.\n%% We already removed the stream flow from the connection\n%% flow and are therefore already accounting for the window\n%% being reduced by these frames.\nframe(State=#state{http2_machine=HTTP2Machine0}, Frame) ->\n\tcase cow_http2_machine:frame(Frame, HTTP2Machine0) of\n\t\t{ok, HTTP2Machine} ->\n\t\t\tmaybe_ack(State#state{http2_machine=HTTP2Machine}, Frame);\n\t\t{ok, {data, StreamID, IsFin, Data}, HTTP2Machine} ->\n\t\t\tdata_frame(State#state{http2_machine=HTTP2Machine}, StreamID, IsFin, Data);\n\t\t{ok, {headers, StreamID, IsFin, Headers, PseudoHeaders, BodyLen}, HTTP2Machine} ->\n\t\t\theaders_frame(State#state{http2_machine=HTTP2Machine},\n\t\t\t\tStreamID, IsFin, Headers, PseudoHeaders, BodyLen);\n\t\t{ok, {trailers, _StreamID, _Trailers}, HTTP2Machine} ->\n\t\t\t%% @todo Propagate trailers.\n\t\t\tState#state{http2_machine=HTTP2Machine};\n\t\t{ok, {rst_stream, StreamID, Reason}, HTTP2Machine} ->\n\t\t\trst_stream_frame(State#state{http2_machine=HTTP2Machine}, StreamID, Reason);\n\t\t{ok, GoAway={goaway, _, _, _}, HTTP2Machine} ->\n\t\t\tgoaway(State#state{http2_machine=HTTP2Machine}, GoAway);\n\t\t{send, SendData, HTTP2Machine} ->\n\t\t\t%% We may need to send an alarm for each of the streams sending data.\n\t\t\tState1 = lists:foldl(\n\t\t\t\tfun({StreamID, _, _}, S) -> maybe_send_data_alarm(S, HTTP2Machine0, StreamID) end,\n\t\t\t\tsend_data(maybe_ack(State#state{http2_machine=HTTP2Machine}, Frame), SendData, []),\n\t\t\t\tSendData),\n\t\t\tmaybe_reset_idle_timeout(State1);\n\t\t{error, {stream_error, StreamID, Reason, Human}, HTTP2Machine} ->\n\t\t\treset_stream(State#state{http2_machine=HTTP2Machine},\n\t\t\t\tStreamID, {stream_error, Reason, Human});\n\t\t{error, Error={connection_error, _, _}, HTTP2Machine} ->\n\t\t\tterminate(State#state{http2_machine=HTTP2Machine}, Error)\n\tend.\n\n%% We use this opportunity to mark the HTTP/2 status as connected\n%% if we were still waiting for a SETTINGS frame.\nmaybe_ack(State=#state{http2_status=settings}, Frame) ->\n\tmaybe_ack(State#state{http2_status=connected}, Frame);\n%% We do not reset the idle timeout on send here because we are\n%% sending data as a consequence of receiving data, which means\n%% we already resetted the idle timeout.\nmaybe_ack(State=#state{socket=Socket, transport=Transport}, Frame) ->\n\tcase Frame of\n\t\t{settings, _} ->\n\t\t\tok = maybe_socket_error(State, Transport:send(Socket, cow_http2:settings_ack()));\n\t\t{ping, Opaque} ->\n\t\t\tok = maybe_socket_error(State, Transport:send(Socket, cow_http2:ping_ack(Opaque)));\n\t\t_ -> ok\n\tend,\n\tState.\n\ndata_frame(State0=#state{opts=Opts, flow=Flow0, streams=Streams}, StreamID, IsFin, Data) ->\n\tcase Streams of\n\t\t#{StreamID := Stream=#stream{status=running, flow=StreamFlow, state=StreamState0}} ->\n\t\t\ttry cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of\n\t\t\t\t{Commands, StreamState} ->\n\t\t\t\t\t%% Remove the amount of data received from the flow.\n\t\t\t\t\t%% We may receive more data than we requested. We ensure\n\t\t\t\t\t%% that the flow value doesn't go lower than 0.\n\t\t\t\t\tSize = byte_size(Data),\n\t\t\t\t\tFlow = max(0, Flow0 - Size),\n\t\t\t\t\t%% We would normally update the window when changing the flow\n\t\t\t\t\t%% value. But because we are running commands, which themselves\n\t\t\t\t\t%% may update the window, and we want to avoid updating the\n\t\t\t\t\t%% window twice in a row, we first run the commands and then\n\t\t\t\t\t%% only update the window a flow command was executed. We know\n\t\t\t\t\t%% that it was because the flow value changed in the state.\n\t\t\t\t\tState1 = State0#state{flow=Flow,\n\t\t\t\t\t\tstreams=Streams#{StreamID => Stream#stream{\n\t\t\t\t\t\t\tflow=max(0, StreamFlow - Size), state=StreamState}}},\n\t\t\t\t\tState = commands(State1, StreamID, Commands),\n\t\t\t\t\tcase State of\n\t\t\t\t\t\t%% No flow command was executed. We must update the window\n\t\t\t\t\t\t%% because we changed the flow value earlier.\n\t\t\t\t\t\t#state{flow=Flow} ->\n\t\t\t\t\t\t\tupdate_window(State, StreamID);\n\t\t\t\t\t\t%% Otherwise the window was updated already.\n\t\t\t\t\t\t_ ->\n\t\t\t\t\t\t\tState\n\t\t\t\t\tend\n\t\t\tcatch Class:Exception:Stacktrace ->\n\t\t\t\tcowboy:log(cowboy_stream:make_error_log(data,\n\t\t\t\t\t[StreamID, IsFin, Data, StreamState0],\n\t\t\t\t\tClass, Exception, Stacktrace), Opts),\n\t\t\t\treset_stream(State0, StreamID, {internal_error, {Class, Exception},\n\t\t\t\t\t'Unhandled exception in cowboy_stream:data/4.'})\n\t\t\tend;\n\t\t%% Stream handlers are not used for the data when relaying.\n\t\t#{StreamID := #stream{status={relaying, _, RelayPid}}} ->\n\t\t\tRelayPid ! {'$cowboy_relay_data', {self(), StreamID}, IsFin, Data},\n\t\t\t%% We keep a steady flow using the configured flow value.\n\t\t\t%% Because we do not change the 'flow' value the update_window/2\n\t\t\t%% function will always maintain this value (of course with\n\t\t\t%% thresholds applying).\n\t\t\tupdate_window(State0, StreamID);\n\t\t%% We ignore DATA frames for streams that are stopping.\n\t\t#{} ->\n\t\t\tState0\n\tend.\n\nheaders_frame(State, StreamID, IsFin, Headers,\n\t\tPseudoHeaders=#{method := <<\"CONNECT\">>}, _)\n\t\twhen map_size(PseudoHeaders) =:= 2 ->\n\tearly_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501,\n\t\t'The CONNECT method is currently not implemented. (RFC7231 4.3.6)');\nheaders_frame(State, StreamID, IsFin, Headers,\n\t\tPseudoHeaders=#{method := <<\"TRACE\">>}, _) ->\n\tearly_error(State, StreamID, IsFin, Headers, PseudoHeaders, 501,\n\t\t'The TRACE method is currently not implemented. (RFC7231 4.3.8)');\nheaders_frame(State, StreamID, IsFin, Headers, PseudoHeaders=#{authority := Authority}, BodyLen) ->\n\theaders_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority);\nheaders_frame(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen) ->\n\tcase lists:keyfind(<<\"host\">>, 1, Headers) of\n\t\t{_, Authority} ->\n\t\t\theaders_frame_parse_host(State, StreamID, IsFin, Headers, PseudoHeaders, BodyLen, Authority);\n\t\t_ ->\n\t\t\treset_stream(State, StreamID, {stream_error, protocol_error,\n\t\t\t\t'Requests translated from HTTP/1.1 must include a host header. (RFC7540 8.1.2.3, RFC7230 5.4)'})\n\tend.\n\nheaders_frame_parse_host(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert, proxy_header=ProxyHeader},\n\t\tStreamID, IsFin, Headers, PseudoHeaders=#{method := Method, scheme := Scheme, path := PathWithQs},\n\t\tBodyLen, Authority) ->\n\ttry cow_http_hd:parse_host(Authority) of\n\t\t{Host, Port0} ->\n\t\t\tPort = ensure_port(Scheme, Port0),\n\t\t\ttry cow_http:parse_fullpath(PathWithQs) of\n\t\t\t\t{<<>>, _} ->\n\t\t\t\t\treset_stream(State, StreamID, {stream_error, protocol_error,\n\t\t\t\t\t\t'The path component must not be empty. (RFC7540 8.1.2.3)'});\n\t\t\t\t{Path, Qs} ->\n\t\t\t\t\tReq0 = #{\n\t\t\t\t\t\tref => Ref,\n\t\t\t\t\t\tpid => self(),\n\t\t\t\t\t\tstreamid => StreamID,\n\t\t\t\t\t\tpeer => Peer,\n\t\t\t\t\t\tsock => Sock,\n\t\t\t\t\t\tcert => Cert,\n\t\t\t\t\t\tmethod => Method,\n\t\t\t\t\t\tscheme => Scheme,\n\t\t\t\t\t\thost => Host,\n\t\t\t\t\t\tport => Port,\n\t\t\t\t\t\tpath => Path,\n\t\t\t\t\t\tqs => Qs,\n\t\t\t\t\t\tversion => 'HTTP/2',\n\t\t\t\t\t\theaders => headers_to_map(Headers, #{}),\n\t\t\t\t\t\thas_body => IsFin =:= nofin,\n\t\t\t\t\t\tbody_length => BodyLen\n\t\t\t\t\t},\n\t\t\t\t\t%% We add the PROXY header information if any.\n\t\t\t\t\tReq1 = case ProxyHeader of\n\t\t\t\t\t\tundefined -> Req0;\n\t\t\t\t\t\t_ -> Req0#{proxy_header => ProxyHeader}\n\t\t\t\t\tend,\n\t\t\t\t\t%% We add the protocol information for extended CONNECTs.\n\t\t\t\t\tReq = case PseudoHeaders of\n\t\t\t\t\t\t#{protocol := Protocol} -> Req1#{protocol => Protocol};\n\t\t\t\t\t\t_ -> Req1\n\t\t\t\t\tend,\n\t\t\t\t\theaders_frame(State, StreamID, Req)\n\t\t\tcatch _:_ ->\n\t\t\t\treset_stream(State, StreamID, {stream_error, protocol_error,\n\t\t\t\t\t'The :path pseudo-header is invalid. (RFC7540 8.1.2.3)'})\n\t\t\tend\n\tcatch _:_ ->\n\t\treset_stream(State, StreamID, {stream_error, protocol_error,\n\t\t\t'The :authority pseudo-header is invalid. (RFC7540 8.1.2.3)'})\n\tend.\n\nensure_port(<<\"http\">>, undefined) -> 80;\nensure_port(<<\"https\">>, undefined) -> 443;\nensure_port(_, Port) -> Port.\n\n%% This function is necessary to properly handle duplicate headers\n%% and the special-case cookie header.\nheaders_to_map([], Acc) ->\n\tAcc;\nheaders_to_map([{Name, Value}|Tail], Acc0) ->\n\tAcc = case Acc0 of\n\t\t%% The cookie header does not use proper HTTP header lists.\n\t\t#{Name := Value0} when Name =:= <<\"cookie\">> ->\n\t\t\tAcc0#{Name => << Value0/binary, \"; \", Value/binary >>};\n\t\t#{Name := Value0} ->\n\t\t\tAcc0#{Name => << Value0/binary, \", \", Value/binary >>};\n\t\t_ ->\n\t\t\tAcc0#{Name => Value}\n\tend,\n\theaders_to_map(Tail, Acc).\n\nheaders_frame(State=#state{opts=Opts, streams=Streams}, StreamID, Req) ->\n\ttry cowboy_stream:init(StreamID, Req, Opts) of\n\t\t{Commands, StreamState} ->\n\t\t\tcommands(State#state{\n\t\t\t\tstreams=Streams#{StreamID => #stream{state=StreamState}}},\n\t\t\t\tStreamID, Commands)\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(init,\n\t\t\t[StreamID, Req, Opts],\n\t\t\tClass, Exception, Stacktrace), Opts),\n\t\treset_stream(State, StreamID, {internal_error, {Class, Exception},\n\t\t\t'Unhandled exception in cowboy_stream:init/3.'})\n\tend.\n\nearly_error(State0=#state{ref=Ref, opts=Opts, peer=Peer},\n\t\tStreamID, _IsFin, Headers, #{method := Method},\n\t\tStatusCode0, HumanReadable) ->\n\t%% We automatically terminate the stream but it is not an error\n\t%% per se (at least not in the first implementation).\n\tReason = {stream_error, no_error, HumanReadable},\n\t%% The partial Req is minimal for now. We only have one case\n\t%% where it can be called (when a method is completely disabled).\n\t%% @todo Fill in the other elements.\n\tPartialReq = #{\n\t\tref => Ref,\n\t\tpeer => Peer,\n\t\tmethod => Method,\n\t\theaders => headers_to_map(Headers, #{})\n\t},\n\tResp = {response, StatusCode0, RespHeaders0=#{<<\"content-length\">> => <<\"0\">>}, <<>>},\n\ttry cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts) of\n\t\t{response, StatusCode, RespHeaders, RespBody} ->\n\t\t\tsend_response(State0, StreamID, StatusCode, RespHeaders, RespBody)\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(early_error,\n\t\t\t[StreamID, Reason, PartialReq, Resp, Opts],\n\t\t\tClass, Exception, Stacktrace), Opts),\n\t\t%% We still need to send an error response, so send what we initially\n\t\t%% wanted to send. It's better than nothing.\n\t\tsend_headers(State0, StreamID, fin, StatusCode0, RespHeaders0)\n\tend.\n\nrst_stream_frame(State=#state{streams=Streams0, children=Children0}, StreamID, Reason) ->\n\tcase maps:take(StreamID, Streams0) of\n\t\t{#stream{state=StreamState}, Streams} ->\n\t\t\tterminate_stream_handler(State, StreamID, Reason, StreamState),\n\t\t\tChildren = cowboy_children:shutdown(Children0, StreamID),\n\t\t\tcancel_rate_limit(State#state{streams=Streams, children=Children});\n\t\terror ->\n\t\t\tState\n\tend.\n\ncancel_rate_limit(State0=#state{cancel_rate_num=Num0, cancel_rate_time=Time}) ->\n\tcase Num0 - 1 of\n\t\t0 ->\n\t\t\tCurrentTime = erlang:monotonic_time(millisecond),\n\t\t\tif\n\t\t\t\tCurrentTime < Time ->\n\t\t\t\t\tterminate(State0, {connection_error, enhance_your_calm,\n\t\t\t\t\t\t'Stream cancel rate larger than configuration allows. Flood? (CVE-2023-44487)'});\n\t\t\t\ttrue ->\n\t\t\t\t\t%% When the option has a period of infinity we cannot reach this clause.\n\t\t\t\t\tinit_cancel_rate_limiting(State0, CurrentTime)\n\t\t\tend;\n\t\tNum ->\n\t\t\tState0#state{cancel_rate_num=Num}\n\tend.\n\nignored_frame(State=#state{http2_machine=HTTP2Machine0}) ->\n\tcase cow_http2_machine:ignored_frame(HTTP2Machine0) of\n\t\t{ok, HTTP2Machine} ->\n\t\t\tState#state{http2_machine=HTTP2Machine};\n\t\t{error, Error={connection_error, _, _}, HTTP2Machine} ->\n\t\t\tterminate(State#state{http2_machine=HTTP2Machine}, Error)\n\tend.\n\n%% HTTP/2 timeouts.\n\ntimeout(State=#state{http2_machine=HTTP2Machine0}, Name, TRef) ->\n\tcase cow_http2_machine:timeout(Name, TRef, HTTP2Machine0) of\n\t\t{ok, HTTP2Machine} ->\n\t\t\tState#state{http2_machine=HTTP2Machine};\n\t\t{error, Error={connection_error, _, _}, HTTP2Machine} ->\n\t\t\tterminate(State#state{http2_machine=HTTP2Machine}, Error)\n\tend.\n\n%% Erlang messages.\n\ndown(State0=#state{opts=Opts, children=Children0}, Pid, Msg) ->\n\tState = case cowboy_children:down(Children0, Pid) of\n\t\t%% The stream was terminated already.\n\t\t{ok, undefined, Children} ->\n\t\t\tState0#state{children=Children};\n\t\t%% The stream is still running.\n\t\t{ok, StreamID, Children} ->\n\t\t\tinfo(State0#state{children=Children}, StreamID, Msg);\n\t\t%% The process was unknown.\n\t\terror ->\n\t\t\tcowboy:log(warning, \"Received EXIT signal ~p for unknown process ~p.~n\",\n\t\t\t\t[Msg, Pid], Opts),\n\t\t\tState0\n\tend,\n\tif\n\t\tState#state.http2_status =:= closing, State#state.streams =:= #{} ->\n\t\t\tterminate(State, {stop, normal, 'The connection is going away.'});\n\t\ttrue ->\n\t\t\tState\n\tend.\n\ninfo(State=#state{opts=Opts, http2_machine=HTTP2Machine, streams=Streams}, StreamID, Msg) ->\n\tcase Streams of\n\t\t#{StreamID := Stream=#stream{state=StreamState0}} ->\n\t\t\ttry cowboy_stream:info(StreamID, Msg, StreamState0) of\n\t\t\t\t{Commands, StreamState} ->\n\t\t\t\t\tcommands(State#state{streams=Streams#{StreamID => Stream#stream{state=StreamState}}},\n\t\t\t\t\t\tStreamID, Commands)\n\t\t\tcatch Class:Exception:Stacktrace ->\n\t\t\t\tcowboy:log(cowboy_stream:make_error_log(info,\n\t\t\t\t\t[StreamID, Msg, StreamState0],\n\t\t\t\t\tClass, Exception, Stacktrace), Opts),\n\t\t\t\treset_stream(State, StreamID, {internal_error, {Class, Exception},\n\t\t\t\t\t'Unhandled exception in cowboy_stream:info/3.'})\n\t\t\tend;\n\t\t_ ->\n\t\t\tcase cow_http2_machine:is_lingering_stream(StreamID, HTTP2Machine) of\n\t\t\t\ttrue ->\n\t\t\t\t\tok;\n\t\t\t\tfalse ->\n\t\t\t\t\tcowboy:log(warning, \"Received message ~p for unknown stream ~p.\",\n\t\t\t\t\t\t[Msg, StreamID], Opts)\n\t\t\tend,\n\t\t\tState\n\tend.\n\n%% Stream handler commands.\n%%\n%% @todo Kill the stream if it tries to send a response, headers,\n%% data or push promise when the stream is closed or half-closed.\n\ncommands(State, _, []) ->\n\tState;\n%% Error responses are sent only if a response wasn't sent already.\ncommands(State=#state{http2_machine=HTTP2Machine}, StreamID,\n\t\t[{error_response, StatusCode, Headers, Body}|Tail]) ->\n\tcase cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of\n\t\t{ok, idle, _} ->\n\t\t\tcommands(State, StreamID, [{response, StatusCode, Headers, Body}|Tail]);\n\t\t_ ->\n\t\t\tcommands(State, StreamID, Tail)\n\tend;\n%% Send an informational response.\ncommands(State0, StreamID, [{inform, StatusCode, Headers}|Tail]) ->\n\tState1 = send_headers(State0, StreamID, idle, StatusCode, Headers),\n\tState = maybe_reset_idle_timeout(State1),\n\tcommands(State, StreamID, Tail);\n%% Send response headers.\ncommands(State0, StreamID, [{response, StatusCode, Headers, Body}|Tail]) ->\n\tState1 = send_response(State0, StreamID, StatusCode, Headers, Body),\n\tState = maybe_reset_idle_timeout(State1),\n\tcommands(State, StreamID, Tail);\n%% Send response headers.\ncommands(State0, StreamID, [{headers, StatusCode, Headers}|Tail]) ->\n\tState1 = send_headers(State0, StreamID, nofin, StatusCode, Headers),\n\tState = maybe_reset_idle_timeout(State1),\n\tcommands(State, StreamID, Tail);\n%% Send a response body chunk.\ncommands(State0, StreamID, [{data, IsFin, Data}|Tail]) ->\n\tState = case maybe_send_data(State0, StreamID, IsFin, Data, []) of\n\t\t{data_sent, State1} ->\n\t\t\tmaybe_reset_idle_timeout(State1);\n\t\t{no_data_sent, State1} ->\n\t\t\tState1\n\tend,\n\tcommands(State, StreamID, Tail);\n%% Send trailers.\ncommands(State0, StreamID, [{trailers, Trailers}|Tail]) ->\n\tState = case maybe_send_data(State0, StreamID, fin,\n\t\t\t{trailers, maps:to_list(Trailers)}, []) of\n\t\t{data_sent, State1} ->\n\t\t\tmaybe_reset_idle_timeout(State1);\n\t\t{no_data_sent, State1} ->\n\t\t\tState1\n\tend,\n\tcommands(State, StreamID, Tail);\n%% Send a push promise.\n%%\n%% @todo Responses sent as a result of a push_promise request\n%% must not send push_promise frames themselves.\n%%\n%% @todo We should not send push_promise frames when we are\n%% in the closing http2_status.\ncommands(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine0},\n\t\tStreamID, [{push, Method, Scheme, Host, Port, Path, Qs, Headers0}|Tail]) ->\n\tAuthority = case {Scheme, Port} of\n\t\t{<<\"http\">>, 80} -> Host;\n\t\t{<<\"https\">>, 443} -> Host;\n\t\t_ -> iolist_to_binary([Host, $:, integer_to_binary(Port)])\n\tend,\n\tPathWithQs = iolist_to_binary(case Qs of\n\t\t<<>> -> Path;\n\t\t_ -> [Path, $?, Qs]\n\tend),\n\tPseudoHeaders = #{\n\t\tmethod => Method,\n\t\tscheme => Scheme,\n\t\tauthority => Authority,\n\t\tpath => PathWithQs\n\t},\n\t%% We need to make sure the header value is binary before we can\n\t%% create the Req object, as it expects them to be flat.\n\tHeaders = maps:to_list(maps:map(fun(_, V) -> iolist_to_binary(V) end, Headers0)),\n\tState = case cow_http2_machine:prepare_push_promise(StreamID, HTTP2Machine0,\n\t\t\tPseudoHeaders, Headers) of\n\t\t{ok, PromisedStreamID, HeaderBlock, HTTP2Machine} ->\n\t\t\tState1 = State0#state{http2_machine=HTTP2Machine},\n\t\t\tok = maybe_socket_error(State1, Transport:send(Socket,\n\t\t\t\tcow_http2:push_promise(StreamID, PromisedStreamID, HeaderBlock))),\n\t\t\tState2 = maybe_reset_idle_timeout(State1),\n\t\t\theaders_frame(State2, PromisedStreamID, fin, Headers, PseudoHeaders, 0);\n\t\t{error, no_push} ->\n\t\t\tState0\n\tend,\n\tcommands(State, StreamID, Tail);\n%% Read the request body.\ncommands(State0=#state{flow=Flow, streams=Streams}, StreamID, [{flow, Size}|Tail]) ->\n\t#{StreamID := Stream=#stream{flow=StreamFlow}} = Streams,\n\tState = update_window(State0#state{flow=Flow + Size,\n\t\tstreams=Streams#{StreamID => Stream#stream{flow=StreamFlow + Size}}},\n\t\tStreamID),\n\tcommands(State, StreamID, Tail);\n%% Supervise a child process.\ncommands(State=#state{children=Children}, StreamID, [{spawn, Pid, Shutdown}|Tail]) ->\n\t commands(State#state{children=cowboy_children:up(Children, Pid, StreamID, Shutdown)},\n\t\tStreamID, Tail);\n%% Error handling.\ncommands(State, StreamID, [Error = {internal_error, _, _}|_Tail]) ->\n\t%% @todo Do we want to run the commands after an internal_error?\n\t%% @todo Do we even allow commands after?\n\t%% @todo Only reset when the stream still exists.\n\treset_stream(State, StreamID, Error);\n%% Upgrade to HTTP/2. This is triggered by cowboy_http2 itself.\n%%\n%% We do not need to reset the idle timeout on send because it\n%% hasn't been set yet. This is called from init/12.\ncommands(State=#state{socket=Socket, transport=Transport, http2_status=upgrade},\n\t\tStreamID, [{switch_protocol, Headers, ?MODULE, _}|Tail]) ->\n\t%% @todo This 101 response needs to be passed through stream handlers.\n\tok = maybe_socket_error(State, Transport:send(Socket,\n\t\tcow_http:response(101, 'HTTP/1.1', maps:to_list(Headers)))),\n\tcommands(State, StreamID, Tail);\n%% Use a different protocol within the stream (CONNECT :protocol).\n%% @todo Make sure we error out when the feature is disabled.\n%% There are two data_delivery: stream_handlers and relay.\n%% The former just has the data go through stream handlers\n%% like normal requests. The latter relays data directly.\n%%\n%% @todo When relaying there might be some data that is\n%%       in stream handlers and that need to be received,\n%%       depending on whether the protocol sends data\n%%       before processing the response.\ncommands(State0=#state{flow=Flow, streams=Streams}, StreamID,\n\t\t[{switch_protocol, Headers, _Mod, ModState=#{data_delivery := relay}}|Tail]) ->\n\tState1 = info(State0, StreamID, {headers, 200, Headers}),\n\t#{StreamID := Stream} = Streams,\n\t#{data_delivery_pid := RelayPid} = ModState,\n\t%% WINDOW_UPDATE frames updating the window will be sent after\n\t%% the first DATA frame has been received.\n\tRelayFlow = maps:get(data_delivery_flow, ModState, 131072),\n\tState = State1#state{flow=Flow + RelayFlow, streams=Streams#{StreamID => Stream#stream{\n\t\tstatus={relaying, RelayFlow, RelayPid},\n\t\tflow=RelayFlow}}},\n\tcommands(State, StreamID, Tail);\ncommands(State0, StreamID, [{switch_protocol, Headers, _Mod, _ModState}|Tail]) ->\n\tState = info(State0, StreamID, {headers, 200, Headers}),\n\tcommands(State, StreamID, Tail);\n%% Set options dynamically.\ncommands(State, StreamID, [{set_options, _Opts}|Tail]) ->\n\tcommands(State, StreamID, Tail);\ncommands(State, StreamID, [stop|_Tail]) ->\n\t%% @todo Do we want to run the commands after a stop?\n\t%% @todo Do we even allow commands after?\n\tstop_stream(State, StreamID);\n%% Log event.\ncommands(State=#state{opts=Opts}, StreamID, [Log={log, _, _, _}|Tail]) ->\n\tcowboy:log(Log, Opts),\n\tcommands(State, StreamID, Tail).\n\n%% Relay data delivery commands.\n\nrelay_command(State, StreamID, DataCmd = {data, _, _}) ->\n\tcommands(State, StreamID, [DataCmd]);\n%% When going active mode again we set the RelayFlow again\n%% and update the window if necessary.\nrelay_command(State=#state{flow=Flow, streams=Streams}, StreamID, active) ->\n\t#{StreamID := Stream} = Streams,\n\t#stream{status={relaying, RelayFlow, _}} = Stream,\n\tupdate_window(State#state{flow=Flow + RelayFlow,\n\t\tstreams=Streams#{StreamID => Stream#stream{flow=RelayFlow}}},\n\t\tStreamID);\n%% When going passive mode we don't update the window\n%% since we have not incremented it.\nrelay_command(State=#state{flow=Flow, streams=Streams}, StreamID, passive) ->\n\t#{StreamID := Stream} = Streams,\n\t#stream{flow=StreamFlow} = Stream,\n\tState#state{flow=Flow - StreamFlow,\n\t\tstreams=Streams#{StreamID => Stream#stream{flow=0}}}.\n\n%% Tentatively update the window after the flow was updated.\n\nupdate_window(State0=#state{socket=Socket, transport=Transport,\n\t\thttp2_machine=HTTP2Machine0, flow=Flow, streams=Streams}, StreamID) ->\n\t{Data1, HTTP2Machine2} = case cow_http2_machine:ensure_window(Flow, HTTP2Machine0) of\n\t\tok -> {<<>>, HTTP2Machine0};\n\t\t{ok, Increment1, HTTP2Machine1} -> {cow_http2:window_update(Increment1), HTTP2Machine1}\n\tend,\n\t{Data2, HTTP2Machine} = case Streams of\n\t\t#{StreamID := #stream{flow=StreamFlow}} ->\n\t\t\tcase cow_http2_machine:ensure_window(StreamID, StreamFlow, HTTP2Machine2) of\n\t\t\t\tok ->\n\t\t\t\t\t{<<>>, HTTP2Machine2};\n\t\t\t\t{ok, Increment2, HTTP2Machine3} ->\n\t\t\t\t\t{cow_http2:window_update(StreamID, Increment2), HTTP2Machine3}\n\t\t\tend;\n\t\t_ ->\n\t\t\t%% Don't update the stream's window if it stopped.\n\t\t\t{<<>>, HTTP2Machine2}\n\tend,\n\tState = State0#state{http2_machine=HTTP2Machine},\n\tcase {Data1, Data2} of\n\t\t{<<>>, <<>>} ->\n\t\t\tState;\n\t\t_ ->\n\t\t\tok = maybe_socket_error(State, Transport:send(Socket, [Data1, Data2])),\n\t\t\tmaybe_reset_idle_timeout(State)\n\tend.\n\n%% Send the response, trailers or data.\n\nsend_response(State0=#state{http2_machine=HTTP2Machine0}, StreamID, StatusCode, Headers, Body) ->\n\tSize = case Body of\n\t\t{sendfile, _, Bytes, _} -> Bytes;\n\t\t_ -> iolist_size(Body)\n\tend,\n\tcase Size of\n\t\t0 ->\n\t\t\tState = send_headers(State0, StreamID, fin, StatusCode, Headers),\n\t\t\tmaybe_terminate_stream(State, StreamID, fin);\n\t\t_ ->\n\t\t\t%% @todo Add a test for HEAD to make sure we don't send the body when\n\t\t\t%% returning {response...} from a stream handler (or {headers...} then {data...}).\n\t\t\t{ok, _IsFin, HeaderBlock, HTTP2Machine}\n\t\t\t\t= cow_http2_machine:prepare_headers(StreamID, HTTP2Machine0, nofin,\n\t\t\t\t\t#{status => cow_http:status_to_integer(StatusCode)},\n\t\t\t\t\theaders_to_list(Headers)),\n\t\t\t{_, State} = maybe_send_data(State0#state{http2_machine=HTTP2Machine},\n\t\t\t\tStreamID, fin, Body, [cow_http2:headers(StreamID, nofin, HeaderBlock)]),\n\t\t\tState\n\tend.\n\nsend_headers(State0=#state{socket=Socket, transport=Transport,\n\t\thttp2_machine=HTTP2Machine0}, StreamID, IsFin0, StatusCode, Headers) ->\n\t{ok, IsFin, HeaderBlock, HTTP2Machine}\n\t\t= cow_http2_machine:prepare_headers(StreamID, HTTP2Machine0, IsFin0,\n\t\t\t#{status => cow_http:status_to_integer(StatusCode)},\n\t\t\theaders_to_list(Headers)),\n\tState = State0#state{http2_machine=HTTP2Machine},\n\tok = maybe_socket_error(State, Transport:send(Socket,\n\t\tcow_http2:headers(StreamID, IsFin, HeaderBlock))),\n\tState.\n\n%% The set-cookie header is special; we can only send one cookie per header.\nheaders_to_list(Headers0=#{<<\"set-cookie\">> := SetCookies}) ->\n\tHeaders = maps:to_list(maps:remove(<<\"set-cookie\">>, Headers0)),\n\tHeaders ++ [{<<\"set-cookie\">>, Value} || Value <- SetCookies];\nheaders_to_list(Headers) ->\n\tmaps:to_list(Headers).\n\nmaybe_send_data(State0=#state{socket=Socket, transport=Transport,\n\t\thttp2_machine=HTTP2Machine0}, StreamID, IsFin, Data0, Prefix) ->\n\tData = case is_tuple(Data0) of\n\t\tfalse -> {data, Data0};\n\t\ttrue -> Data0\n\tend,\n\tcase cow_http2_machine:send_or_queue_data(StreamID, HTTP2Machine0, IsFin, Data) of\n\t\t{ok, HTTP2Machine} ->\n\t\t\tState1 = State0#state{http2_machine=HTTP2Machine},\n\t\t\t%% If we have prefix data (like a HEADERS frame) we need to send it\n\t\t\t%% even if we do not send any DATA frames.\n\t\t\tWasDataSent = case Prefix of\n\t\t\t\t[] ->\n\t\t\t\t\tno_data_sent;\n\t\t\t\t_ ->\n\t\t\t\t\tok = maybe_socket_error(State1, Transport:send(Socket, Prefix)),\n\t\t\t\t\tdata_sent\n\t\t\tend,\n\t\t\tState = maybe_send_data_alarm(State1, HTTP2Machine0, StreamID),\n\t\t\t{WasDataSent, State};\n\t\t{send, SendData, HTTP2Machine} ->\n\t\t\tState = #state{http2_status=Status, streams=Streams}\n\t\t\t\t= send_data(State0#state{http2_machine=HTTP2Machine}, SendData, Prefix),\n\t\t\t%% Terminate the connection if we are closing and all streams have completed.\n\t\t\tif\n\t\t\t\tStatus =:= closing, Streams =:= #{} ->\n\t\t\t\t\tterminate(State, {stop, normal, 'The connection is going away.'});\n\t\t\t\ttrue ->\n\t\t\t\t\t{data_sent, maybe_send_data_alarm(State, HTTP2Machine0, StreamID)}\n\t\t\tend\n\tend.\n\nsend_data(State0=#state{socket=Socket, transport=Transport, opts=Opts}, SendData, Prefix) ->\n\t{Acc, State} = prepare_data(State0, SendData, [], Prefix),\n\t_ = [case Data of\n\t\t{sendfile, Offset, Bytes, Path} ->\n\t\t\t%% When sendfile is disabled we explicitly use the fallback.\n\t\t\t{ok, _} = maybe_socket_error(State,\n\t\t\t\tcase maps:get(sendfile, Opts, true) of\n\t\t\t\t\ttrue -> Transport:sendfile(Socket, Path, Offset, Bytes);\n\t\t\t\t\tfalse -> ranch_transport:sendfile(Transport, Socket, Path, Offset, Bytes, [])\n\t\t\t\tend\n\t\t\t),\n\t\t\tok;\n\t\t_ ->\n\t\t\tok = maybe_socket_error(State, Transport:send(Socket, Data))\n\tend || Data <- Acc],\n\tsend_data_terminate(State, SendData).\n\nsend_data_terminate(State, []) ->\n\tState;\nsend_data_terminate(State0, [{StreamID, IsFin, _}|Tail]) ->\n\tState = maybe_terminate_stream(State0, StreamID, IsFin),\n\tsend_data_terminate(State, Tail).\n\nprepare_data(State, [], Acc, []) ->\n\t{lists:reverse(Acc), State};\nprepare_data(State, [], Acc, Buffer) ->\n\t{lists:reverse([lists:reverse(Buffer)|Acc]), State};\nprepare_data(State0, [{StreamID, IsFin, SendData}|Tail], Acc0, Buffer0) ->\n\t{Acc, Buffer, State} = prepare_data(State0, StreamID, IsFin, SendData, Acc0, Buffer0),\n\tprepare_data(State, Tail, Acc, Buffer).\n\nprepare_data(State, _, _, [], Acc, Buffer) ->\n\t{Acc, Buffer, State};\nprepare_data(State0, StreamID, IsFin, [FrameData|Tail], Acc, Buffer) ->\n\tFrameIsFin = case Tail of\n\t\t[] -> IsFin;\n\t\t_ -> nofin\n\tend,\n\tcase prepare_data_frame(State0, StreamID, FrameIsFin, FrameData) of\n\t\t{{MoreData, Sendfile}, State} when is_tuple(Sendfile) ->\n\t\t\tcase Buffer of\n\t\t\t\t[] ->\n\t\t\t\t\tprepare_data(State, StreamID, IsFin, Tail,\n\t\t\t\t\t\t[Sendfile, MoreData|Acc], []);\n\t\t\t\t_ ->\n\t\t\t\t\tprepare_data(State, StreamID, IsFin, Tail,\n\t\t\t\t\t\t[Sendfile, lists:reverse([MoreData|Buffer])|Acc], [])\n\t\t\tend;\n\t\t{MoreData, State} ->\n\t\t\tprepare_data(State, StreamID, IsFin, Tail,\n\t\t\t\tAcc, [MoreData|Buffer])\n\tend.\n\nprepare_data_frame(State, StreamID, IsFin, {data, Data}) ->\n\t{cow_http2:data(StreamID, IsFin, Data),\n\t\tState};\nprepare_data_frame(State, StreamID, IsFin, Sendfile={sendfile, _, Bytes, _}) ->\n\t{{cow_http2:data_header(StreamID, IsFin, Bytes), Sendfile},\n\t\tState};\n%% The stream is terminated in cow_http2_machine:prepare_trailers.\nprepare_data_frame(State=#state{http2_machine=HTTP2Machine0},\n\t\tStreamID, nofin, {trailers, Trailers}) ->\n\t{ok, HeaderBlock, HTTP2Machine}\n\t\t= cow_http2_machine:prepare_trailers(StreamID, HTTP2Machine0, Trailers),\n\t{cow_http2:headers(StreamID, fin, HeaderBlock),\n\t\tState#state{http2_machine=HTTP2Machine}}.\n\n%% After we have sent or queued data we may need to set or clear an alarm.\n%% We do this by comparing the HTTP2Machine buffer state before/after for\n%% the relevant streams.\nmaybe_send_data_alarm(State=#state{opts=Opts, http2_machine=HTTP2Machine}, HTTP2Machine0, StreamID) ->\n\tConnBufferSizeBefore = cow_http2_machine:get_connection_local_buffer_size(HTTP2Machine0),\n\tConnBufferSizeAfter = cow_http2_machine:get_connection_local_buffer_size(HTTP2Machine),\n\t{ok, StreamBufferSizeBefore} = cow_http2_machine:get_stream_local_buffer_size(StreamID, HTTP2Machine0),\n\t%% When the stream ends up closed after it finished sending data,\n\t%% we do not want to trigger an alarm. We act as if the buffer\n\t%% size did not change.\n\tStreamBufferSizeAfter = case cow_http2_machine:get_stream_local_buffer_size(StreamID, HTTP2Machine) of\n\t\t{ok, BSA} -> BSA;\n\t\t{error, closed} -> StreamBufferSizeBefore\n\tend,\n\tMaxConnBufferSize = maps:get(max_connection_buffer_size, Opts, 16000000),\n\tMaxStreamBufferSize = maps:get(max_stream_buffer_size, Opts, 8000000),\n\t%% I do not want to document these internal events yet. I am not yet\n\t%% convinced it should be {alarm, Name, on|off} and not {internal_event, E}\n\t%% or something else entirely. Though alarms are probably right.\n\tif\n\t\tConnBufferSizeBefore >= MaxConnBufferSize, ConnBufferSizeAfter < MaxConnBufferSize ->\n\t\t\tconnection_alarm(State, connection_buffer_full, off);\n\t\tConnBufferSizeBefore < MaxConnBufferSize, ConnBufferSizeAfter >= MaxConnBufferSize ->\n\t\t\tconnection_alarm(State, connection_buffer_full, on);\n\t\tStreamBufferSizeBefore >= MaxStreamBufferSize, StreamBufferSizeAfter < MaxStreamBufferSize ->\n\t\t\tstream_alarm(State, StreamID, stream_buffer_full, off);\n\t\tStreamBufferSizeBefore < MaxStreamBufferSize, StreamBufferSizeAfter >= MaxStreamBufferSize ->\n\t\t\tstream_alarm(State, StreamID, stream_buffer_full, on);\n\t\ttrue ->\n\t\t\tState\n\tend.\n\nconnection_alarm(State0=#state{streams=Streams}, Name, Value) ->\n\tlists:foldl(fun(StreamID, State) ->\n\t\tstream_alarm(State, StreamID, Name, Value)\n\tend, State0, maps:keys(Streams)).\n\nstream_alarm(State, StreamID, Name, Value) ->\n\tinfo(State, StreamID, {alarm, Name, Value}).\n\n%% Terminate a stream or the connection.\n\n%% We may have to cancel streams even if we receive multiple\n%% GOAWAY frames as the LastStreamID value may be lower than\n%% the one previously received.\n%%\n%% We do not reset the idle timeout on send here. We already\n%% disabled it if we initiated shutdown; and we already reset\n%% it if the client sent a GOAWAY frame.\ngoaway(State0=#state{socket=Socket, transport=Transport, http2_machine=HTTP2Machine0,\n\t\thttp2_status=Status, streams=Streams0}, {goaway, LastStreamID, Reason, _})\n\t\twhen Status =:= connected; Status =:= closing_initiated; Status =:= closing ->\n\tStreams = goaway_streams(State0, maps:to_list(Streams0), LastStreamID,\n\t\t{stop, {goaway, Reason}, 'The connection is going away.'}, []),\n\tState1 = State0#state{streams=maps:from_list(Streams)},\n\tif\n\t\tStatus =:= connected; Status =:= closing_initiated ->\n\t\t\t{OurLastStreamID, HTTP2Machine} =\n\t\t\t\tcow_http2_machine:set_last_streamid(HTTP2Machine0),\n\t\t\tState = State1#state{http2_status=closing, http2_machine=HTTP2Machine},\n\t\t\tok = maybe_socket_error(State, Transport:send(Socket,\n\t\t\t\tcow_http2:goaway(OurLastStreamID, no_error, <<>>))),\n\t\t\tState;\n\t\ttrue ->\n\t\t\tState1\n\tend;\n%% We terminate the connection immediately if it hasn't fully been initialized.\ngoaway(State, {goaway, _, Reason, _}) ->\n\tterminate(State, {stop, {goaway, Reason}, 'The connection is going away.'}).\n\n%% Cancel client-initiated streams that are above LastStreamID.\ngoaway_streams(_, [], _, _, Acc) ->\n\tAcc;\ngoaway_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], LastStreamID, Reason, Acc)\n\t\twhen StreamID > LastStreamID, (StreamID rem 2) =:= 0 ->\n\tterminate_stream_handler(State, StreamID, Reason, StreamState),\n\tgoaway_streams(State, Tail, LastStreamID, Reason, Acc);\ngoaway_streams(State, [Stream|Tail], LastStreamID, Reason, Acc) ->\n\tgoaway_streams(State, Tail, LastStreamID, Reason, [Stream|Acc]).\n\n%% A server that is attempting to gracefully shut down a connection SHOULD send\n%% an initial GOAWAY frame with the last stream identifier set to 2^31-1 and a\n%% NO_ERROR code. This signals to the client that a shutdown is imminent and\n%% that initiating further requests is prohibited. After allowing time for any\n%% in-flight stream creation (at least one round-trip time), the server can send\n%% another GOAWAY frame with an updated last stream identifier. This ensures\n%% that a connection can be cleanly shut down without losing requests.\n\n-spec initiate_closing(#state{}, _) -> #state{}.\n\ninitiate_closing(State=#state{http2_status=connected, socket=Socket,\n\t\ttransport=Transport, opts=Opts}, Reason) ->\n\tok = maybe_socket_error(State, Transport:send(Socket,\n\t\tcow_http2:goaway(16#7fffffff, no_error, <<>>))),\n\tTimeout = maps:get(goaway_initial_timeout, Opts, 1000),\n\tMessage = {goaway_initial_timeout, Reason},\n\tset_timeout(State#state{http2_status=closing_initiated}, Timeout, Message);\ninitiate_closing(State=#state{http2_status=Status}, _Reason)\n\t\twhen Status =:= closing_initiated; Status =:= closing ->\n\t%% This happens if sys:terminate/2,3 is called twice or if the supervisor\n\t%% tells us to shutdown after sys:terminate/2,3 is called or vice versa.\n\tState;\ninitiate_closing(State, Reason) ->\n\tterminate(State, {stop, stop_reason(Reason), 'The connection is going away.'}).\n\n%% Switch to 'closing' state and stop accepting new streams.\n\n-spec closing(#state{}, Reason :: term()) -> #state{}.\n\nclosing(State=#state{streams=Streams}, Reason) when Streams =:= #{} ->\n\tterminate(State, Reason);\nclosing(State0=#state{http2_status=closing_initiated,\n\t\thttp2_machine=HTTP2Machine0, socket=Socket, transport=Transport},\n\t\tReason) ->\n\t%% Stop accepting new streams.\n\t{LastStreamID, HTTP2Machine} =\n\t\tcow_http2_machine:set_last_streamid(HTTP2Machine0),\n\tState = State0#state{http2_status=closing, http2_machine=HTTP2Machine},\n\tok = maybe_socket_error(State, Transport:send(Socket,\n\t\tcow_http2:goaway(LastStreamID, no_error, <<>>))),\n\tclosing(State, Reason);\nclosing(State=#state{http2_status=closing, opts=Opts}, Reason) ->\n\t%% If client sent GOAWAY, we may already be in 'closing' but without the\n\t%% goaway complete timeout set.\n\tTimeout = maps:get(goaway_complete_timeout, Opts, 3000),\n\tMessage = {goaway_complete_timeout, Reason},\n\tset_timeout(State, Timeout, Message).\n\nstop_reason({stop, Reason, _}) -> Reason;\nstop_reason(Reason) -> Reason.\n\n%% Function copied from cowboy_http.\nmaybe_socket_error(State, {error, closed}) ->\n\tterminate(State, {socket_error, closed, 'The socket has been closed.'});\nmaybe_socket_error(State, Reason) ->\n\tmaybe_socket_error(State, Reason, 'An error has occurred on the socket.').\n\nmaybe_socket_error(_, Result = ok, _) ->\n\tResult;\nmaybe_socket_error(_, Result = {ok, _}, _) ->\n\tResult;\nmaybe_socket_error(State, {error, Reason}, Human) ->\n\tterminate(State, {socket_error, Reason, Human}).\n\n-spec terminate(#state{} | undefined, _) -> no_return().\n\nterminate(undefined, Reason) ->\n\texit({shutdown, Reason});\nterminate(State=#state{socket=Socket, transport=Transport, http2_status=Status,\n\t\thttp2_machine=HTTP2Machine, streams=Streams, children=Children}, Reason)\n\t\twhen Status =:= connected; Status =:= closing_initiated; Status =:= closing ->\n\t%% @todo We might want to optionally send the Reason value\n\t%% as debug data in the GOAWAY frame here. Perhaps more.\n\tif\n\t\tStatus =:= connected; Status =:= closing_initiated ->\n\t\t\t%% We are terminating so it's OK if we can't send the GOAWAY anymore.\n\t\t\t_ = Transport:send(Socket, cow_http2:goaway(\n\t\t\t\tcow_http2_machine:get_last_streamid(HTTP2Machine),\n\t\t\t\tterminate_reason(Reason), <<>>));\n\t\t%% We already sent the GOAWAY frame.\n\t\tStatus =:= closing ->\n\t\t\tok\n\tend,\n\tterminate_all_streams(State, maps:to_list(Streams), Reason),\n\tcowboy_children:terminate(Children),\n\t%% @todo Don't linger on connection errors.\n\tterminate_linger(State),\n\texit({shutdown, Reason});\n%% We are not fully connected so we can just terminate the connection.\nterminate(_State, Reason) ->\n\texit({shutdown, Reason}).\n\nterminate_reason({connection_error, Reason, _}) -> Reason;\nterminate_reason({stop, _, _}) -> no_error;\nterminate_reason({socket_error, _, _}) -> internal_error;\nterminate_reason({internal_error, _, _}) -> internal_error.\n\nterminate_all_streams(_, [], _) ->\n\tok;\nterminate_all_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], Reason) ->\n\tterminate_stream_handler(State, StreamID, Reason, StreamState),\n\tterminate_all_streams(State, Tail, Reason).\n\n%% This code is copied from cowboy_http.\nterminate_linger(State=#state{socket=Socket, transport=Transport, opts=Opts}) ->\n\tcase Transport:shutdown(Socket, write) of\n\t\tok ->\n\t\t\tcase maps:get(linger_timeout, Opts, 1000) of\n\t\t\t\t0 ->\n\t\t\t\t\tok;\n\t\t\t\tinfinity ->\n\t\t\t\t\tterminate_linger_before_loop(State, undefined, Transport:messages());\n\t\t\t\tTimeout ->\n\t\t\t\t\tTimerRef = erlang:start_timer(Timeout, self(), linger_timeout),\n\t\t\t\t\tterminate_linger_before_loop(State, TimerRef, Transport:messages())\n\t\t\tend;\n\t\t{error, _} ->\n\t\t\tok\n\tend.\n\nterminate_linger_before_loop(State, TimerRef, Messages) ->\n\t%% We may already be in active mode when we do this\n\t%% but it's OK because we are shutting down anyway.\n\t%%\n\t%% We specially handle the socket error to terminate\n\t%% when an error occurs.\n\tcase setopts_active(State) of\n\t\tok ->\n\t\t\tterminate_linger_loop(State, TimerRef, Messages);\n\t\t{error, _} ->\n\t\t\tok\n\tend.\n\nterminate_linger_loop(State=#state{socket=Socket}, TimerRef, Messages) ->\n\treceive\n\t\t{OK, Socket, _} when OK =:= element(1, Messages) ->\n\t\t\tterminate_linger_loop(State, TimerRef, Messages);\n\t\t{Closed, Socket} when Closed =:= element(2, Messages) ->\n\t\t\tok;\n\t\t{Error, Socket, _} when Error =:= element(3, Messages) ->\n\t\t\tok;\n\t\t{Passive, Socket} when Passive =:= tcp_passive; Passive =:= ssl_passive ->\n\t\t\tterminate_linger_before_loop(State, TimerRef, Messages);\n\t\t{timeout, TimerRef, linger_timeout} ->\n\t\t\tok;\n\t\t_ ->\n\t\t\tterminate_linger_loop(State, TimerRef, Messages)\n\tend.\n\n%% @todo Don't send an RST_STREAM if one was already sent.\n%%\n%% When resetting the stream we are technically sending data\n%% on the socket. However due to implementation complexities\n%% we do not attempt to reset the idle timeout on send.\nreset_stream(State0=#state{socket=Socket, transport=Transport,\n\t\thttp2_machine=HTTP2Machine0}, StreamID, Error) ->\n\tReason = case Error of\n\t\t{internal_error, _, _} -> internal_error;\n\t\t{stream_error, Reason0, _} -> Reason0\n\tend,\n\tok = maybe_socket_error(State0, Transport:send(Socket,\n\t\tcow_http2:rst_stream(StreamID, Reason))),\n\tState1 = case cow_http2_machine:reset_stream(StreamID, HTTP2Machine0) of\n\t\t{ok, HTTP2Machine} ->\n\t\t\tterminate_stream(State0#state{http2_machine=HTTP2Machine}, StreamID, Error);\n\t\t{error, not_found} ->\n\t\t\tterminate_stream(State0, StreamID, Error)\n\tend,\n\tcase reset_rate(State1) of\n\t\t{ok, State} ->\n\t\t\tState;\n\t\terror ->\n\t\t\tterminate(State1, {connection_error, enhance_your_calm,\n\t\t\t\t'Stream reset rate larger than configuration allows. Flood? (CVE-2019-9514)'})\n\tend.\n\nreset_rate(State0=#state{reset_rate_num=Num0, reset_rate_time=Time}) ->\n\tcase Num0 - 1 of\n\t\t0 ->\n\t\t\tCurrentTime = erlang:monotonic_time(millisecond),\n\t\t\tif\n\t\t\t\tCurrentTime < Time ->\n\t\t\t\t\terror;\n\t\t\t\ttrue ->\n\t\t\t\t\t%% When the option has a period of infinity we cannot reach this clause.\n\t\t\t\t\t{ok, init_reset_rate_limiting(State0, CurrentTime)}\n\t\t\tend;\n\t\tNum ->\n\t\t\t{ok, State0#state{reset_rate_num=Num}}\n\tend.\n\nstop_stream(State=#state{http2_machine=HTTP2Machine}, StreamID) ->\n\tcase cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine) of\n\t\t%% When the stream terminates normally (without sending RST_STREAM)\n\t\t%% and no response was sent, we need to send a proper response back to the client.\n\t\t%% We delay the termination of the stream until the response is fully sent.\n\t\t{ok, idle, _} ->\n\t\t\tinfo(stopping(State, StreamID), StreamID, {response, 204, #{}, <<>>});\n\t\t%% When a response was sent but not terminated, we need to close the stream.\n\t\t%% We delay the termination of the stream until the response is fully sent.\n\t\t{ok, nofin, fin} ->\n\t\t\tstopping(State, StreamID);\n\t\t%% We only send a final DATA frame if there isn't one queued yet.\n\t\t{ok, nofin, _} ->\n\t\t\tinfo(stopping(State, StreamID), StreamID, {data, fin, <<>>});\n\t\t%% When a response was sent fully we can terminate the stream,\n\t\t%% regardless of the stream being in half-closed or closed state.\n\t\t_ ->\n\t\t\tterminate_stream(State, StreamID)\n\tend.\n\nstopping(State=#state{streams=Streams}, StreamID) ->\n\t#{StreamID := Stream} = Streams,\n\tState#state{streams=Streams#{StreamID => Stream#stream{status=stopping}}}.\n\n%% If we finished sending data and the stream is stopping, terminate it.\nmaybe_terminate_stream(State=#state{streams=Streams}, StreamID, fin) ->\n\tcase Streams of\n\t\t#{StreamID := #stream{status=stopping}} ->\n\t\t\tterminate_stream(State, StreamID);\n\t\t_ ->\n\t\t\tState\n\tend;\nmaybe_terminate_stream(State, _, _) ->\n\tState.\n\n%% When the stream stops normally without reading the request\n%% body fully we need to tell the client to stop sending it.\n%% We do this by sending an RST_STREAM with reason NO_ERROR. (RFC7540 8.1.0)\nterminate_stream(State0=#state{socket=Socket, transport=Transport,\n\t\thttp2_machine=HTTP2Machine0}, StreamID) ->\n\tState = case cow_http2_machine:get_stream_local_state(StreamID, HTTP2Machine0) of\n\t\t{ok, fin, _} ->\n\t\t\tok = maybe_socket_error(State0, Transport:send(Socket,\n\t\t\t\tcow_http2:rst_stream(StreamID, no_error))),\n\t\t\t{ok, HTTP2Machine} = cow_http2_machine:reset_stream(StreamID, HTTP2Machine0),\n\t\t\tState0#state{http2_machine=HTTP2Machine};\n\t\t{error, closed} ->\n\t\t\tState0\n\tend,\n\tterminate_stream(State, StreamID, normal).\n\n%% We remove the stream flow from the connection flow. Any further\n%% data received for this stream is therefore fully contained within\n%% the extra window we allocated for this stream.\nterminate_stream(State=#state{flow=Flow, streams=Streams0, children=Children0}, StreamID, Reason) ->\n\tcase maps:take(StreamID, Streams0) of\n\t\t{#stream{flow=StreamFlow, state=StreamState}, Streams} ->\n\t\t\tterminate_stream_handler(State, StreamID, Reason, StreamState),\n\t\t\tChildren = cowboy_children:shutdown(Children0, StreamID),\n\t\t\tState#state{flow=Flow - StreamFlow, streams=Streams, children=Children};\n\t\terror ->\n\t\t\tState\n\tend.\n\nterminate_stream_handler(#state{opts=Opts}, StreamID, Reason, StreamState) ->\n\ttry\n\t\tcowboy_stream:terminate(StreamID, Reason, StreamState)\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(terminate,\n\t\t\t[StreamID, Reason, StreamState],\n\t\t\tClass, Exception, Stacktrace), Opts)\n\tend.\n\n%% System callbacks.\n\n-spec system_continue(_, _, {#state{}, binary()}) -> no_return().\n\nsystem_continue(_, _, {State, Buffer}) ->\n\tbefore_loop(State, Buffer).\n\n-spec system_terminate(any(), _, _, {#state{}, binary()}) -> no_return().\n\nsystem_terminate(Reason0, _, _, {State, Buffer}) ->\n\tReason = {stop, {exit, Reason0}, 'sys:terminate/2,3 was called.'},\n\tbefore_loop(initiate_closing(State, Reason), Buffer).\n\n-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::{#state{}, binary()}.\n\nsystem_code_change(Misc, _, _, _) ->\n\t{ok, Misc}.\n"
  },
  {
    "path": "src/cowboy_http3.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% A key difference between cowboy_http2 and cowboy_http3\n%% is that HTTP/3 streams are QUIC streams and therefore\n%% much of the connection state is handled outside of\n%% Cowboy.\n\n-module(cowboy_http3).\n\n-ifdef(COWBOY_QUICER).\n\n-export([init/4]).\n\n%% Temporary callback to do sendfile over QUIC.\n-export([send/2]).\n\n%% @todo Graceful shutdown? Linger? Timeouts? Frame rates? PROXY header?\n-type opts() :: #{\n\tcompress_buffering => boolean(),\n\tcompress_threshold => non_neg_integer(),\n\tconnection_type => worker | supervisor,\n\tenable_connect_protocol => boolean(),\n\tenv => cowboy_middleware:env(),\n\tlogger => module(),\n\tmax_decode_blocked_streams => 0..16#3fffffffffffffff,\n\tmax_decode_table_size => 0..16#3fffffffffffffff,\n\tmax_encode_blocked_streams => 0..16#3fffffffffffffff,\n\tmax_encode_table_size => 0..16#3fffffffffffffff,\n\tmax_ignored_frame_size_received => non_neg_integer() | infinity,\n\tmetrics_callback => cowboy_metrics_h:metrics_callback(),\n\tmetrics_req_filter => fun((cowboy_req:req()) -> map()),\n\tmetrics_resp_headers_filter => fun((cowboy:http_headers()) -> cowboy:http_headers()),\n\tmiddlewares => [module()],\n\tshutdown_timeout => timeout(),\n\tstream_handlers => [module()],\n\ttracer_callback => cowboy_tracer_h:tracer_callback(),\n\ttracer_flags => [atom()],\n\ttracer_match_specs => cowboy_tracer_h:tracer_match_specs(),\n\t%% Open ended because configured stream handlers might add options.\n\t_ => _\n}.\n-export_type([opts/0]).\n\n%% HTTP/3 or WebTransport stream.\n%%\n%% WebTransport sessions involve one bidirectional CONNECT stream\n%% that must stay open (and can be used for signaling using the\n%% Capsule Protocol) and an application-defined number of\n%% unidirectional and bidirectional streams, as well as datagrams.\n%%\n%% WebTransport sessions run in the CONNECT request process and\n%% all events related to the session is sent there as a message.\n%% The pid of the process is kept in the state.\n-record(stream, {\n\tid :: cow_http3:stream_id(),\n\n\t%% Whether the stream is currently in a special state.\n\tstatus :: header | {unidi, control | encoder | decoder}\n\t\t| normal | {data | ignore, non_neg_integer()} | stopping\n\t\t| {relaying, normal | {data, non_neg_integer()}, pid()}\n\t\t| {webtransport_session, normal | {ignore, non_neg_integer()}}\n\t\t| {webtransport_stream, cow_http3:stream_id()},\n\n\t%% Stream buffer.\n\tbuffer = <<>> :: binary(),\n\n\t%% Stream state.\n\tstate = undefined :: undefined | {module(), any()}\n}).\n\n-record(state, {\n\tparent :: pid(),\n\tref :: ranch:ref(),\n\tconn :: cowboy_quicer:quicer_connection_handle(),\n\topts = #{} :: opts(),\n\n\t%% Remote address and port for the connection.\n\tpeer = undefined :: {inet:ip_address(), inet:port_number()},\n\n\t%% Local address and port for the connection.\n\tsock = undefined :: {inet:ip_address(), inet:port_number()},\n\n\t%% Client certificate.\n\tcert :: undefined | binary(),\n\n\t%% HTTP/3 state machine.\n\thttp3_machine :: cow_http3_machine:http3_machine(),\n\n\t%% Specially handled local unidi streams.\n\tlocal_control_id = undefined :: undefined | cow_http3:stream_id(),\n\tlocal_encoder_id = undefined :: undefined | cow_http3:stream_id(),\n\tlocal_decoder_id = undefined :: undefined | cow_http3:stream_id(),\n\n\t%% Bidirectional streams used for requests and responses,\n\t%% as well as unidirectional streams initiated by the client.\n\tstreams = #{} :: #{cow_http3:stream_id() => #stream{}},\n\n\t%% Lingering streams that were recently reset. We may receive\n\t%% pending data or messages for these streams a short while\n\t%% after they have been reset.\n\tlingering_streams = [] :: [non_neg_integer()],\n\n\t%% Streams can spawn zero or more children which are then managed\n\t%% by this module if operating as a supervisor.\n\tchildren = cowboy_children:init() :: cowboy_children:children()\n}).\n\n-spec init(pid(), ranch:ref(), cowboy_quicer:quicer_connection_handle(), opts())\n\t-> no_return().\n\ninit(Parent, Ref, Conn, Opts) ->\n\t{ok, SettingsBin, HTTP3Machine0} = cow_http3_machine:init(server, Opts),\n\t%% Immediately open a control, encoder and decoder stream.\n\t%% @todo An endpoint MAY avoid creating an encoder stream if it will not be used (for example, if its encoder does not wish to use the dynamic table or if the maximum size of the dynamic table permitted by the peer is zero).\n\t%% @todo An endpoint MAY avoid creating a decoder stream if its decoder sets the maximum capacity of the dynamic table to zero.\n\t{ok, ControlID} = maybe_socket_error(undefined,\n\t\tcowboy_quicer:start_unidi_stream(Conn, [<<0>>, SettingsBin]),\n\t\t'A socket error occurred when opening the control stream.'),\n\t{ok, EncoderID} = maybe_socket_error(undefined,\n\t\tcowboy_quicer:start_unidi_stream(Conn, <<2>>),\n\t\t'A socket error occurred when opening the encoder stream.'),\n\t{ok, DecoderID} = maybe_socket_error(undefined,\n\t\tcowboy_quicer:start_unidi_stream(Conn, <<3>>),\n\t\t'A socket error occurred when opening the encoder stream.'),\n\t%% Set the control, encoder and decoder streams in the machine.\n\tHTTP3Machine = cow_http3_machine:init_unidi_local_streams(\n\t\tControlID, EncoderID, DecoderID, HTTP3Machine0),\n\t%% Get the peername/sockname/cert.\n\t{ok, Peer} = maybe_socket_error(undefined, cowboy_quicer:peername(Conn),\n\t\t'A socket error occurred when retrieving the peer name.'),\n\t{ok, Sock} = maybe_socket_error(undefined, cowboy_quicer:sockname(Conn),\n\t\t'A socket error occurred when retrieving the sock name.'),\n\tCertResult = case cowboy_quicer:peercert(Conn) of\n\t\t{error, no_peercert} ->\n\t\t\t{ok, undefined};\n\t\tCert0 ->\n\t\t\tCert0\n\tend,\n\t{ok, Cert} = maybe_socket_error(undefined, CertResult,\n\t\t'A socket error occurred when retrieving the client TLS certificate.'),\n\t%% Quick! Let's go!\n\tloop(#state{parent=Parent, ref=Ref, conn=Conn,\n\t\topts=Opts, peer=Peer, sock=Sock, cert=Cert,\n\t\thttp3_machine=HTTP3Machine, local_control_id=ControlID,\n\t\tlocal_encoder_id=EncoderID, local_decoder_id=DecoderID}).\n\nloop(State0=#state{opts=Opts, children=Children}) ->\n\treceive\n\t\tMsg when element(1, Msg) =:= quic ->\n\t\t\thandle_quic_msg(State0, Msg);\n\t\t%% Timeouts.\n\t\t{timeout, Ref, {shutdown, Pid}} ->\n\t\t\tcowboy_children:shutdown_timeout(Children, Ref, Pid),\n\t\t\tloop(State0);\n\t\t%% Messages pertaining to a stream.\n\t\t{{Pid, StreamID}, Msg} when Pid =:= self() ->\n\t\t\tloop(info(State0, StreamID, Msg));\n\t\t{'$cowboy_relay_command', {Pid, StreamID}, RelayCommand} when Pid =:= self() ->\n\t\t\tloop(relay_command(State0, StreamID, RelayCommand));\n\t\t%% WebTransport commands.\n\t\t{'$webtransport_commands', SessionID, Commands} ->\n\t\t\tloop(webtransport_commands(State0, SessionID, Commands));\n\t\t%% Exit signal from children.\n\t\tMsg = {'EXIT', Pid, _} ->\n\t\t\tloop(down(State0, Pid, Msg));\n\t\tMsg ->\n\t\t\tcowboy:log(warning, \"Received stray message ~p.\", [Msg], Opts),\n\t\t\tloop(State0)\n\tend.\n\nhandle_quic_msg(State0=#state{opts=Opts}, Msg) ->\n\tcase cowboy_quicer:handle(Msg) of\n\t\t{data, StreamID, IsFin, Data} ->\n\t\t\tparse(State0, StreamID, Data, IsFin);\n\t\t{datagram, Data} ->\n\t\t\tparse_datagram(State0, Data);\n\t\t{stream_started, StreamID, StreamType} ->\n\t\t\tState = stream_new_remote(State0, StreamID, StreamType),\n\t\t\tloop(State);\n\t\t{stream_closed, StreamID, ErrorCode} ->\n\t\t\tState = stream_closed(State0, StreamID, ErrorCode),\n\t\t\tloop(State);\n\t\t{peer_send_shutdown, StreamID} ->\n\t\t\tState = stream_peer_send_shutdown(State0, StreamID),\n\t\t\tloop(State);\n\t\tclosed ->\n\t\t\t%% @todo Different error reason if graceful?\n\t\t\tReason = {socket_error, closed, 'The socket has been closed.'},\n\t\t\tterminate(State0, Reason);\n\t\tok ->\n\t\t\tloop(State0);\n\t\tunknown ->\n\t\t\tcowboy:log(warning, \"Received unknown QUIC message ~p.\", [Msg], Opts),\n\t\t\tloop(State0);\n\t\t{socket_error, Reason} ->\n\t\t\tterminate(State0, {socket_error, Reason,\n\t\t\t\t'An error has occurred on the socket.'})\n\tend.\n\nparse(State=#state{opts=Opts}, StreamID, Data, IsFin) ->\n\tcase stream_get(State, StreamID) of\n\t\tStream=#stream{buffer= <<>>} ->\n\t\t\tparse1(State, Stream, Data, IsFin);\n\t\tStream=#stream{buffer=Buffer} ->\n\t\t\tStream1 = Stream#stream{buffer= <<>>},\n\t\t\tparse1(stream_store(State, Stream1),\n\t\t\t\tStream1, <<Buffer/binary, Data/binary>>, IsFin);\n\t\t%% Pending data for a stream that has been reset. Ignore.\n\t\terror ->\n\t\t\tcase is_lingering_stream(State, StreamID) of\n\t\t\t\ttrue ->\n\t\t\t\t\tok;\n\t\t\t\tfalse ->\n\t\t\t\t\t%% We avoid logging the data as it could be quite large.\n\t\t\t\t\tcowboy:log(warning, \"Received data for unknown stream ~p.\",\n\t\t\t\t\t\t[StreamID], Opts)\n\t\t\tend,\n\t\t\tloop(State)\n\tend.\n\nparse1(State, Stream=#stream{status=header}, Data, IsFin) ->\n\tparse_unidirectional_stream_header(State, Stream, Data, IsFin);\nparse1(State=#state{http3_machine=HTTP3Machine0},\n\t\t#stream{status={unidi, Type}, id=StreamID}, Data, IsFin)\n\t\twhen Type =:= encoder; Type =:= decoder ->\n\tcase cow_http3_machine:unidi_data(Data, IsFin, StreamID, HTTP3Machine0) of\n\t\t{ok, Instrs, HTTP3Machine} ->\n\t\t\tloop(send_instructions(State#state{http3_machine=HTTP3Machine}, Instrs));\n\t\t{error, Error={connection_error, _, _}, HTTP3Machine} ->\n\t\t\tterminate(State#state{http3_machine=HTTP3Machine}, Error)\n\tend;\n%% @todo Handle when IsFin = fin which must terminate the WT session.\nparse1(State=#state{conn=Conn}, Stream=#stream{id=SessionID, status=\n\t\t{webtransport_session, normal}}, Data, IsFin) ->\n\tcase cow_capsule:parse(Data) of\n\t\t{ok, wt_drain_session, Rest} ->\n\t\t\twebtransport_event(State, SessionID, close_initiated),\n\t\t\tparse1(State, Stream, Rest, IsFin);\n\t\t{ok, {wt_close_session, AppCode, AppMsg}, Rest} ->\n\t\t\t%% This event will be handled specially and lead\n\t\t\t%% to the termination of the session process.\n\t\t\twebtransport_event(State, SessionID, {closed, AppCode, AppMsg}),\n\t\t\t%% Shutdown the CONNECT stream immediately.\n\t\t\tcowboy_quicer:shutdown_stream(Conn, SessionID),\n\t\t\t%% @todo Will we receive a {stream_closed,...} after that?\n\t\t\t%% If any data is received past that point this is an error.\n\t\t\t%% @todo Don't crash, error out properly.\n\t\t\t<<>> = Rest,\n\t\t\tloop(webtransport_terminate_session(State, Stream));\n\t\tmore ->\n\t\t\tloop(stream_store(State, Stream#stream{buffer=Data}));\n\t\t%% Ignore unhandled/unknown capsules.\n\t\t%% @todo Do this when cow_capsule includes some.\n%\t\t{ok, _, Rest} ->\n%\t\t\tparse1(State, Stream, Rest, IsFin);\n%\t\t{ok, Rest} ->\n%\t\t\tparse1(State, Stream, Rest, IsFin);\n\t\t%% @todo Make the max length configurable?\n\t\t{skip, Len} when Len =< 8192 ->\n\t\t\tloop(stream_store(State, Stream#stream{\n\t\t\t\tstatus={webtransport_session, {ignore, Len}}}));\n\t\t{skip, Len} ->\n\t\t\t%% @todo What should be done on capsule error?\n\t\t\terror({todo, capsule_too_long, Len});\n\t\terror ->\n\t\t\t%% @todo What should be done on capsule error?\n\t\t\terror({todo, capsule_error, Data})\n\tend;\nparse1(State, Stream=#stream{status=\n\t\t{webtransport_session, {ignore, Len}}}, Data, IsFin) ->\n\tcase Data of\n\t\t<<_:Len/unit:8, Rest/bits>> ->\n\t\t\tparse1(State, Stream#stream{status={webtransport_session, normal}}, Rest, IsFin);\n\t\t_ ->\n\t\t\tloop(stream_store(State, Stream#stream{\n\t\t\t\tstatus={webtransport_session, {ignore, Len - byte_size(Data)}}}))\n\tend;\nparse1(State, #stream{id=StreamID, status={webtransport_stream, SessionID}}, Data, IsFin) ->\n\twebtransport_event(State, SessionID, {stream_data, StreamID, IsFin, Data}),\n\t%% No need to store the stream again, WT streams don't get changed here.\n\tloop(State);\nparse1(State, Stream=#stream{status={data, Len}, id=StreamID}, Data, IsFin) ->\n\tDataLen = byte_size(Data),\n\tif\n\t\tDataLen < Len ->\n\t\t\t%% We don't have the full frame but this is the end of the\n\t\t\t%% data we have. So FrameIsFin is equivalent to IsFin here.\n\t\t\tloop(frame(State, Stream#stream{status={data, Len - DataLen}}, {data, Data}, IsFin));\n\t\ttrue ->\n\t\t\t<<Data1:Len/binary, Rest/bits>> = Data,\n\t\t\tFrameIsFin = is_fin(IsFin, Rest),\n\t\t\tparse(frame(State, Stream#stream{status=normal}, {data, Data1}, FrameIsFin),\n\t\t\t\tStreamID, Rest, IsFin)\n\tend;\n%% This clause mirrors the {data, Len} clause.\nparse1(State, Stream=#stream{status={relaying, {data, Len}, RelayPid}, id=StreamID},\n\t\tData, IsFin) ->\n\tDataLen = byte_size(Data),\n\tif\n\t\tDataLen < Len ->\n\t\t\t%% We don't have the full frame but this is the end of the\n\t\t\t%% data we have. So FrameIsFin is equivalent to IsFin here.\n\t\t\tloop(frame(State, Stream#stream{status={relaying, {data, Len - DataLen}, RelayPid}},\n\t\t\t\t{data, Data}, IsFin));\n\t\ttrue ->\n\t\t\t<<Data1:Len/binary, Rest/bits>> = Data,\n\t\t\tFrameIsFin = is_fin(IsFin, Rest),\n\t\t\tparse(frame(State, Stream#stream{status={relaying, normal, RelayPid}},\n\t\t\t\t{data, Data1}, FrameIsFin), StreamID, Rest, IsFin)\n\tend;\nparse1(State, Stream=#stream{status={ignore, Len}, id=StreamID}, Data, IsFin) ->\n\tDataLen = byte_size(Data),\n\tif\n\t\tDataLen < Len ->\n\t\t\tloop(stream_store(State, Stream#stream{status={ignore, Len - DataLen}}));\n\t\ttrue ->\n\t\t\t<<_:Len/binary, Rest/bits>> = Data,\n\t\t\tparse(stream_store(State, Stream#stream{status=normal}),\n\t\t\t\tStreamID, Rest, IsFin)\n\tend;\n%% @todo Clause that discards receiving data for stopping streams.\n%%       We may receive a few more frames after we abort receiving.\nparse1(State=#state{opts=Opts}, Stream=#stream{status=Status0, id=StreamID}, Data, IsFin) ->\n\tcase cow_http3:parse(Data) of\n\t\t{ok, Frame, Rest} ->\n\t\t\tFrameIsFin = is_fin(IsFin, Rest),\n\t\t\tparse(frame(State, Stream, Frame, FrameIsFin), StreamID, Rest, IsFin);\n\t\t%% The WebTransport stream header is not a real frame.\n\t\t{webtransport_stream_header, SessionID, Rest} ->\n\t\t\tbecome_webtransport_stream(State, Stream, bidi, SessionID, Rest, IsFin);\n\t\t{more, Frame = {data, _}, Len} ->\n\t\t\t%% We're at the end of the data so FrameIsFin is equivalent to IsFin.\n\t\t\tcase IsFin of\n\t\t\t\tnofin when element(1, Status0) =:= relaying ->\n\t\t\t\t\t%% The stream will be stored at the end of processing commands.\n\t\t\t\t\tStatus = setelement(2, Status0, {data, Len}),\n\t\t\t\t\tloop(frame(State, Stream#stream{status=Status}, Frame, nofin));\n\t\t\t\tnofin ->\n\t\t\t\t\t%% The stream will be stored at the end of processing commands.\n\t\t\t\t\tloop(frame(State, Stream#stream{status={data, Len}}, Frame, nofin));\n\t\t\t\tfin ->\n\t\t\t\t\tterminate(State, {connection_error, h3_frame_error,\n\t\t\t\t\t\t'Last frame on stream was truncated. (RFC9114 7.1)'})\n\t\t\tend;\n\t\t{more, ignore, Len} ->\n\t\t\t%% @todo This setting should be tested.\n\t\t\t%%\n\t\t\t%% While the default value doesn't warrant doing a streaming ignore\n\t\t\t%% (and could work just fine with the 'more' clause), this value\n\t\t\t%% is configurable and users may want to set it large.\n\t\t\tMaxIgnoredLen = maps:get(max_ignored_frame_size_received, Opts, 16384),\n\t\t\t%% We're at the end of the data so FrameIsFin is equivalent to IsFin.\n\t\t\tcase IsFin of\n\t\t\t\tnofin when Len < MaxIgnoredLen ->\n\t\t\t\t\t%% We are not processing commands so we must store the stream.\n\t\t\t\t\t%% We also call ignored_frame here; we will not need to call\n\t\t\t\t\t%% it again when ignoring the rest of the data.\n\t\t\t\t\tStream1 = Stream#stream{status={ignore, Len}},\n\t\t\t\t\tState1 = ignored_frame(State, Stream1),\n\t\t\t\t\tloop(stream_store(State1, Stream1));\n\t\t\t\tnofin ->\n\t\t\t\t\tterminate(State, {connection_error, h3_excessive_load,\n\t\t\t\t\t\t'Ignored frame larger than limit. (RFC9114 10.5)'});\n\t\t\t\tfin ->\n\t\t\t\t\tterminate(State, {connection_error, h3_frame_error,\n\t\t\t\t\t\t'Last frame on stream was truncated. (RFC9114 7.1)'})\n\t\t\tend;\n\t\t{ignore, Rest} ->\n\t\t\tparse(ignored_frame(State, Stream), StreamID, Rest, IsFin);\n\t\tError = {connection_error, _, _} ->\n\t\t\tterminate(State, Error);\n\t\tmore when Data =:= <<>> ->\n\t\t\t%% The buffer was already reset to <<>>.\n\t\t\tloop(stream_store(State, Stream));\n\t\tmore ->\n\t\t\t%% We're at the end of the data so FrameIsFin is equivalent to IsFin.\n\t\t\tcase IsFin of\n\t\t\t\tnofin ->\n\t\t\t\t\tloop(stream_store(State, Stream#stream{buffer=Data}));\n\t\t\t\tfin ->\n\t\t\t\t\tterminate(State, {connection_error, h3_frame_error,\n\t\t\t\t\t\t'Last frame on stream was truncated. (RFC9114 7.1)'})\n\t\t\tend\n\tend.\n\n%% We may receive multiple frames in a single QUIC packet.\n%% The FIN flag applies to the QUIC packet, not to the frame.\n%% We must therefore only consider the frame to have a FIN\n%% flag if there's no data remaining to be read.\nis_fin(fin, <<>>) -> fin;\nis_fin(_, _) -> nofin.\n\nparse_unidirectional_stream_header(State0=#state{http3_machine=HTTP3Machine0},\n\t\tStream0=#stream{id=StreamID}, Data, IsFin) ->\n\tcase cow_http3:parse_unidi_stream_header(Data) of\n\t\t{ok, Type, Rest} when Type =:= control; Type =:= encoder; Type =:= decoder ->\n\t\t\tcase cow_http3_machine:set_unidi_remote_stream_type(\n\t\t\t\t\tStreamID, Type, HTTP3Machine0) of\n\t\t\t\t{ok, HTTP3Machine} ->\n\t\t\t\t\tState = State0#state{http3_machine=HTTP3Machine},\n\t\t\t\t\tStream = Stream0#stream{status={unidi, Type}},\n\t\t\t\t\tparse(stream_store(State, Stream), StreamID, Rest, IsFin);\n\t\t\t\t{error, Error={connection_error, _, _}, HTTP3Machine} ->\n\t\t\t\t\tterminate(State0#state{http3_machine=HTTP3Machine}, Error)\n\t\t\tend;\n\t\t%% @todo Perhaps do this in cow_http3_machine directly.\n\t\t{ok, push, _} ->\n\t\t\tterminate(State0, {connection_error, h3_stream_creation_error,\n\t\t\t\t'Only servers can push. (RFC9114 6.2.2)'});\n\t\t{ok, {webtransport, SessionID}, Rest} ->\n\t\t\tbecome_webtransport_stream(State0, Stream0, unidi, SessionID, Rest, IsFin);\n\t\t%% Unknown stream types must be ignored. We choose to abort the\n\t\t%% stream instead of reading and discarding the incoming data.\n\t\t{undefined, _} ->\n\t\t\tloop(stream_abort_receive(State0, Stream0, h3_stream_creation_error));\n\t\t%% Very unlikely to happen but WebTransport headers may be fragmented\n\t\t%% as they are more than one byte. The fin flag in this case is an error,\n\t\t%% but because it happens in WebTransport application data (the Session ID)\n\t\t%% we only reset the impacted stream and not the entire connection.\n\t\tmore when IsFin =:= fin ->\n\t\t\tloop(stream_abort_receive(State0, Stream0, h3_stream_creation_error));\n\t\tmore ->\n\t\t\tloop(stream_store(State0, Stream0#stream{buffer=Data}))\n\tend.\n\nframe(State=#state{http3_machine=HTTP3Machine0},\n\t\tStream=#stream{id=StreamID}, Frame, IsFin) ->\n\tcase cow_http3_machine:frame(Frame, IsFin, StreamID, HTTP3Machine0) of\n\t\t{ok, HTTP3Machine} ->\n\t\t\tState#state{http3_machine=HTTP3Machine};\n\t\t{ok, {data, Data}, HTTP3Machine} ->\n\t\t\tdata_frame(State#state{http3_machine=HTTP3Machine}, Stream, IsFin, Data);\n\t\t{ok, {headers, Headers, PseudoHeaders, BodyLen}, Instrs, HTTP3Machine} ->\n\t\t\theaders_frame(send_instructions(State#state{http3_machine=HTTP3Machine}, Instrs),\n\t\t\t\tStream, IsFin, Headers, PseudoHeaders, BodyLen);\n\t\t{ok, {trailers, _Trailers}, Instrs, HTTP3Machine} ->\n\t\t\t%% @todo Propagate trailers.\n\t\t\tsend_instructions(State#state{http3_machine=HTTP3Machine}, Instrs);\n\t\t{ok, GoAway={goaway, _}, HTTP3Machine} ->\n\t\t\tgoaway(State#state{http3_machine=HTTP3Machine}, GoAway);\n\t\t{error, Error={stream_error, _Reason, _Human}, Instrs, HTTP3Machine} ->\n\t\t\tState1 = send_instructions(State#state{http3_machine=HTTP3Machine}, Instrs),\n\t\t\treset_stream(State1, Stream, Error);\n\t\t{error, Error={connection_error, _, _}, HTTP3Machine} ->\n\t\t\tterminate(State#state{http3_machine=HTTP3Machine}, Error)\n\tend.\n\ndata_frame(State, Stream=#stream{status={relaying, _, RelayPid}, id=StreamID}, IsFin, Data) ->\n\tRelayPid ! {'$cowboy_relay_data', {self(), StreamID}, IsFin, Data},\n\tstream_store(State, Stream);\ndata_frame(State=#state{opts=Opts},\n\t\tStream=#stream{id=StreamID, state=StreamState0}, IsFin, Data) ->\n\ttry cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of\n\t\t{Commands, StreamState} ->\n\t\t\tcommands(State, Stream#stream{state=StreamState}, Commands)\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(data,\n\t\t\t[StreamID, IsFin, Data, StreamState0],\n\t\t\tClass, Exception, Stacktrace), Opts),\n\t\treset_stream(State, Stream, {internal_error, {Class, Exception},\n\t\t\t'Unhandled exception in cowboy_stream:data/4.'})\n\tend.\n\nheaders_frame(State, Stream, IsFin, Headers,\n\t\tPseudoHeaders=#{method := <<\"CONNECT\">>}, _)\n\t\twhen map_size(PseudoHeaders) =:= 2 ->\n\tearly_error(State, Stream, IsFin, Headers, PseudoHeaders, 501,\n\t\t'The CONNECT method is currently not implemented. (RFC7231 4.3.6)');\nheaders_frame(State, Stream, IsFin, Headers,\n\t\tPseudoHeaders=#{method := <<\"TRACE\">>}, _) ->\n\tearly_error(State, Stream, IsFin, Headers, PseudoHeaders, 501,\n\t\t'The TRACE method is currently not implemented. (RFC9114 4.4, RFC7231 4.3.8)');\nheaders_frame(State, Stream, IsFin, Headers, PseudoHeaders=#{authority := Authority}, BodyLen) ->\n\theaders_frame_parse_host(State, Stream, IsFin, Headers, PseudoHeaders, BodyLen, Authority);\nheaders_frame(State, Stream, IsFin, Headers, PseudoHeaders, BodyLen) ->\n\tcase lists:keyfind(<<\"host\">>, 1, Headers) of\n\t\t{_, Authority} ->\n\t\t\theaders_frame_parse_host(State, Stream, IsFin, Headers, PseudoHeaders, BodyLen, Authority);\n\t\t_ ->\n\t\t\treset_stream(State, Stream, {stream_error, h3_message_error,\n\t\t\t\t'Requests translated from HTTP/1.1 must include a host header. (RFC7540 8.1.2.3, RFC7230 5.4)'})\n\tend.\n\nheaders_frame_parse_host(State=#state{ref=Ref, peer=Peer, sock=Sock, cert=Cert},\n\t\tStream=#stream{id=StreamID}, IsFin, Headers,\n\t\tPseudoHeaders=#{method := Method, scheme := Scheme, path := PathWithQs},\n\t\tBodyLen, Authority) ->\n\ttry cow_http_hd:parse_host(Authority) of\n\t\t{Host, Port0} ->\n\t\t\tPort = ensure_port(Scheme, Port0),\n\t\t\ttry cow_http:parse_fullpath(PathWithQs) of\n\t\t\t\t{<<>>, _} ->\n\t\t\t\t\treset_stream(State, Stream, {stream_error, h3_message_error,\n\t\t\t\t\t\t'The path component must not be empty. (RFC7540 8.1.2.3)'});\n\t\t\t\t{Path, Qs} ->\n\t\t\t\t\tReq0 = #{\n\t\t\t\t\t\tref => Ref,\n\t\t\t\t\t\tpid => self(),\n\t\t\t\t\t\tstreamid => StreamID,\n\t\t\t\t\t\tpeer => Peer,\n\t\t\t\t\t\tsock => Sock,\n\t\t\t\t\t\tcert => Cert,\n\t\t\t\t\t\tmethod => Method,\n\t\t\t\t\t\tscheme => Scheme,\n\t\t\t\t\t\thost => Host,\n\t\t\t\t\t\tport => Port,\n\t\t\t\t\t\tpath => Path,\n\t\t\t\t\t\tqs => Qs,\n\t\t\t\t\t\tversion => 'HTTP/3',\n\t\t\t\t\t\theaders => headers_to_map(Headers, #{}),\n\t\t\t\t\t\thas_body => IsFin =:= nofin,\n\t\t\t\t\t\tbody_length => BodyLen\n\t\t\t\t\t},\n\t\t\t\t\t%% We add the protocol information for extended CONNECTs.\n\t\t\t\t\tReq = case PseudoHeaders of\n\t\t\t\t\t\t#{protocol := Protocol} -> Req0#{protocol => Protocol};\n\t\t\t\t\t\t_ -> Req0\n\t\t\t\t\tend,\n\t\t\t\t\theaders_frame(State, Stream, Req)\n\t\t\tcatch _:_ ->\n\t\t\t\treset_stream(State, Stream, {stream_error, h3_message_error,\n\t\t\t\t\t'The :path pseudo-header is invalid. (RFC7540 8.1.2.3)'})\n\t\t\tend\n\tcatch _:_ ->\n\t\treset_stream(State, Stream, {stream_error, h3_message_error,\n\t\t\t'The :authority pseudo-header is invalid. (RFC7540 8.1.2.3)'})\n\tend.\n\n%% @todo Copied from cowboy_http2.\n%% @todo How to handle \"http\"?\nensure_port(<<\"http\">>, undefined) -> 80;\nensure_port(<<\"https\">>, undefined) -> 443;\nensure_port(_, Port) -> Port.\n\n%% @todo Copied from cowboy_http2.\n%% This function is necessary to properly handle duplicate headers\n%% and the special-case cookie header.\nheaders_to_map([], Acc) ->\n\tAcc;\nheaders_to_map([{Name, Value}|Tail], Acc0) ->\n\tAcc = case Acc0 of\n\t\t%% The cookie header does not use proper HTTP header lists.\n\t\t#{Name := Value0} when Name =:= <<\"cookie\">> ->\n\t\t\tAcc0#{Name => << Value0/binary, \"; \", Value/binary >>};\n\t\t#{Name := Value0} ->\n\t\t\tAcc0#{Name => << Value0/binary, \", \", Value/binary >>};\n\t\t_ ->\n\t\t\tAcc0#{Name => Value}\n\tend,\n\theaders_to_map(Tail, Acc).\n\n%% @todo WebTransport CONNECT requests must have extra checks on settings.\n%% @todo We may also need to defer them if we didn't get settings.\nheaders_frame(State=#state{opts=Opts}, Stream=#stream{id=StreamID}, Req) ->\n\ttry cowboy_stream:init(StreamID, Req, Opts) of\n\t\t{Commands, StreamState} ->\n\t\t\tcommands(State, Stream#stream{state=StreamState}, Commands)\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(init,\n\t\t\t[StreamID, Req, Opts],\n\t\t\tClass, Exception, Stacktrace), Opts),\n\t\treset_stream(State, Stream, {internal_error, {Class, Exception},\n\t\t\t'Unhandled exception in cowboy_stream:init/3.'})\n\tend.\n\nearly_error(State0=#state{ref=Ref, opts=Opts, peer=Peer},\n\t\tStream=#stream{id=StreamID}, _IsFin, Headers, #{method := Method},\n\t\tStatusCode0, HumanReadable) ->\n\t%% We automatically terminate the stream but it is not an error\n\t%% per se (at least not in the first implementation).\n\tReason = {stream_error, h3_no_error, HumanReadable},\n\t%% The partial Req is minimal for now. We only have one case\n\t%% where it can be called (when a method is completely disabled).\n\tPartialReq = #{\n\t\tref => Ref,\n\t\tpeer => Peer,\n\t\tmethod => Method,\n\t\theaders => headers_to_map(Headers, #{})\n\t},\n\tResp = {response, StatusCode0, RespHeaders0=#{<<\"content-length\">> => <<\"0\">>}, <<>>},\n\ttry cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts) of\n\t\t{response, StatusCode, RespHeaders, RespBody} ->\n\t\t\tsend_response(State0, Stream, StatusCode, RespHeaders, RespBody)\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(early_error,\n\t\t\t[StreamID, Reason, PartialReq, Resp, Opts],\n\t\t\tClass, Exception, Stacktrace), Opts),\n\t\t%% We still need to send an error response, so send what we initially\n\t\t%% wanted to send. It's better than nothing.\n\t\tsend_headers(State0, Stream, fin, StatusCode0, RespHeaders0)\n\tend.\n\n%% Datagrams.\n\nparse_datagram(State, Data0) ->\n\t{SessionID, Data} = cow_http3:parse_datagram(Data0),\n\tcase stream_get(State, SessionID) of\n\t\t#stream{status={webtransport_session, _}} ->\n\t\t\twebtransport_event(State, SessionID, {datagram, Data}),\n\t\t\tloop(State);\n\t\t_ ->\n\t\t\terror(todo) %% @todo Might be a future WT session or an error.\n\tend.\n\n%% Erlang messages.\n\ndown(State0=#state{opts=Opts, children=Children0}, Pid, Msg) ->\n\tState = case cowboy_children:down(Children0, Pid) of\n\t\t%% The stream was terminated already.\n\t\t{ok, undefined, Children} ->\n\t\t\tState0#state{children=Children};\n\t\t%% The stream is still running.\n\t\t{ok, StreamID, Children} ->\n\t\t\tinfo(State0#state{children=Children}, StreamID, Msg);\n\t\t%% The process was unknown.\n\t\terror ->\n\t\t\tcowboy:log(warning, \"Received EXIT signal ~p for unknown process ~p.~n\",\n\t\t\t\t[Msg, Pid], Opts),\n\t\t\tState0\n\tend,\n\tif\n%% @todo\n%\t\tState#state.http2_status =:= closing, State#state.streams =:= #{} ->\n%\t\t\tterminate(State, {stop, normal, 'The connection is going away.'});\n\t\ttrue ->\n\t\t\tState\n\tend.\n\ninfo(State=#state{opts=Opts, http3_machine=_HTTP3Machine}, StreamID, Msg) ->\n\tcase stream_get(State, StreamID) of\n\t\tStream=#stream{state=StreamState0} ->\n\t\t\ttry cowboy_stream:info(StreamID, Msg, StreamState0) of\n\t\t\t\t{Commands, StreamState} ->\n\t\t\t\t\tcommands(State, Stream#stream{state=StreamState}, Commands)\n\t\t\tcatch Class:Exception:Stacktrace ->\n\t\t\t\tcowboy:log(cowboy_stream:make_error_log(info,\n\t\t\t\t\t[StreamID, Msg, StreamState0],\n\t\t\t\t\tClass, Exception, Stacktrace), Opts),\n\t\t\t\treset_stream(State, Stream, {internal_error, {Class, Exception},\n\t\t\t\t\t'Unhandled exception in cowboy_stream:info/3.'})\n\t\t\tend;\n\t\terror ->\n\t\t\tcase is_lingering_stream(State, StreamID) of\n\t\t\t\ttrue ->\n\t\t\t\t\tok;\n\t\t\t\tfalse ->\n\t\t\t\t\tcowboy:log(warning, \"Received message ~p for unknown stream ~p.\",\n\t\t\t\t\t\t[Msg, StreamID], Opts)\n\t\t\tend,\n\t\t\tState\n\tend.\n\n%% Stream handler commands.\n\ncommands(State, Stream, []) ->\n\tstream_store(State, Stream);\n%% Error responses are sent only if a response wasn't sent already.\ncommands(State=#state{http3_machine=HTTP3Machine}, Stream=#stream{id=StreamID},\n\t\t[{error_response, StatusCode, Headers, Body}|Tail]) ->\n\tcase cow_http3_machine:get_bidi_stream_local_state(StreamID, HTTP3Machine) of\n\t\t{ok, idle} ->\n\t\t\tcommands(State, Stream, [{response, StatusCode, Headers, Body}|Tail]);\n\t\t_ ->\n\t\t\tcommands(State, Stream, Tail)\n\tend;\n%% Send an informational response.\ncommands(State0, Stream, [{inform, StatusCode, Headers}|Tail]) ->\n\tState = send_headers(State0, Stream, idle, StatusCode, Headers),\n\tcommands(State, Stream, Tail);\n%% Send response headers.\ncommands(State0, Stream, [{response, StatusCode, Headers, Body}|Tail]) ->\n\tState = send_response(State0, Stream, StatusCode, Headers, Body),\n\tcommands(State, Stream, Tail);\n%% Send response headers.\ncommands(State0, Stream, [{headers, StatusCode, Headers}|Tail]) ->\n\tState = send_headers(State0, Stream, nofin, StatusCode, Headers),\n\tcommands(State, Stream, Tail);\n%%% Send a response body chunk.\ncommands(State0=#state{conn=Conn}, Stream=#stream{id=StreamID}, [{data, IsFin, Data}|Tail]) ->\n\t_ = case Data of\n\t\t{sendfile, Offset, Bytes, Path} ->\n\t\t\t%% Temporary solution to do sendfile over QUIC.\n\t\t\t{ok, _} = ranch_transport:sendfile(?MODULE, {Conn, StreamID},\n\t\t\t\tPath, Offset, Bytes, []),\n\t\t\tok = maybe_socket_error(State0,\n\t\t\t\tcowboy_quicer:send(Conn, StreamID, cow_http3:data(<<>>), IsFin));\n\t\t_ ->\n\t\t\tok = maybe_socket_error(State0,\n\t\t\t\tcowboy_quicer:send(Conn, StreamID, cow_http3:data(Data), IsFin))\n\tend,\n\tState = maybe_send_is_fin(State0, Stream, IsFin),\n\tcommands(State, Stream, Tail);\n%%% Send trailers.\ncommands(State0=#state{conn=Conn, http3_machine=HTTP3Machine0},\n\t\tStream=#stream{id=StreamID}, [{trailers, Trailers}|Tail]) ->\n\tState = case cow_http3_machine:prepare_trailers(\n\t\t\tStreamID, HTTP3Machine0, maps:to_list(Trailers)) of\n\t\t{trailers, HeaderBlock, Instrs, HTTP3Machine} ->\n\t\t\tState1 = send_instructions(State0#state{http3_machine=HTTP3Machine}, Instrs),\n\t\t\tok = maybe_socket_error(State1,\n\t\t\t\tcowboy_quicer:send(Conn, StreamID, cow_http3:headers(HeaderBlock), fin)),\n\t\t\tState1;\n\t\t{no_trailers, HTTP3Machine} ->\n\t\t\tok = maybe_socket_error(State0,\n\t\t\t\tcowboy_quicer:send(Conn, StreamID, cow_http3:data(<<>>), fin)),\n\t\t\tState0#state{http3_machine=HTTP3Machine}\n\tend,\n\tcommands(State, Stream, Tail);\n%% Send a push promise.\n%%\n%% @todo Responses sent as a result of a push_promise request\n%% must not send push_promise frames themselves.\n%%\n%% @todo We should not send push_promise frames when we are\n%% in the closing http2_status.\n%commands(State0=#state{socket=Socket, transport=Transport, http3_machine=HTTP3Machine0},\n%\t\tStream, [{push, Method, Scheme, Host, Port, Path, Qs, Headers0}|Tail]) ->\n%\tAuthority = case {Scheme, Port} of\n%\t\t{<<\"http\">>, 80} -> Host;\n%\t\t{<<\"https\">>, 443} -> Host;\n%\t\t_ -> iolist_to_binary([Host, $:, integer_to_binary(Port)])\n%\tend,\n%\tPathWithQs = iolist_to_binary(case Qs of\n%\t\t<<>> -> Path;\n%\t\t_ -> [Path, $?, Qs]\n%\tend),\n%\tPseudoHeaders = #{\n%\t\tmethod => Method,\n%\t\tscheme => Scheme,\n%\t\tauthority => Authority,\n%\t\tpath => PathWithQs\n%\t},\n%\t%% We need to make sure the header value is binary before we can\n%\t%% create the Req object, as it expects them to be flat.\n%\tHeaders = maps:to_list(maps:map(fun(_, V) -> iolist_to_binary(V) end, Headers0)),\n%\t%% @todo\n%\tState = case cow_http2_machine:prepare_push_promise(StreamID, HTTP3Machine0,\n%\t\t\tPseudoHeaders, Headers) of\n%\t\t{ok, PromisedStreamID, HeaderBlock, HTTP3Machine} ->\n%\t\t\tTransport:send(Socket, cow_http2:push_promise(\n%\t\t\t\tStreamID, PromisedStreamID, HeaderBlock)),\n%\t\t\theaders_frame(State0#state{http3_machine=HTTP2Machine},\n%\t\t\t\tPromisedStreamID, fin, Headers, PseudoHeaders, 0);\n%\t\t{error, no_push} ->\n%\t\t\tState0\n%\tend,\n%\tcommands(State, Stream, Tail);\n%%% Read the request body.\n%commands(State0=#state{flow=Flow, streams=Streams}, Stream, [{flow, Size}|Tail]) ->\ncommands(State, Stream, [{flow, _Size}|Tail]) ->\n\t%% @todo We should tell the QUIC stream to increase its window size.\n%\t#{StreamID := Stream=#stream{flow=StreamFlow}} = Streams,\n%\tState = update_window(State0#state{flow=Flow + Size,\n%\t\tstreams=Streams#{StreamID => Stream#stream{flow=StreamFlow + Size}}},\n%\t\tStreamID),\n\tcommands(State, Stream, Tail);\n%% Supervise a child process.\ncommands(State=#state{children=Children}, Stream=#stream{id=StreamID},\n\t\t[{spawn, Pid, Shutdown}|Tail]) ->\n\t commands(State#state{children=cowboy_children:up(Children, Pid, StreamID, Shutdown)},\n\t\tStream, Tail);\n%% Error handling.\ncommands(State, Stream, [Error = {internal_error, _, _}|_Tail]) ->\n\t%% @todo Do we want to run the commands after an internal_error?\n\t%% @todo Do we even allow commands after?\n\t%% @todo Only reset when the stream still exists.\n\treset_stream(State, Stream, Error);\n%% Use a different protocol within the stream (CONNECT :protocol).\n%% @todo Make sure we error out when the feature is disabled.\ncommands(State0, Stream0=#stream{id=StreamID},\n\t\t[{switch_protocol, Headers, cowboy_webtransport, WTState=#{}}|Tail]) ->\n\tState = info(stream_store(State0, Stream0), StreamID, {headers, 200, Headers}),\n\t#state{http3_machine=HTTP3Machine0} = State,\n\tStream1 = #stream{state=StreamState} = stream_get(State, StreamID),\n\t%% The stream becomes a WT session at that point. It is the\n\t%% parent stream of all streams in this WT session. The\n\t%% cowboy_stream state is kept because it will be needed\n\t%% to terminate the stream properly.\n\tHTTP3Machine = cow_http3_machine:become_webtransport_session(StreamID, HTTP3Machine0),\n\tStream = Stream1#stream{\n\t\tstatus={webtransport_session, normal},\n\t\tstate={cowboy_webtransport, WTState#{stream_state => StreamState}}\n\t},\n\t%% @todo We must propagate the buffer to capsule handling if any.\n\tcommands(State#state{http3_machine=HTTP3Machine}, Stream, Tail);\n%% There are two data_delivery: stream_handlers and relay.\n%% The former just has the data go through stream handlers\n%% like normal requests. The latter relays data directly.\ncommands(State0, Stream0=#stream{id=StreamID},\n\t\t[{switch_protocol, Headers, _Mod, ModState=#{data_delivery := relay}}|Tail]) ->\n\tState = info(stream_store(State0, Stream0), StreamID, {headers, 200, Headers}),\n\tStream1 = #stream{status=normal} = stream_get(State, StreamID),\n\t#{data_delivery_pid := RelayPid} = ModState,\n\t%% We do not set data_delivery_flow because it is managed by quicer\n\t%% and we do not have an easy way to modify it.\n\tStream = Stream1#stream{status={relaying, normal, RelayPid}},\n\tcommands(State, Stream, Tail);\ncommands(State0, Stream0=#stream{id=StreamID},\n\t\t[{switch_protocol, Headers, _Mod, _ModState}|Tail]) ->\n\tState = info(stream_store(State0, Stream0), StreamID, {headers, 200, Headers}),\n\tStream = stream_get(State, StreamID),\n\tcommands(State, Stream, Tail);\n%% Set options dynamically.\ncommands(State, Stream, [{set_options, _Opts}|Tail]) ->\n\tcommands(State, Stream, Tail);\ncommands(State, Stream, [stop|_Tail]) ->\n\t%% @todo Do we want to run the commands after a stop?\n\t%% @todo Do we even allow commands after?\n\tstop_stream(State, Stream);\n%% Log event.\ncommands(State=#state{opts=Opts}, Stream, [Log={log, _, _, _}|Tail]) ->\n\tcowboy:log(Log, Opts),\n\tcommands(State, Stream, Tail).\n\nsend_response(State0=#state{conn=Conn, http3_machine=HTTP3Machine0},\n\t\tStream=#stream{id=StreamID}, StatusCode, Headers, Body) ->\n\tSize = case Body of\n\t\t{sendfile, _, Bytes0, _} -> Bytes0;\n\t\t_ -> iolist_size(Body)\n\tend,\n\tcase Size of\n\t\t0 ->\n\t\t\tState = send_headers(State0, Stream, fin, StatusCode, Headers),\n\t\t\tmaybe_send_is_fin(State, Stream, fin);\n\t\t_ ->\n\t\t\t%% @todo Add a test for HEAD to make sure we don't send the body when\n\t\t\t%% returning {response...} from a stream handler (or {headers...} then {data...}).\n\t\t\t{ok, _IsFin, HeaderBlock, Instrs, HTTP3Machine}\n\t\t\t\t= cow_http3_machine:prepare_headers(StreamID, HTTP3Machine0, nofin,\n\t\t\t\t\t#{status => cow_http:status_to_integer(StatusCode)},\n\t\t\t\t\theaders_to_list(Headers)),\n\t\t\tState = send_instructions(State0#state{http3_machine=HTTP3Machine}, Instrs),\n\t\t\t%% @todo It might be better to do async sends.\n\t\t\t_ = case Body of\n\t\t\t\t{sendfile, Offset, Bytes, Path} ->\n\t\t\t\t\tok = maybe_socket_error(State,\n\t\t\t\t\t\tcowboy_quicer:send(Conn, StreamID, cow_http3:headers(HeaderBlock))),\n\t\t\t\t\t%% Temporary solution to do sendfile over QUIC.\n\t\t\t\t\t{ok, _} = maybe_socket_error(State,\n\t\t\t\t\t\tranch_transport:sendfile(?MODULE, {Conn, StreamID},\n\t\t\t\t\t\t\tPath, Offset, Bytes, [])),\n\t\t\t\t\tok = maybe_socket_error(State,\n\t\t\t\t\t\tcowboy_quicer:send(Conn, StreamID, cow_http3:data(<<>>), fin));\n\t\t\t\t_ ->\n\t\t\t\t\tok = maybe_socket_error(State,\n\t\t\t\t\t\tcowboy_quicer:send(Conn, StreamID, [\n\t\t\t\t\t\t\tcow_http3:headers(HeaderBlock),\n\t\t\t\t\t\t\tcow_http3:data(Body)\n\t\t\t\t\t\t], fin))\n\t\t\tend,\n\t\t\tmaybe_send_is_fin(State, Stream, fin)\n\tend.\n\nmaybe_send_is_fin(State=#state{http3_machine=HTTP3Machine0},\n\t\tStream=#stream{id=StreamID}, fin) ->\n\tHTTP3Machine = cow_http3_machine:close_bidi_stream_for_sending(StreamID, HTTP3Machine0),\n\tmaybe_terminate_stream(State#state{http3_machine=HTTP3Machine}, Stream);\nmaybe_send_is_fin(State, _, _) ->\n\tState.\n\n%% Temporary callback to do sendfile over QUIC.\n-spec send({cowboy_quicer:quicer_connection_handle(), cow_http3:stream_id()},\n\tiodata()) -> ok | {error, any()}.\n\nsend({Conn, StreamID}, IoData) ->\n\tcowboy_quicer:send(Conn, StreamID, cow_http3:data(IoData)).\n\nsend_headers(State0=#state{conn=Conn, http3_machine=HTTP3Machine0},\n\t\t#stream{id=StreamID}, IsFin0, StatusCode, Headers) ->\n\t{ok, IsFin, HeaderBlock, Instrs, HTTP3Machine}\n\t\t= cow_http3_machine:prepare_headers(StreamID, HTTP3Machine0, IsFin0,\n\t\t\t#{status => cow_http:status_to_integer(StatusCode)},\n\t\t\theaders_to_list(Headers)),\n\tState = send_instructions(State0#state{http3_machine=HTTP3Machine}, Instrs),\n\tok = maybe_socket_error(State,\n\t\tcowboy_quicer:send(Conn, StreamID, cow_http3:headers(HeaderBlock), IsFin)),\n\tState.\n\n%% The set-cookie header is special; we can only send one cookie per header.\nheaders_to_list(Headers0=#{<<\"set-cookie\">> := SetCookies}) ->\n\tHeaders = maps:to_list(maps:remove(<<\"set-cookie\">>, Headers0)),\n\tHeaders ++ [{<<\"set-cookie\">>, Value} || Value <- SetCookies];\nheaders_to_list(Headers) ->\n\tmaps:to_list(Headers).\n\n%% @todo We would open unidi streams here if we only open on-demand.\n%% No instructions.\nsend_instructions(State, undefined) ->\n\tState;\n%% Decoder instructions.\nsend_instructions(State=#state{conn=Conn, local_decoder_id=DecoderID},\n\t\t{decoder_instructions, DecData}) ->\n\tok = maybe_socket_error(State,\n\t\tcowboy_quicer:send(Conn, DecoderID, DecData)),\n\tState;\n%% Encoder instructions.\nsend_instructions(State=#state{conn=Conn, local_encoder_id=EncoderID},\n\t\t{encoder_instructions, EncData}) ->\n\tok = maybe_socket_error(State,\n\t\tcowboy_quicer:send(Conn, EncoderID, EncData)),\n\tState.\n\n%% Relay data delivery commands.\n\nrelay_command(State, StreamID, DataCmd = {data, _, _}) ->\n\tStream = stream_get(State, StreamID),\n\tcommands(State, Stream, [DataCmd]);\nrelay_command(State=#state{conn=Conn}, StreamID, active) ->\n\tok = maybe_socket_error(State,\n\t\tcowboy_quicer:setopt(Conn, StreamID, active, true)),\n\tState;\nrelay_command(State=#state{conn=Conn}, StreamID, passive) ->\n\tok = maybe_socket_error(State,\n\t\tcowboy_quicer:setopt(Conn, StreamID, active, false)),\n\tState.\n\n%% We mark the stream as being a WebTransport stream\n%% and then continue parsing the data as a WebTransport\n%% stream. This function is common for incoming unidi\n%% and bidi streams.\nbecome_webtransport_stream(State0=#state{http3_machine=HTTP3Machine0},\n\t\tStream0=#stream{id=StreamID}, StreamType, SessionID, Rest, IsFin) ->\n\tcase cow_http3_machine:become_webtransport_stream(StreamID, SessionID, HTTP3Machine0) of\n\t\t{ok, HTTP3Machine} ->\n\t\t\tState = State0#state{http3_machine=HTTP3Machine},\n\t\t\tStream = Stream0#stream{status={webtransport_stream, SessionID}},\n\t\t\twebtransport_event(State, SessionID, {stream_open, StreamID, StreamType}),\n\t\t\t%% We don't need to parse the remaining data if there isn't any.\n\t\t\tcase {Rest, IsFin} of\n\t\t\t\t{<<>>, nofin} -> loop(stream_store(State, Stream));\n\t\t\t\t_ -> parse(stream_store(State, Stream), StreamID, Rest, IsFin)\n\t\t\tend\n\t\t%% @todo Error conditions.\n\tend.\n\nwebtransport_event(State, SessionID, Event) ->\n\t#stream{\n\t\tstatus={webtransport_session, _},\n\t\tstate={cowboy_webtransport, #{session_pid := SessionPid}}\n\t} = stream_get(State, SessionID),\n\tSessionPid ! {'$webtransport_event', SessionID, Event},\n\tok.\n\nwebtransport_commands(State, SessionID, Commands) ->\n\tcase stream_get(State, SessionID) of\n\t\tSession = #stream{status={webtransport_session, _}} ->\n\t\t\twt_commands(State, Session, Commands);\n\t\t%% The stream has been terminated, ignore pending commands.\n\t\terror ->\n\t\t\tState\n\tend.\n\nwt_commands(State, _, []) ->\n\tState;\nwt_commands(State0=#state{conn=Conn}, Session=#stream{id=SessionID},\n\t\t[{open_stream, OpenStreamRef, StreamType, InitialData}|Tail]) ->\n\t%% Because opening the stream involves sending a short header\n\t%% we necessarily write data. The InitialData variable allows\n\t%% providing additional data to be sent in the same packet.\n\tStartF = case StreamType of\n\t\tbidi -> start_bidi_stream;\n\t\tunidi -> start_unidi_stream\n\tend,\n\tHeader = cow_http3:webtransport_stream_header(SessionID, StreamType),\n\tcase cowboy_quicer:StartF(Conn, [Header, InitialData]) of\n\t\t{ok, StreamID} ->\n\t\t\t%% @todo Pass Session directly?\n\t\t\twebtransport_event(State0, SessionID,\n\t\t\t\t{opened_stream_id, OpenStreamRef, StreamID}),\n\t\t\tState = stream_new_local(State0, StreamID, StreamType,\n\t\t\t\t{webtransport_stream, SessionID}),\n\t\t\twt_commands(State, Session, Tail)\n\t\t%% @todo Handle errors.\n\tend;\nwt_commands(State, Session, [{close_stream, StreamID, Code}|Tail]) ->\n\t%% @todo Check that StreamID belongs to Session.\n\terror({todo, State, Session, [{close_stream, StreamID, Code}|Tail]});\nwt_commands(State=#state{conn=Conn}, Session=#stream{id=SessionID},\n\t\t[{send, datagram, Data}|Tail]) ->\n\tcase cowboy_quicer:send_datagram(Conn, cow_http3:datagram(SessionID, Data)) of\n\t\tok ->\n\t\t\twt_commands(State, Session, Tail)\n\t\t%% @todo Handle errors.\n\tend;\nwt_commands(State=#state{conn=Conn}, Session, [{send, StreamID, Data}|Tail]) ->\n\t%% @todo Check that StreamID belongs to Session.\n\tcase cowboy_quicer:send(Conn, StreamID, Data, nofin) of\n\t\tok ->\n\t\t\twt_commands(State, Session, Tail)\n\t\t%% @todo Handle errors.\n\tend;\nwt_commands(State=#state{conn=Conn}, Session, [{send, StreamID, IsFin, Data}|Tail]) ->\n\t%% @todo Check that StreamID belongs to Session.\n\tcase cowboy_quicer:send(Conn, StreamID, Data, IsFin) of\n\t\tok ->\n\t\t\twt_commands(State, Session, Tail)\n\t\t%% @todo Handle errors.\n\tend;\nwt_commands(State=#state{conn=Conn}, Session=#stream{id=SessionID}, [initiate_close|Tail]) ->\n\t%% We must send a WT_DRAIN_SESSION capsule on the CONNECT stream.\n\tCapsule = cow_capsule:wt_drain_session(),\n\tcase cowboy_quicer:send(Conn, SessionID, Capsule, nofin) of\n\t\tok ->\n\t\t\twt_commands(State, Session, Tail)\n\t\t%% @todo Handle errors.\n\tend;\nwt_commands(State0=#state{conn=Conn}, Session=#stream{id=SessionID}, [Cmd|Tail])\n\t\twhen Cmd =:= close; element(1, Cmd) =:= close ->\n\t%% We must send a WT_CLOSE_SESSION capsule on the CONNECT stream.\n\t{AppCode, AppMsg} = case Cmd of\n\t\tclose -> {0, <<>>};\n\t\t{close, AppCode0} -> {AppCode0, <<>>};\n\t\t{close, AppCode0, AppMsg0} -> {AppCode0, AppMsg0}\n\tend,\n\tCapsule = cow_capsule:wt_close_session(AppCode, AppMsg),\n\tcase cowboy_quicer:send(Conn, SessionID, Capsule, fin) of\n\t\tok ->\n\t\t\tState = webtransport_terminate_session(State0, Session),\n\t\t\t%% @todo Because the handler is in a separate process\n\t\t\t%%       we must wait for it to stop and eventually\n\t\t\t%%       kill the process if it takes too long.\n\t\t\t%% @todo We may need to fully close the CONNECT stream (if remote doesn't reset it).\n\t\t\twt_commands(State, Session, Tail)\n\t\t%% @todo Handle errors.\n\tend.\n\nwebtransport_terminate_session(State=#state{conn=Conn, http3_machine=HTTP3Machine0,\n\t\tstreams=Streams0, lingering_streams=Lingering0}, #stream{id=SessionID}) ->\n\t%% Reset/abort the WT streams.\n\tStreams = maps:filtermap(fun\n\t\t(_, #stream{id=StreamID, status={webtransport_session, _}})\n\t\t\t\twhen StreamID =:= SessionID ->\n\t\t\t%% We remove the session stream but do the shutdown outside this function.\n\t\t\tfalse;\n\t\t(StreamID, #stream{status={webtransport_stream, StreamSessionID}})\n\t\t\t\twhen StreamSessionID =:= SessionID ->\n\t\t\tcowboy_quicer:shutdown_stream(Conn, StreamID,\n\t\t\t\tboth, cow_http3:error_to_code(wt_session_gone)),\n\t\t\tfalse;\n\t\t(_, _) ->\n\t\t\ttrue\n\tend, Streams0),\n\t%% Keep the streams in lingering state.\n\t%% We only keep up to 100 streams in this state. @todo Make it configurable?\n\tTerminated = maps:keys(Streams0) -- maps:keys(Streams),\n\tLingering = lists:sublist(Terminated ++ Lingering0, 100),\n\t%% Update the HTTP3 state machine.\n\tHTTP3Machine = cow_http3_machine:close_webtransport_session(SessionID, HTTP3Machine0),\n\tState#state{\n\t\thttp3_machine=HTTP3Machine,\n\t\tstreams=Streams,\n\t\tlingering_streams=Lingering\n\t}.\n\nstream_peer_send_shutdown(State=#state{conn=Conn}, StreamID) ->\n\tcase stream_get(State, StreamID) of\n\t\t%% Cleanly terminating the CONNECT stream is equivalent\n\t\t%% to an application error code of 0 and empty message.\n\t\tStream = #stream{status={webtransport_session, _}} ->\n\t\t\twebtransport_event(State, StreamID, {closed, 0, <<>>}),\n\t\t\t%% Shutdown the CONNECT stream fully.\n\t\t\tcowboy_quicer:shutdown_stream(Conn, StreamID),\n\t\t\twebtransport_terminate_session(State, Stream);\n\t\t_ ->\n\t\t\tState\n\tend.\n\nreset_stream(State0=#state{conn=Conn, http3_machine=HTTP3Machine0},\n\t\tStream=#stream{id=StreamID}, Error) ->\n\tReason = case Error of\n\t\t{internal_error, _, _} -> h3_internal_error;\n\t\t{stream_error, Reason0, _} -> Reason0\n\tend,\n\t%% @todo Do we want to close both sides?\n\t%% @todo Should we close the send side if the receive side was already closed?\n\tcowboy_quicer:shutdown_stream(Conn, StreamID,\n\t\tboth, cow_http3:error_to_code(Reason)),\n\tState1 = case cow_http3_machine:reset_stream(StreamID, HTTP3Machine0) of\n\t\t{ok, HTTP3Machine} ->\n\t\t\tterminate_stream(State0#state{http3_machine=HTTP3Machine}, Stream, Error);\n\t\t{error, not_found} ->\n\t\t\tterminate_stream(State0, Stream, Error)\n\tend,\n%% @todo\n%\tcase reset_rate(State1) of\n%\t\t{ok, State} ->\n%\t\t\tState;\n%\t\terror ->\n%\t\t\tterminate(State1, {connection_error, enhance_your_calm,\n%\t\t\t\t'Stream reset rate larger than configuration allows. Flood? (CVE-2019-9514)'})\n%\tend.\n\tState1.\n\nstop_stream(State0=#state{http3_machine=HTTP3Machine}, Stream=#stream{id=StreamID}) ->\n\t%% We abort reading when stopping the stream but only\n\t%% if the client was not finished sending data.\n\t%% We mark the stream as 'stopping' either way.\n\tState = case cow_http3_machine:get_bidi_stream_remote_state(StreamID, HTTP3Machine) of\n\t\t{ok, fin} ->\n\t\t\tstream_store(State0, Stream#stream{status=stopping});\n\t\t{error, not_found} ->\n\t\t\tstream_store(State0, Stream#stream{status=stopping});\n\t\t_ ->\n\t\t\tstream_abort_receive(State0, Stream, h3_no_error)\n\tend,\n\t%% Then we may need to send a response or terminate it\n\t%% if the stream handler did not do so already.\n\tcase cow_http3_machine:get_bidi_stream_local_state(StreamID, HTTP3Machine) of\n\t\t%% When the stream terminates normally (without resetting the stream)\n\t\t%% and no response was sent, we need to send a proper response back to the client.\n\t\t{ok, idle} ->\n\t\t\tinfo(State, StreamID, {response, 204, #{}, <<>>});\n\t\t%% When a response was sent but not terminated, we need to close the stream.\n\t\t%% We send a final DATA frame to complete the stream.\n\t\t{ok, nofin} ->\n\t\t\tinfo(State, StreamID, {data, fin, <<>>});\n\t\t%% When a response was sent fully we can terminate the stream,\n\t\t%% regardless of the stream being in half-closed or closed state.\n\t\t_ ->\n\t\t\tterminate_stream(State, Stream, normal)\n\tend.\n\nmaybe_terminate_stream(State, Stream=#stream{status=stopping}) ->\n\tterminate_stream(State, Stream, normal);\n%% The Stream will be stored in the State at the end of commands processing.\nmaybe_terminate_stream(State, _) ->\n\tState.\n\nterminate_stream(State=#state{streams=Streams0, children=Children0},\n\t\t#stream{id=StreamID, state=StreamState}, Reason) ->\n\tStreams = maps:remove(StreamID, Streams0),\n\tterminate_stream_handler(State, StreamID, Reason, StreamState),\n\tChildren = cowboy_children:shutdown(Children0, StreamID),\n\tstream_linger(State#state{streams=Streams, children=Children}, StreamID).\n\nterminate_stream_handler(#state{opts=Opts}, StreamID, Reason, StreamState) ->\n\ttry\n\t\tcowboy_stream:terminate(StreamID, Reason, StreamState)\n\tcatch Class:Exception:Stacktrace ->\n\t\tcowboy:log(cowboy_stream:make_error_log(terminate,\n\t\t\t[StreamID, Reason, StreamState],\n\t\t\tClass, Exception, Stacktrace), Opts)\n\tend.\n\nignored_frame(State=#state{http3_machine=HTTP3Machine0}, #stream{id=StreamID}) ->\n\tcase cow_http3_machine:ignored_frame(StreamID, HTTP3Machine0) of\n\t\t{ok, HTTP3Machine} ->\n\t\t\tState#state{http3_machine=HTTP3Machine};\n\t\t{error, Error={connection_error, _, _}, HTTP3Machine} ->\n\t\t\tterminate(State#state{http3_machine=HTTP3Machine}, Error)\n\tend.\n\nstream_abort_receive(State=#state{conn=Conn}, Stream=#stream{id=StreamID}, Reason) ->\n\tcowboy_quicer:shutdown_stream(Conn, StreamID,\n\t\treceiving, cow_http3:error_to_code(Reason)),\n\tstream_store(State, Stream#stream{status=stopping}).\n\n%% @todo Graceful connection shutdown.\n%% We terminate the connection immediately if it hasn't fully been initialized.\n-spec goaway(#state{}, {goaway, _}) -> no_return().\ngoaway(State, {goaway, _}) ->\n\tterminate(State, {stop, goaway, 'The connection is going away.'}).\n\n%% Function copied from cowboy_http.\nmaybe_socket_error(State, {error, closed}) ->\n\tterminate(State, {socket_error, closed, 'The socket has been closed.'});\nmaybe_socket_error(State, Reason) ->\n\tmaybe_socket_error(State, Reason, 'An error has occurred on the socket.').\n\nmaybe_socket_error(_, Result = ok, _) ->\n\tResult;\nmaybe_socket_error(_, Result = {ok, _}, _) ->\n\tResult;\nmaybe_socket_error(State, {error, Reason}, Human) ->\n\tterminate(State, {socket_error, Reason, Human}).\n\n-spec terminate(#state{} | undefined, _) -> no_return().\nterminate(undefined, Reason) ->\n\texit({shutdown, Reason});\nterminate(State=#state{conn=Conn, %http3_status=Status,\n\t\t%http3_machine=HTTP3Machine,\n\t\tstreams=Streams, children=Children}, Reason) ->\n%\tif\n%\t\tStatus =:= connected; Status =:= closing_initiated ->\n%% @todo\n%\t\t\t%% We are terminating so it's OK if we can't send the GOAWAY anymore.\n%\t\t\t_ = cowboy_quicer:send(Conn, ControlID, cow_http3:goaway(\n%\t\t\t\tcow_http3_machine:get_last_streamid(HTTP3Machine))),\n\t\t%% We already sent the GOAWAY frame.\n%\t\tStatus =:= closing ->\n%\t\t\tok\n%\tend,\n\tterminate_all_streams(State, maps:to_list(Streams), Reason),\n\tcowboy_children:terminate(Children),\n%\tterminate_linger(State),\n\t_ = cowboy_quicer:shutdown(Conn, cow_http3:error_to_code(terminate_reason(Reason))),\n\texit({shutdown, Reason}).\n\nterminate_reason({connection_error, Reason, _}) -> Reason;\nterminate_reason({stop, _, _}) -> h3_no_error;\nterminate_reason({socket_error, _, _}) -> h3_internal_error.\n%terminate_reason({internal_error, _, _}) -> internal_error.\n\nterminate_all_streams(_, [], _) ->\n\tok;\nterminate_all_streams(State, [{StreamID, #stream{state=StreamState}}|Tail], Reason) ->\n\tterminate_stream_handler(State, StreamID, Reason, StreamState),\n\tterminate_all_streams(State, Tail, Reason).\n\nstream_get(#state{streams=Streams}, StreamID) ->\n\tmaps:get(StreamID, Streams, error).\n\nstream_new_local(State, StreamID, StreamType, Status) ->\n\tstream_new(State, StreamID, StreamType, unidi_local, Status).\n\nstream_new_remote(State, StreamID, StreamType) ->\n\tStatus = case StreamType of\n\t\tunidi -> header;\n\t\tbidi -> normal\n\tend,\n\tstream_new(State, StreamID, StreamType, unidi_remote, Status).\n\nstream_new(State=#state{http3_machine=HTTP3Machine0, streams=Streams},\n\t\tStreamID, StreamType, UnidiType, Status) ->\n\t{HTTP3Machine, Status} = case StreamType of\n\t\tunidi ->\n\t\t\t{cow_http3_machine:init_unidi_stream(StreamID, UnidiType, HTTP3Machine0),\n\t\t\t\tStatus};\n\t\tbidi ->\n\t\t\t{cow_http3_machine:init_bidi_stream(StreamID, HTTP3Machine0),\n\t\t\t\tStatus}\n\tend,\n\tStream = #stream{id=StreamID, status=Status},\n\tState#state{http3_machine=HTTP3Machine, streams=Streams#{StreamID => Stream}}.\n\n%% Stream closed message for a local (write-only) unidi stream.\nstream_closed(State=#state{local_control_id=StreamID}, StreamID, _) ->\n\tstream_closed1(State, StreamID);\nstream_closed(State=#state{local_encoder_id=StreamID}, StreamID, _) ->\n\tstream_closed1(State, StreamID);\nstream_closed(State=#state{local_decoder_id=StreamID}, StreamID, _) ->\n\tstream_closed1(State, StreamID);\nstream_closed(State=#state{opts=Opts,\n\t\tstreams=Streams0, children=Children0}, StreamID, ErrorCode) ->\n\tcase maps:take(StreamID, Streams0) of\n\t\t%% In the WT session's case, streams will be\n\t\t%% removed in webtransport_terminate_session.\n\t\t{Stream=#stream{status={webtransport_session, _}}, _} ->\n\t\t\twebtransport_event(State, StreamID, closed_abruptly),\n\t\t\twebtransport_terminate_session(State, Stream);\n\t\t{#stream{state=undefined}, Streams} ->\n\t\t\t%% Unidi stream has no handler/children.\n\t\t\tstream_closed1(State#state{streams=Streams}, StreamID);\n\t\t%% We only stop bidi streams if the stream was closed with an error\n\t\t%% or the stream was already in the process of stopping.\n\t\t{#stream{status=Status, state=StreamState}, Streams}\n\t\t\t\twhen Status =:= stopping; ErrorCode =/= 0 ->\n\t\t\tterminate_stream_handler(State, StreamID, closed, StreamState),\n\t\t\tChildren = cowboy_children:shutdown(Children0, StreamID),\n\t\t\tstream_closed1(State#state{streams=Streams, children=Children}, StreamID);\n\t\t%% Don't remove a stream that terminated properly but\n\t\t%% has chosen to remain up (custom stream handlers).\n\t\t{_, _} ->\n\t\t\tstream_closed1(State, StreamID);\n\t\t%% Stream closed message for a stream that has been reset. Ignore.\n\t\terror ->\n\t\t\tcase is_lingering_stream(State, StreamID) of\n\t\t\t\ttrue ->\n\t\t\t\t\tok;\n\t\t\t\tfalse ->\n\t\t\t\t\t%% We avoid logging the data as it could be quite large.\n\t\t\t\t\tcowboy:log(warning, \"Received stream_closed for unknown stream ~p. ~p ~p\",\n\t\t\t\t\t\t[StreamID, self(), Streams0], Opts)\n\t\t\tend,\n\t\t\tState\n\tend.\n\nstream_closed1(State=#state{http3_machine=HTTP3Machine0}, StreamID) ->\n\tcase cow_http3_machine:close_stream(StreamID, HTTP3Machine0) of\n\t\t{ok, HTTP3Machine} ->\n\t\t\tState#state{http3_machine=HTTP3Machine};\n\t\t{error, Error={connection_error, _, _}, HTTP3Machine} ->\n\t\t\tterminate(State#state{http3_machine=HTTP3Machine}, Error)\n\tend.\n\nstream_store(State=#state{streams=Streams}, Stream=#stream{id=StreamID}) ->\n\tState#state{streams=Streams#{StreamID => Stream}}.\n\nstream_linger(State=#state{lingering_streams=Lingering0}, StreamID) ->\n\t%% We only keep up to 100 streams in this state. @todo Make it configurable?\n\tLingering = [StreamID|lists:sublist(Lingering0, 100 - 1)],\n\tState#state{lingering_streams=Lingering}.\n\nis_lingering_stream(#state{lingering_streams=Lingering}, StreamID) ->\n\tlists:member(StreamID, Lingering).\n\n-endif.\n"
  },
  {
    "path": "src/cowboy_loop.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_loop).\n-behaviour(cowboy_sub_protocol).\n\n-export([upgrade/4]).\n-export([upgrade/5]).\n-export([loop/5]).\n\n-export([system_continue/3]).\n-export([system_terminate/4]).\n-export([system_code_change/4]).\n\n%% From gen_server.\n-define(is_timeout(X), ((X) =:= infinity orelse (is_integer(X) andalso (X) >= 0))).\n\n-callback init(Req, any())\n\t-> {ok | module(), Req, any()}\n\t| {module(), Req, any(), any()}\n\twhen Req::cowboy_req:req().\n\n-callback info(any(), Req, State)\n\t-> {ok, Req, State}\n\t| {ok, Req, State, hibernate}\n\t| {stop, Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n\n-callback terminate(any(), cowboy_req:req(), any()) -> ok.\n-optional_callbacks([terminate/3]).\n\n-spec upgrade(Req, Env, module(), any())\n\t-> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\nupgrade(Req, Env, Handler, HandlerState) ->\n\tloop(Req, Env, Handler, HandlerState, infinity).\n\n-spec upgrade(Req, Env, module(), any(), hibernate | timeout())\n\t-> {suspend, ?MODULE, loop, [any()]}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\nupgrade(Req, Env, Handler, HandlerState, hibernate) ->\n\tsuspend(Req, Env, Handler, HandlerState);\nupgrade(Req, Env, Handler, HandlerState, Timeout) when ?is_timeout(Timeout) ->\n\tloop(Req, Env, Handler, HandlerState, Timeout).\n\n-spec loop(Req, Env, module(), any(), timeout())\n\t-> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\n%% @todo Handle system messages.\nloop(Req=#{pid := Parent}, Env, Handler, HandlerState, Timeout) ->\n\treceive\n\t\t%% System messages.\n\t\t{'EXIT', Parent, Reason} ->\n\t\t\tterminate(Req, Env, Handler, HandlerState, Reason);\n\t\t{system, From, Request} ->\n\t\t\tsys:handle_system_msg(Request, From, Parent, ?MODULE, [],\n\t\t\t\t{Req, Env, Handler, HandlerState, Timeout});\n\t\t%% Calls from supervisor module.\n\t\t{'$gen_call', From, Call} ->\n\t\t\tcowboy_children:handle_supervisor_call(Call, From, [], ?MODULE),\n\t\t\tloop(Req, Env, Handler, HandlerState, Timeout);\n\t\tMessage ->\n\t\t\tcall(Req, Env, Handler, HandlerState, Timeout, Message)\n\tafter Timeout ->\n\t\tcall(Req, Env, Handler, HandlerState, Timeout, timeout)\n\tend.\n\ncall(Req0, Env, Handler, HandlerState0, Timeout, Message) ->\n\ttry Handler:info(Message, Req0, HandlerState0) of\n\t\t{ok, Req, HandlerState} ->\n\t\t\tloop(Req, Env, Handler, HandlerState, Timeout);\n\t\t{ok, Req, HandlerState, hibernate} ->\n\t\t\tsuspend(Req, Env, Handler, HandlerState);\n\t\t{ok, Req, HandlerState, NewTimeout} when ?is_timeout(NewTimeout) ->\n\t\t\tloop(Req, Env, Handler, HandlerState, NewTimeout);\n\t\t{stop, Req, HandlerState} ->\n\t\t\tterminate(Req, Env, Handler, HandlerState, stop)\n\tcatch Class:Reason:Stacktrace ->\n\t\tcowboy_handler:terminate({crash, Class, Reason}, Req0, HandlerState0, Handler),\n\t\terlang:raise(Class, Reason, Stacktrace)\n\tend.\n\nsuspend(Req, Env, Handler, HandlerState) ->\n\t{suspend, ?MODULE, loop, [Req, Env, Handler, HandlerState, infinity]}.\n\nterminate(Req, Env, Handler, HandlerState, Reason) ->\n\tResult = cowboy_handler:terminate(Reason, Req, HandlerState, Handler),\n\t{ok, Req, Env#{result => Result}}.\n\n%% System callbacks.\n\n-spec system_continue(_, _, {Req, Env, module(), any(), timeout()})\n\t-> {ok, Req, Env} | {suspend, ?MODULE, loop, [any()]}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\nsystem_continue(_, _, {Req, Env, Handler, HandlerState, Timeout}) ->\n\tloop(Req, Env, Handler, HandlerState, Timeout).\n\n-spec system_terminate(any(), _, _, {Req, Env, module(), any(), timeout()})\n\t-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().\nsystem_terminate(Reason, _, _, {Req, Env, Handler, HandlerState, _}) ->\n\tterminate(Req, Env, Handler, HandlerState, Reason).\n\n-spec system_code_change(Misc, _, _, _) -> {ok, Misc}\n\twhen Misc::{cowboy_req:req(), cowboy_middleware:env(), module(), any()}.\nsystem_code_change(Misc, _, _, _) ->\n\t{ok, Misc}.\n"
  },
  {
    "path": "src/cowboy_metrics_h.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_metrics_h).\n-behavior(cowboy_stream).\n\n-export([init/3]).\n-export([data/4]).\n-export([info/3]).\n-export([terminate/3]).\n-export([early_error/5]).\n\n-type proc_metrics() :: #{pid() => #{\n\t%% Time at which the process spawned.\n\tspawn := integer(),\n\n\t%% Time at which the process exited.\n\texit => integer(),\n\n\t%% Reason for the process exit.\n\treason => any()\n}}.\n\n-type informational_metrics() :: #{\n\t%% Informational response status.\n\tstatus := cowboy:http_status(),\n\n\t%% Headers sent with the informational response.\n\theaders := cowboy:http_headers(),\n\n\t%% Time when the informational response was sent.\n\ttime := integer()\n}.\n\n-type metrics() :: #{\n\t%% The identifier for this listener.\n\tref := ranch:ref(),\n\n\t%% The pid for this connection.\n\tpid := pid(),\n\n\t%% The streamid also indicates the total number of requests on\n\t%% this connection (StreamID div 2 + 1).\n\tstreamid := cowboy_stream:streamid(),\n\n\t%% The terminate reason is always useful.\n\treason := cowboy_stream:reason(),\n\n\t%% A filtered Req object or a partial Req object\n\t%% depending on how far the request got to.\n\treq => cowboy_req:req(),\n\tpartial_req => cowboy_stream:partial_req(),\n\n\t%% Response status.\n\tresp_status := cowboy:http_status(),\n\n\t%% Filtered response headers.\n\tresp_headers := cowboy:http_headers(),\n\n\t%% Start/end of the processing of the request.\n\t%%\n\t%% This represents the time from this stream handler's init\n\t%% to terminate.\n\treq_start => integer(),\n\treq_end => integer(),\n\n\t%% Start/end of the receiving of the request body.\n\t%% Begins when the first packet has been received.\n\treq_body_start => integer(),\n\treq_body_end => integer(),\n\n\t%% Start/end of the sending of the response.\n\t%% Begins when we send the headers and ends on the final\n\t%% packet of the response body. If everything is sent at\n\t%% once these values are identical.\n\tresp_start => integer(),\n\tresp_end => integer(),\n\n\t%% For early errors all we get is the time we received it.\n\tearly_error_time => integer(),\n\n\t%% Start/end of spawned processes. This is where most of\n\t%% the user code lies, excluding stream handlers. On a\n\t%% default Cowboy configuration there should be only one\n\t%% process: the request process.\n\tprocs => proc_metrics(),\n\n\t%% Informational responses sent before the final response.\n\tinformational => [informational_metrics()],\n\n\t%% Length of the request and response bodies. This does\n\t%% not include the framing.\n\treq_body_length => non_neg_integer(),\n\tresp_body_length => non_neg_integer(),\n\n\t%% Additional metadata set by the user.\n\tuser_data => map()\n}.\n-export_type([metrics/0]).\n\n-type metrics_callback() :: fun((metrics()) -> any()).\n-export_type([metrics_callback/0]).\n\n-record(state, {\n\tnext :: any(),\n\tcallback :: fun((metrics()) -> any()),\n\tresp_headers_filter :: undefined | fun((cowboy:http_headers()) -> cowboy:http_headers()),\n\treq :: map(),\n\tresp_status :: undefined | cowboy:http_status(),\n\tresp_headers :: undefined | cowboy:http_headers(),\n\tref :: ranch:ref(),\n\treq_start :: integer(),\n\treq_end :: undefined | integer(),\n\treq_body_start :: undefined | integer(),\n\treq_body_end :: undefined | integer(),\n\tresp_start :: undefined | integer(),\n\tresp_end :: undefined | integer(),\n\tprocs = #{} :: proc_metrics(),\n\tinformational = [] :: [informational_metrics()],\n\treq_body_length = 0 :: non_neg_integer(),\n\tresp_body_length = 0 :: non_neg_integer(),\n\tuser_data = #{} :: map()\n}).\n\n-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())\n\t-> {[{spawn, pid(), timeout()}], #state{}}.\ninit(StreamID, Req=#{ref := Ref}, Opts=#{metrics_callback := Fun}) ->\n\tReqStart = erlang:monotonic_time(),\n\t{Commands, Next} = cowboy_stream:init(StreamID, Req, Opts),\n\tFilteredReq = case maps:get(metrics_req_filter, Opts, undefined) of\n\t\tundefined -> Req;\n\t\tReqFilter -> ReqFilter(Req)\n\tend,\n\tRespHeadersFilter = maps:get(metrics_resp_headers_filter, Opts, undefined),\n\t{Commands, fold(Commands, #state{\n\t\tnext=Next,\n\t\tcallback=Fun,\n\t\tresp_headers_filter=RespHeadersFilter,\n\t\treq=FilteredReq,\n\t\tref=Ref,\n\t\treq_start=ReqStart\n\t})}.\n\n-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)\n\t-> {cowboy_stream:commands(), State} when State::#state{}.\ndata(StreamID, IsFin=fin, Data, State=#state{req_body_start=undefined}) ->\n\tReqBody = erlang:monotonic_time(),\n\tdo_data(StreamID, IsFin, Data, State#state{\n\t\treq_body_start=ReqBody,\n\t\treq_body_end=ReqBody,\n\t\treq_body_length=byte_size(Data)\n\t});\ndata(StreamID, IsFin=fin, Data, State=#state{req_body_length=ReqBodyLen}) ->\n\tReqBodyEnd = erlang:monotonic_time(),\n\tdo_data(StreamID, IsFin, Data, State#state{\n\t\treq_body_end=ReqBodyEnd,\n\t\treq_body_length=ReqBodyLen + byte_size(Data)\n\t});\ndata(StreamID, IsFin, Data, State=#state{req_body_start=undefined}) ->\n\tReqBodyStart = erlang:monotonic_time(),\n\tdo_data(StreamID, IsFin, Data, State#state{\n\t\treq_body_start=ReqBodyStart,\n\t\treq_body_length=byte_size(Data)\n\t});\ndata(StreamID, IsFin, Data, State=#state{req_body_length=ReqBodyLen}) ->\n\tdo_data(StreamID, IsFin, Data, State#state{\n\t\treq_body_length=ReqBodyLen + byte_size(Data)\n\t}).\n\ndo_data(StreamID, IsFin, Data, State0=#state{next=Next0}) ->\n\t{Commands, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),\n\t{Commands, fold(Commands, State0#state{next=Next})}.\n\n-spec info(cowboy_stream:streamid(), any(), State)\n\t-> {cowboy_stream:commands(), State} when State::#state{}.\ninfo(StreamID, Info={'EXIT', Pid, Reason}, State0=#state{procs=Procs}) ->\n\tProcEnd = erlang:monotonic_time(),\n\tP = maps:get(Pid, Procs),\n\tState = State0#state{procs=Procs#{Pid => P#{\n\t\texit => ProcEnd,\n\t\treason => Reason\n\t}}},\n\tdo_info(StreamID, Info, State);\ninfo(StreamID, Info, State) ->\n\tdo_info(StreamID, Info, State).\n\ndo_info(StreamID, Info, State0=#state{next=Next0}) ->\n\t{Commands, Next} = cowboy_stream:info(StreamID, Info, Next0),\n\t{Commands, fold(Commands, State0#state{next=Next})}.\n\nfold([], State) ->\n\tState;\nfold([{spawn, Pid, _}|Tail], State0=#state{procs=Procs}) ->\n\tProcStart = erlang:monotonic_time(),\n\tState = State0#state{procs=Procs#{Pid => #{spawn => ProcStart}}},\n\tfold(Tail, State);\nfold([{inform, Status, Headers}|Tail],\n\t\tState=#state{informational=Infos}) ->\n\tTime = erlang:monotonic_time(),\n\tfold(Tail, State#state{informational=[#{\n\t\tstatus => Status,\n\t\theaders => Headers,\n\t\ttime => Time\n\t}|Infos]});\nfold([{response, Status, Headers, Body}|Tail],\n\t\tState=#state{resp_headers_filter=RespHeadersFilter}) ->\n\tResp = erlang:monotonic_time(),\n\tfold(Tail, State#state{\n\t\tresp_status=Status,\n\t\tresp_headers=case RespHeadersFilter of\n\t\t\tundefined -> Headers;\n\t\t\t_ -> RespHeadersFilter(Headers)\n\t\tend,\n\t\tresp_start=Resp,\n\t\tresp_end=Resp,\n\t\tresp_body_length=resp_body_length(Body)\n\t});\nfold([{error_response, Status, Headers, Body}|Tail],\n\t\tState=#state{resp_status=RespStatus}) ->\n\t%% The error_response command only results in a response\n\t%% if no response was sent before.\n\tcase RespStatus of\n\t\tundefined ->\n\t\t\tfold([{response, Status, Headers, Body}|Tail], State);\n\t\t_ ->\n\t\t\tfold(Tail, State)\n\tend;\nfold([{headers, Status, Headers}|Tail],\n\t\tState=#state{resp_headers_filter=RespHeadersFilter}) ->\n\tRespStart = erlang:monotonic_time(),\n\tfold(Tail, State#state{\n\t\tresp_status=Status,\n\t\tresp_headers=case RespHeadersFilter of\n\t\t\tundefined -> Headers;\n\t\t\t_ -> RespHeadersFilter(Headers)\n\t\tend,\n\t\tresp_start=RespStart\n\t});\n%% @todo It might be worthwhile to keep the sendfile information around,\n%% especially if these frames ultimately result in a sendfile syscall.\nfold([{data, nofin, Data}|Tail], State=#state{resp_body_length=RespBodyLen}) ->\n\tfold(Tail, State#state{\n\t\tresp_body_length=RespBodyLen + resp_body_length(Data)\n\t});\nfold([{data, fin, Data}|Tail], State=#state{resp_body_length=RespBodyLen}) ->\n\tRespEnd = erlang:monotonic_time(),\n\tfold(Tail, State#state{\n\t\tresp_end=RespEnd,\n\t\tresp_body_length=RespBodyLen + resp_body_length(Data)\n\t});\nfold([{set_options, SetOpts}|Tail], State0=#state{user_data=OldUserData}) ->\n\tState = case SetOpts of\n\t\t#{metrics_user_data := NewUserData} ->\n\t\t\tState0#state{user_data=maps:merge(OldUserData, NewUserData)};\n\t\t_ ->\n\t\t\tState0\n\tend,\n\tfold(Tail, State);\nfold([_|Tail], State) ->\n\tfold(Tail, State).\n\n-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> any().\nterminate(StreamID, Reason, #state{next=Next, callback=Fun,\n\t\treq=Req, resp_status=RespStatus, resp_headers=RespHeaders, ref=Ref,\n\t\treq_start=ReqStart, req_body_start=ReqBodyStart,\n\t\treq_body_end=ReqBodyEnd, resp_start=RespStart, resp_end=RespEnd,\n\t\tprocs=Procs, informational=Infos, user_data=UserData,\n\t\treq_body_length=ReqBodyLen, resp_body_length=RespBodyLen}) ->\n\tRes = cowboy_stream:terminate(StreamID, Reason, Next),\n\tReqEnd = erlang:monotonic_time(),\n\tMetrics = #{\n\t\tref => Ref,\n\t\tpid => self(),\n\t\tstreamid => StreamID,\n\t\treason => Reason,\n\t\treq => Req,\n\t\tresp_status => RespStatus,\n\t\tresp_headers => RespHeaders,\n\t\treq_start => ReqStart,\n\t\treq_end => ReqEnd,\n\t\treq_body_start => ReqBodyStart,\n\t\treq_body_end => ReqBodyEnd,\n\t\tresp_start => RespStart,\n\t\tresp_end => RespEnd,\n\t\tprocs => Procs,\n\t\tinformational => lists:reverse(Infos),\n\t\treq_body_length => ReqBodyLen,\n\t\tresp_body_length => RespBodyLen,\n\t\tuser_data => UserData\n\t},\n\tFun(Metrics),\n\tRes.\n\n-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),\n\tcowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp\n\twhen Resp::cowboy_stream:resp_command().\nearly_error(StreamID, Reason, PartialReq=#{ref := Ref}, Resp0, Opts=#{metrics_callback := Fun}) ->\n\tTime = erlang:monotonic_time(),\n\tResp = {response, RespStatus, RespHeaders, RespBody}\n\t\t= cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp0, Opts),\n\t%% As far as metrics go we are limited in what we can provide\n\t%% in this case.\n\tMetrics = #{\n\t\tref => Ref,\n\t\tpid => self(),\n\t\tstreamid => StreamID,\n\t\treason => Reason,\n\t\tpartial_req => PartialReq,\n\t\tresp_status => RespStatus,\n\t\tresp_headers => RespHeaders,\n\t\tearly_error_time => Time,\n\t\tresp_body_length => resp_body_length(RespBody)\n\t},\n\tFun(Metrics),\n\tResp.\n\nresp_body_length({sendfile, _, Len, _}) ->\n\tLen;\nresp_body_length(Data) ->\n\tiolist_size(Data).\n"
  },
  {
    "path": "src/cowboy_middleware.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_middleware).\n\n-type env() :: #{atom() => any()}.\n-export_type([env/0]).\n\n-callback execute(Req, Env)\n\t-> {ok, Req, Env}\n\t| {suspend, module(), atom(), [any()]}\n\t| {stop, Req}\n\twhen Req::cowboy_req:req(), Env::env().\n"
  },
  {
    "path": "src/cowboy_quicer.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% QUIC transport using the emqx/quicer NIF.\n\n-module(cowboy_quicer).\n\n-ifdef(COWBOY_QUICER).\n\n%% Connection.\n-export([peername/1]).\n-export([sockname/1]).\n-export([peercert/1]).\n-export([shutdown/2]).\n\n%% Streams.\n-export([start_bidi_stream/2]).\n-export([start_unidi_stream/2]).\n-export([setopt/4]).\n-export([send/3]).\n-export([send/4]).\n-export([send_datagram/2]).\n-export([shutdown_stream/2]).\n-export([shutdown_stream/4]).\n\n%% Messages.\n-export([handle/1]).\n\n%% @todo Make quicer export these types.\n-type quicer_connection_handle() :: reference().\n-export_type([quicer_connection_handle/0]).\n\n-type quicer_app_errno() :: non_neg_integer().\n\n-include_lib(\"quicer/include/quicer.hrl\").\n\n%% Connection.\n\n-spec peername(quicer_connection_handle())\n\t-> {ok, {inet:ip_address(), inet:port_number()}}\n\t| {error, any()}.\n\npeername(Conn) ->\n\tquicer:peername(Conn).\n\n-spec sockname(quicer_connection_handle())\n\t-> {ok, {inet:ip_address(), inet:port_number()}}\n\t| {error, any()}.\n\nsockname(Conn) ->\n\tquicer:sockname(Conn).\n\n-spec peercert(quicer_connection_handle())\n\t-> {ok, public_key:der_encoded()}\n\t| {error, any()}.\n\npeercert(Conn) ->\n\tquicer_nif:peercert(Conn).\n\n-spec shutdown(quicer_connection_handle(), quicer_app_errno())\n\t-> ok | {error, any()}.\n\nshutdown(Conn, ErrorCode) ->\n\tquicer:shutdown_connection(Conn,\n\t\t?QUIC_CONNECTION_SHUTDOWN_FLAG_NONE,\n\t\tErrorCode).\n\n%% Streams.\n\n-spec start_bidi_stream(quicer_connection_handle(), iodata())\n\t-> {ok, cow_http3:stream_id()}\n\t| {error, any()}.\n\nstart_bidi_stream(Conn, InitialData) ->\n\tstart_stream(Conn, InitialData, ?QUIC_STREAM_OPEN_FLAG_NONE).\n\n-spec start_unidi_stream(quicer_connection_handle(), iodata())\n\t-> {ok, cow_http3:stream_id()}\n\t| {error, any()}.\n\nstart_unidi_stream(Conn, InitialData) ->\n\tstart_stream(Conn, InitialData, ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL).\n\nstart_stream(Conn, InitialData, OpenFlag) ->\n\tcase quicer:start_stream(Conn, #{\n\t\t\tactive => true,\n\t\t\topen_flag => OpenFlag}) of\n\t\t{ok, StreamRef} ->\n\t\t\tcase quicer:send(StreamRef, InitialData) of\n\t\t\t\t{ok, _} ->\n\t\t\t\t\t{ok, StreamID} = quicer:get_stream_id(StreamRef),\n\t\t\t\t\tput({quicer_stream, StreamID}, StreamRef),\n\t\t\t\t\t{ok, StreamID};\n\t\t\t\tError ->\n\t\t\t\t\tError\n\t\t\tend;\n\t\t{error, Reason1, Reason2} ->\n\t\t\t{error, {Reason1, Reason2}};\n\t\tError ->\n\t\t\tError\n\tend.\n\n-spec setopt(quicer_connection_handle(), cow_http3:stream_id(), active, boolean())\n\t-> ok | {error, any()}.\n\nsetopt(_Conn, StreamID, active, Value) ->\n\tStreamRef = get({quicer_stream, StreamID}),\n\tquicer:setopt(StreamRef, active, Value).\n\n-spec send(quicer_connection_handle(), cow_http3:stream_id(), iodata())\n\t-> ok | {error, any()}.\n\nsend(Conn, StreamID, Data) ->\n\tsend(Conn, StreamID, Data, nofin).\n\n-spec send(quicer_connection_handle(), cow_http3:stream_id(), iodata(), cow_http:fin())\n\t-> ok | {error, any()}.\n\nsend(_Conn, StreamID, Data, IsFin) ->\n\tStreamRef = get({quicer_stream, StreamID}),\n\tSize = iolist_size(Data),\n\tcase quicer:send(StreamRef, Data, send_flag(IsFin)) of\n\t\t{ok, Size} ->\n\t\t\tok;\n\t\t{error, Reason1, Reason2} ->\n\t\t\t{error, {Reason1, Reason2}};\n\t\tError ->\n\t\t\tError\n\tend.\n\nsend_flag(nofin) -> ?QUIC_SEND_FLAG_NONE;\nsend_flag(fin) -> ?QUIC_SEND_FLAG_FIN.\n\n-spec send_datagram(quicer_connection_handle(), iodata())\n\t-> ok | {error, any()}.\n\nsend_datagram(Conn, Data) ->\n\t%% @todo Fix/ignore the Dialyzer error instead of doing this.\n\tDataBin = iolist_to_binary(Data),\n\tSize = byte_size(DataBin),\n\tcase quicer:send_dgram(Conn, DataBin) of\n\t\t{ok, Size} ->\n\t\t\tok;\n\t\t%% @todo Handle error cases.\n\t\tError ->\n\t\t\tError\n\tend.\n\n-spec shutdown_stream(quicer_connection_handle(), cow_http3:stream_id())\n\t-> ok.\n\nshutdown_stream(_Conn, StreamID) ->\n\tStreamRef = get({quicer_stream, StreamID}),\n\t_ = quicer:shutdown_stream(StreamRef),\n\tok.\n\n-spec shutdown_stream(quicer_connection_handle(),\n\tcow_http3:stream_id(), both | receiving, quicer_app_errno())\n\t-> ok.\n\nshutdown_stream(_Conn, StreamID, Dir, ErrorCode) ->\n\tStreamRef = get({quicer_stream, StreamID}),\n\t_ = quicer:shutdown_stream(StreamRef, shutdown_flag(Dir), ErrorCode, infinity),\n\tok.\n\n%% @todo Are these flags correct for what we want?\nshutdown_flag(both) -> ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT;\nshutdown_flag(receiving) -> ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT_RECEIVE.\n\n%% Messages.\n\n%% @todo Probably should have the Conn given as argument too?\n-spec handle({quic, _, _, _})\n\t-> {data, cow_http3:stream_id(), cow_http:fin(), binary()}\n\t| {datagram, binary()}\n\t| {stream_started, cow_http3:stream_id(), unidi | bidi}\n\t| {stream_closed, cow_http3:stream_id(), quicer_app_errno()}\n\t| closed\n\t| {peer_send_shutdown, cow_http3:stream_id()}\n\t| ok\n\t| unknown\n\t| {socket_error, any()}.\n\nhandle({quic, Data, StreamRef, #{flags := Flags}}) when is_binary(Data) ->\n\t{ok, StreamID} = quicer:get_stream_id(StreamRef),\n\tIsFin = case Flags band ?QUIC_RECEIVE_FLAG_FIN of\n\t\t?QUIC_RECEIVE_FLAG_FIN -> fin;\n\t\t_ -> nofin\n\tend,\n\t{data, StreamID, IsFin, Data};\n%% @todo Match on Conn.\nhandle({quic, Data, _Conn, Flags}) when is_binary(Data), is_integer(Flags) ->\n\t{datagram, Data};\n%% QUIC_CONNECTION_EVENT_PEER_STREAM_STARTED.\nhandle({quic, new_stream, StreamRef, #{flags := Flags}}) ->\n\tcase quicer:setopt(StreamRef, active, true) of\n\t\tok ->\n\t\t\t{ok, StreamID} = quicer:get_stream_id(StreamRef),\n\t\t\tput({quicer_stream, StreamID}, StreamRef),\n\t\t\tStreamType = case quicer:is_unidirectional(Flags) of\n\t\t\t\ttrue -> unidi;\n\t\t\t\tfalse -> bidi\n\t\t\tend,\n\t\t\t{stream_started, StreamID, StreamType};\n\t\t{error, Reason} ->\n\t\t\t{socket_error, Reason}\n\tend;\n%% QUIC_STREAM_EVENT_SHUTDOWN_COMPLETE.\nhandle({quic, stream_closed, StreamRef, #{error := ErrorCode}}) ->\n\t{ok, StreamID} = quicer:get_stream_id(StreamRef),\n\t{stream_closed, StreamID, ErrorCode};\n%% QUIC_CONNECTION_EVENT_SHUTDOWN_COMPLETE.\nhandle({quic, closed, Conn, _Flags}) ->\n\t_ = quicer:close_connection(Conn),\n\tclosed;\n%% The following events are currently ignored either because\n%% I do not know what they do or because we do not need to\n%% take action.\nhandle({quic, streams_available, _Conn, _Props}) ->\n\tok;\nhandle({quic, dgram_state_changed, _Conn, _Props}) ->\n\tok;\n%% QUIC_CONNECTION_EVENT_SHUTDOWN_INITIATED_BY_TRANSPORT\nhandle({quic, transport_shutdown, _Conn, _Flags}) ->\n\tok;\nhandle({quic, peer_send_shutdown, StreamRef, undefined}) ->\n\t{ok, StreamID} = quicer:get_stream_id(StreamRef),\n\t{peer_send_shutdown, StreamID};\nhandle({quic, send_shutdown_complete, _StreamRef, _IsGraceful}) ->\n\tok;\nhandle({quic, shutdown, _Conn, success}) ->\n\tok;\nhandle(_Msg) ->\n\tunknown.\n\n-endif.\n"
  },
  {
    "path": "src/cowboy_req.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%% Copyright (c) Anthony Ramine <nox@dev-extend.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_req).\n\n%% Request.\n-export([method/1]).\n-export([version/1]).\n-export([peer/1]).\n-export([sock/1]).\n-export([cert/1]).\n-export([scheme/1]).\n-export([host/1]).\n-export([host_info/1]).\n-export([port/1]).\n-export([path/1]).\n-export([path_info/1]).\n-export([qs/1]).\n-export([parse_qs/1]).\n-export([match_qs/2]).\n-export([uri/1]).\n-export([uri/2]).\n-export([binding/2]).\n-export([binding/3]).\n-export([bindings/1]).\n-export([header/2]).\n-export([header/3]).\n-export([headers/1]).\n-export([parse_header/2]).\n-export([parse_header/3]).\n-export([filter_cookies/2]).\n-export([parse_cookies/1]).\n-export([match_cookies/2]).\n\n%% Request body.\n-export([has_body/1]).\n-export([body_length/1]).\n-export([read_body/1]).\n-export([read_body/2]).\n-export([read_urlencoded_body/1]).\n-export([read_urlencoded_body/2]).\n-export([read_and_match_urlencoded_body/2]).\n-export([read_and_match_urlencoded_body/3]).\n\n%% Multipart.\n-export([read_part/1]).\n-export([read_part/2]).\n-export([read_part_body/1]).\n-export([read_part_body/2]).\n\n%% Response.\n-export([set_resp_cookie/3]).\n-export([set_resp_cookie/4]).\n-export([resp_header/2]).\n-export([resp_header/3]).\n-export([resp_headers/1]).\n-export([set_resp_header/3]).\n-export([set_resp_headers/2]).\n-export([has_resp_header/2]).\n-export([delete_resp_header/2]).\n-export([set_resp_body/2]).\n%% @todo set_resp_body/3 with a ContentType or even Headers argument, to set content headers.\n-export([has_resp_body/1]).\n-export([inform/2]).\n-export([inform/3]).\n-export([reply/2]).\n-export([reply/3]).\n-export([reply/4]).\n-export([stream_reply/2]).\n-export([stream_reply/3]).\n%% @todo stream_body/2 (nofin)\n-export([stream_body/3]).\n%% @todo stream_events/2 (nofin)\n-export([stream_events/3]).\n-export([stream_trailers/2]).\n-export([push/3]).\n-export([push/4]).\n\n%% Stream handlers.\n-export([cast/2]).\n\n%% Internal.\n-export([response_headers/2]).\n\n-type read_body_opts() :: #{\n\tlength => non_neg_integer() | infinity,\n\tperiod => non_neg_integer(),\n\ttimeout => timeout()\n}.\n-export_type([read_body_opts/0]).\n\n%% While sendfile allows a Len of 0 that means \"everything past Offset\",\n%% Cowboy expects the real length as it is used as metadata.\n-type resp_body() :: iodata()\n\t| {sendfile, non_neg_integer(), non_neg_integer(), file:name_all()}.\n-export_type([resp_body/0]).\n\n-type push_opts() :: #{\n\tmethod => binary(),\n\tscheme => binary(),\n\thost => binary(),\n\tport => inet:port_number(),\n\tqs => binary()\n}.\n-export_type([push_opts/0]).\n\n-type req() :: #{\n\t%% Public interface.\n\tmethod := binary(),\n\tversion := cowboy:http_version() | atom(),\n\tscheme := binary(),\n\thost := binary(),\n\tport := inet:port_number(),\n\tpath := binary(),\n\tqs := binary(),\n\theaders := cowboy:http_headers(),\n\tpeer := {inet:ip_address(), inet:port_number()},\n\tsock := {inet:ip_address(), inet:port_number()},\n\tcert := binary() | undefined,\n\n\t%% Private interface.\n\tref := ranch:ref(),\n\tpid := pid(),\n\tstreamid := cowboy_stream:streamid(),\n\n\thost_info => cowboy_router:tokens(),\n\tpath_info => cowboy_router:tokens(),\n\tbindings => cowboy_router:bindings(),\n\n\thas_body := boolean(),\n\tbody_length := non_neg_integer() | undefined,\n\thas_read_body => true,\n\tmultipart => {binary(), binary()} | done,\n\n\thas_sent_resp => headers | true,\n\tresp_cookies => #{iodata() => iodata()},\n\tresp_headers => #{binary() => iodata()},\n\tresp_body => resp_body(),\n\n\tproxy_header => ranch_proxy_header:proxy_info(),\n\tmedia_type => {binary(), binary(), [{binary(), binary()}]},\n\tlanguage => binary() | undefined,\n\tcharset => binary() | undefined,\n\trange => {binary(), binary()\n\t\t| [{non_neg_integer(), non_neg_integer() | infinity} | neg_integer()]},\n\twebsocket_version => 7 | 8 | 13,\n\n\t%% The user is encouraged to use the Req to store information\n\t%% when no better solution is available.\n\t_ => _\n}.\n-export_type([req/0]).\n\n%% Request.\n\n-spec method(req()) -> binary().\nmethod(#{method := Method}) ->\n\tMethod.\n\n-spec version(req()) -> cowboy:http_version().\nversion(#{version := Version}) ->\n\tVersion.\n\n-spec peer(req()) -> {inet:ip_address(), inet:port_number()}.\npeer(#{peer := Peer}) ->\n\tPeer.\n\n-spec sock(req()) -> {inet:ip_address(), inet:port_number()}.\nsock(#{sock := Sock}) ->\n\tSock.\n\n-spec cert(req()) -> binary() | undefined.\ncert(#{cert := Cert}) ->\n\tCert.\n\n-spec scheme(req()) -> binary().\nscheme(#{scheme := Scheme}) ->\n\tScheme.\n\n-spec host(req()) -> binary().\nhost(#{host := Host}) ->\n\tHost.\n\n%% @todo The host_info is undefined if cowboy_router isn't used. Do we want to crash?\n-spec host_info(req()) -> cowboy_router:tokens() | undefined.\nhost_info(#{host_info := HostInfo}) ->\n\tHostInfo.\n\n-spec port(req()) -> inet:port_number().\nport(#{port := Port}) ->\n\tPort.\n\n-spec path(req()) -> binary().\npath(#{path := Path}) ->\n\tPath.\n\n%% @todo The path_info is undefined if cowboy_router isn't used. Do we want to crash?\n-spec path_info(req()) -> cowboy_router:tokens() | undefined.\npath_info(#{path_info := PathInfo}) ->\n\tPathInfo.\n\n-spec qs(req()) -> binary().\nqs(#{qs := Qs}) ->\n\tQs.\n\n%% @todo Might be useful to limit the number of keys.\n-spec parse_qs(req()) -> [{binary(), binary() | true}].\nparse_qs(#{qs := Qs}) ->\n\ttry\n\t\tcow_qs:parse_qs(Qs)\n\tcatch _:_:Stacktrace ->\n\t\terlang:raise(exit, {request_error, qs,\n\t\t\t'Malformed query string; application/x-www-form-urlencoded expected.'\n\t\t}, Stacktrace)\n\tend.\n\n-spec match_qs(cowboy:fields(), req()) -> map().\nmatch_qs(Fields, Req) ->\n\tcase filter(Fields, kvlist_to_map(Fields, parse_qs(Req))) of\n\t\t{ok, Map} ->\n\t\t\tMap;\n\t\t{error, Errors} ->\n\t\t\texit({request_error, {match_qs, Errors},\n\t\t\t\t'Query string validation constraints failed for the reasons provided.'})\n\tend.\n\n-spec uri(req()) -> iodata().\nuri(Req) ->\n\turi(Req, #{}).\n\n-spec uri(req(), map()) -> iodata().\nuri(#{scheme := Scheme0, host := Host0, port := Port0,\n\t\tpath := Path0, qs := Qs0}, Opts) ->\n\tScheme = case maps:get(scheme, Opts, Scheme0) of\n\t\tS = undefined -> S;\n\t\tS -> iolist_to_binary(S)\n\tend,\n\tHost = maps:get(host, Opts, Host0),\n\tPort = maps:get(port, Opts, Port0),\n\t{Path, Qs} = case maps:get(path, Opts, Path0) of\n\t\t<<\"*\">> -> {<<>>, <<>>};\n\t\tP -> {P, maps:get(qs, Opts, Qs0)}\n\tend,\n\tFragment = maps:get(fragment, Opts, undefined),\n\t[uri_host(Scheme, Scheme0, Port, Host), uri_path(Path), uri_qs(Qs), uri_fragment(Fragment)].\n\nuri_host(_, _, _, undefined) -> <<>>;\nuri_host(Scheme, Scheme0, Port, Host) ->\n\tcase iolist_size(Host) of\n\t\t0 -> <<>>;\n\t\t_ -> [uri_scheme(Scheme), <<\"//\">>, Host, uri_port(Scheme, Scheme0, Port)]\n\tend.\n\nuri_scheme(undefined) -> <<>>;\nuri_scheme(Scheme) ->\n\tcase iolist_size(Scheme) of\n\t\t0 -> Scheme;\n\t\t_ -> [Scheme, $:]\n\tend.\n\nuri_port(_, _, undefined) -> <<>>;\nuri_port(undefined, <<\"http\">>, 80) -> <<>>;\nuri_port(undefined, <<\"https\">>, 443) -> <<>>;\nuri_port(<<\"http\">>, _, 80) -> <<>>;\nuri_port(<<\"https\">>, _, 443) -> <<>>;\nuri_port(_, _, Port) ->\n\t[$:, integer_to_binary(Port)].\n\nuri_path(undefined) -> <<>>;\nuri_path(Path) -> Path.\n\nuri_qs(undefined) -> <<>>;\nuri_qs(Qs) ->\n\tcase iolist_size(Qs) of\n\t\t0 -> Qs;\n\t\t_ -> [$?, Qs]\n\tend.\n\nuri_fragment(undefined) -> <<>>;\nuri_fragment(Fragment) ->\n\tcase iolist_size(Fragment) of\n\t\t0 -> Fragment;\n\t\t_ -> [$#, Fragment]\n\tend.\n\n-ifdef(TEST).\nuri1_test() ->\n\t<<\"http://localhost/path\">> = iolist_to_binary(uri(#{\n\t\tscheme => <<\"http\">>, host => <<\"localhost\">>, port => 80,\n\t\tpath => <<\"/path\">>, qs => <<>>})),\n\t<<\"http://localhost:443/path\">> = iolist_to_binary(uri(#{\n\t\tscheme => <<\"http\">>, host => <<\"localhost\">>, port => 443,\n\t\tpath => <<\"/path\">>, qs => <<>>})),\n\t<<\"http://localhost:8080/path\">> = iolist_to_binary(uri(#{\n\t\tscheme => <<\"http\">>, host => <<\"localhost\">>, port => 8080,\n\t\tpath => <<\"/path\">>, qs => <<>>})),\n\t<<\"http://localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(#{\n\t\tscheme => <<\"http\">>, host => <<\"localhost\">>, port => 8080,\n\t\tpath => <<\"/path\">>, qs => <<\"dummy=2785\">>})),\n\t<<\"https://localhost/path\">> = iolist_to_binary(uri(#{\n\t\tscheme => <<\"https\">>, host => <<\"localhost\">>, port => 443,\n\t\tpath => <<\"/path\">>, qs => <<>>})),\n\t<<\"https://localhost:8443/path\">> = iolist_to_binary(uri(#{\n\t\tscheme => <<\"https\">>, host => <<\"localhost\">>, port => 8443,\n\t\tpath => <<\"/path\">>, qs => <<>>})),\n\t<<\"https://localhost:8443/path?dummy=2785\">> = iolist_to_binary(uri(#{\n\t\tscheme => <<\"https\">>, host => <<\"localhost\">>, port => 8443,\n\t\tpath => <<\"/path\">>, qs => <<\"dummy=2785\">>})),\n\tok.\n\nuri2_test() ->\n\tReq = #{\n\t\tscheme => <<\"http\">>, host => <<\"localhost\">>, port => 8080,\n\t\tpath => <<\"/path\">>, qs => <<\"dummy=2785\">>\n\t},\n\t<<\"http://localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{})),\n\t%% Disable individual components.\n\t<<\"//localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{scheme => undefined})),\n\t<<\"/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{host => undefined})),\n\t<<\"http://localhost/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{port => undefined})),\n\t<<\"http://localhost:8080?dummy=2785\">> = iolist_to_binary(uri(Req, #{path => undefined})),\n\t<<\"http://localhost:8080/path\">> = iolist_to_binary(uri(Req, #{qs => undefined})),\n\t<<\"http://localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{fragment => undefined})),\n\t<<\"http://localhost:8080\">> = iolist_to_binary(uri(Req, #{path => undefined, qs => undefined})),\n\t<<>> = iolist_to_binary(uri(Req, #{host => undefined, path => undefined, qs => undefined})),\n\t%% Empty values.\n\t<<\"//localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{scheme => <<>>})),\n\t<<\"//localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{scheme => \"\"})),\n\t<<\"//localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{scheme => [<<>>]})),\n\t<<\"/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{host => <<>>})),\n\t<<\"/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{host => \"\"})),\n\t<<\"/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{host => [<<>>]})),\n\t<<\"http://localhost:8080?dummy=2785\">> = iolist_to_binary(uri(Req, #{path => <<>>})),\n\t<<\"http://localhost:8080?dummy=2785\">> = iolist_to_binary(uri(Req, #{path => \"\"})),\n\t<<\"http://localhost:8080?dummy=2785\">> = iolist_to_binary(uri(Req, #{path => [<<>>]})),\n\t<<\"http://localhost:8080/path\">> = iolist_to_binary(uri(Req, #{qs => <<>>})),\n\t<<\"http://localhost:8080/path\">> = iolist_to_binary(uri(Req, #{qs => \"\"})),\n\t<<\"http://localhost:8080/path\">> = iolist_to_binary(uri(Req, #{qs => [<<>>]})),\n\t<<\"http://localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{fragment => <<>>})),\n\t<<\"http://localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{fragment => \"\"})),\n\t<<\"http://localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{fragment => [<<>>]})),\n\t%% Port is integer() | undefined.\n\t{'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => <<>>}))),\n\t{'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => \"\"}))),\n\t{'EXIT', _} = (catch iolist_to_binary(uri(Req, #{port => [<<>>]}))),\n\t%% Update components.\n\t<<\"https://localhost:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{scheme => \"https\"})),\n\t<<\"http://example.org:8080/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{host => \"example.org\"})),\n\t<<\"http://localhost:123/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{port => 123})),\n\t<<\"http://localhost:8080/custom?dummy=2785\">> = iolist_to_binary(uri(Req, #{path => \"/custom\"})),\n\t<<\"http://localhost:8080/path?smart=42\">> = iolist_to_binary(uri(Req, #{qs => \"smart=42\"})),\n\t<<\"http://localhost:8080/path?dummy=2785#intro\">> = iolist_to_binary(uri(Req, #{fragment => \"intro\"})),\n\t%% Interesting combinations.\n\t<<\"http://localhost/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{port => 80})),\n\t<<\"https://localhost/path?dummy=2785\">> = iolist_to_binary(uri(Req, #{scheme => \"https\", port => 443})),\n\tok.\n-endif.\n\n-spec binding(atom(), req()) -> any() | undefined.\nbinding(Name, Req) ->\n\tbinding(Name, Req, undefined).\n\n-spec binding(atom(), req(), Default) -> any() | Default when Default::any().\nbinding(Name, #{bindings := Bindings}, Default) when is_atom(Name) ->\n\tcase Bindings of\n\t\t#{Name := Value} -> Value;\n\t\t_ -> Default\n\tend;\nbinding(Name, _, Default) when is_atom(Name) ->\n\tDefault.\n\n-spec bindings(req()) -> cowboy_router:bindings().\nbindings(#{bindings := Bindings}) ->\n\tBindings;\nbindings(_) ->\n\t#{}.\n\n-spec header(binary(), req()) -> binary() | undefined.\nheader(Name, Req) ->\n\theader(Name, Req, undefined).\n\n-spec header(binary(), req(), Default) -> binary() | Default when Default::any().\nheader(Name, #{headers := Headers}, Default) ->\n\tmaps:get(Name, Headers, Default).\n\n-spec headers(req()) -> cowboy:http_headers().\nheaders(#{headers := Headers}) ->\n\tHeaders.\n\n-spec parse_header(binary(), Req) -> any() when Req::req().\nparse_header(Name = <<\"content-length\">>, Req) ->\n\tparse_header(Name, Req, 0);\nparse_header(Name = <<\"cookie\">>, Req) ->\n\tparse_header(Name, Req, []);\nparse_header(Name, Req) ->\n\tparse_header(Name, Req, undefined).\n\n-spec parse_header(binary(), Req, any()) -> any() when Req::req().\nparse_header(Name, Req, Default) ->\n\ttry\n\t\tparse_header(Name, Req, Default, parse_header_fun(Name))\n\tcatch _:_:Stacktrace ->\n\t\terlang:raise(exit, {request_error, {header, Name},\n\t\t\t'Malformed header. Please consult the relevant specification.'\n\t\t}, Stacktrace)\n\tend.\n\nparse_header_fun(<<\"accept\">>) -> fun cow_http_hd:parse_accept/1;\nparse_header_fun(<<\"accept-charset\">>) -> fun cow_http_hd:parse_accept_charset/1;\nparse_header_fun(<<\"accept-encoding\">>) -> fun cow_http_hd:parse_accept_encoding/1;\nparse_header_fun(<<\"accept-language\">>) -> fun cow_http_hd:parse_accept_language/1;\nparse_header_fun(<<\"access-control-request-headers\">>) -> fun cow_http_hd:parse_access_control_request_headers/1;\nparse_header_fun(<<\"access-control-request-method\">>) -> fun cow_http_hd:parse_access_control_request_method/1;\nparse_header_fun(<<\"authorization\">>) -> fun cow_http_hd:parse_authorization/1;\nparse_header_fun(<<\"connection\">>) -> fun cow_http_hd:parse_connection/1;\nparse_header_fun(<<\"content-encoding\">>) -> fun cow_http_hd:parse_content_encoding/1;\nparse_header_fun(<<\"content-language\">>) -> fun cow_http_hd:parse_content_language/1;\nparse_header_fun(<<\"content-length\">>) -> fun cow_http_hd:parse_content_length/1;\nparse_header_fun(<<\"content-type\">>) -> fun cow_http_hd:parse_content_type/1;\nparse_header_fun(<<\"cookie\">>) -> fun cow_cookie:parse_cookie/1;\nparse_header_fun(<<\"expect\">>) -> fun cow_http_hd:parse_expect/1;\nparse_header_fun(<<\"if-match\">>) -> fun cow_http_hd:parse_if_match/1;\nparse_header_fun(<<\"if-modified-since\">>) -> fun cow_http_hd:parse_if_modified_since/1;\nparse_header_fun(<<\"if-none-match\">>) -> fun cow_http_hd:parse_if_none_match/1;\nparse_header_fun(<<\"if-range\">>) -> fun cow_http_hd:parse_if_range/1;\nparse_header_fun(<<\"if-unmodified-since\">>) -> fun cow_http_hd:parse_if_unmodified_since/1;\nparse_header_fun(<<\"max-forwards\">>) -> fun cow_http_hd:parse_max_forwards/1;\nparse_header_fun(<<\"origin\">>) -> fun cow_http_hd:parse_origin/1;\nparse_header_fun(<<\"proxy-authorization\">>) -> fun cow_http_hd:parse_proxy_authorization/1;\nparse_header_fun(<<\"range\">>) -> fun cow_http_hd:parse_range/1;\nparse_header_fun(<<\"sec-websocket-extensions\">>) -> fun cow_http_hd:parse_sec_websocket_extensions/1;\nparse_header_fun(<<\"sec-websocket-protocol\">>) -> fun cow_http_hd:parse_sec_websocket_protocol_req/1;\nparse_header_fun(<<\"sec-websocket-version\">>) -> fun cow_http_hd:parse_sec_websocket_version_req/1;\nparse_header_fun(<<\"trailer\">>) -> fun cow_http_hd:parse_trailer/1;\nparse_header_fun(<<\"upgrade\">>) -> fun cow_http_hd:parse_upgrade/1;\nparse_header_fun(<<\"wt-available-protocols\">>) -> fun cow_http_hd:parse_wt_available_protocols/1;\nparse_header_fun(<<\"x-forwarded-for\">>) -> fun cow_http_hd:parse_x_forwarded_for/1.\n\nparse_header(Name, Req, Default, ParseFun) ->\n\tcase header(Name, Req) of\n\t\tundefined -> Default;\n\t\tValue -> ParseFun(Value)\n\tend.\n\n-spec filter_cookies([atom() | binary()], Req) -> Req when Req::req().\nfilter_cookies(Names0, Req=#{headers := Headers}) ->\n\tNames = [if\n\t\tis_atom(N) -> atom_to_binary(N, utf8);\n\t\ttrue -> N\n\tend || N <- Names0],\n\tcase header(<<\"cookie\">>, Req) of\n\t\tundefined -> Req;\n\t\tValue0 ->\n\t\t\tCookies0 = binary:split(Value0, <<$;>>, [global]),\n\t\t\tCookies = lists:filter(fun(Cookie) ->\n\t\t\t\tlists:member(cookie_name(Cookie), Names)\n\t\t\tend, Cookies0),\n\t\t\tValue = iolist_to_binary(lists:join($;, Cookies)),\n\t\t\tReq#{headers => Headers#{<<\"cookie\">> => Value}}\n\tend.\n\n%% This is a specialized function to extract a cookie name\n%% regardless of whether the name is valid or not. We skip\n%% whitespace at the beginning and take whatever's left to\n%% be the cookie name, up to the = sign.\ncookie_name(<<$\\s, Rest/binary>>) -> cookie_name(Rest);\ncookie_name(<<$\\t, Rest/binary>>) -> cookie_name(Rest);\ncookie_name(Name) -> cookie_name(Name, <<>>).\n\ncookie_name(<<>>, Name) -> Name;\ncookie_name(<<$=, _/bits>>, Name) -> Name;\ncookie_name(<<C, Rest/bits>>, Acc) -> cookie_name(Rest, <<Acc/binary, C>>).\n\n-spec parse_cookies(req()) -> [{binary(), binary()}].\nparse_cookies(Req) ->\n\tparse_header(<<\"cookie\">>, Req).\n\n-spec match_cookies(cowboy:fields(), req()) -> map().\nmatch_cookies(Fields, Req) ->\n\tcase filter(Fields, kvlist_to_map(Fields, parse_cookies(Req))) of\n\t\t{ok, Map} ->\n\t\t\tMap;\n\t\t{error, Errors} ->\n\t\t\texit({request_error, {match_cookies, Errors},\n\t\t\t\t'Cookie validation constraints failed for the reasons provided.'})\n\tend.\n\n%% Request body.\n\n-spec has_body(req()) -> boolean().\nhas_body(#{has_body := HasBody}) ->\n\tHasBody.\n\n%% The length may not be known if HTTP/1.1 with a transfer-encoding;\n%% or HTTP/2 with no content-length header. The length is always\n%% known once the body has been completely read.\n-spec body_length(req()) -> undefined | non_neg_integer().\nbody_length(#{body_length := Length}) ->\n\tLength.\n\n-spec read_body(Req) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().\nread_body(Req) ->\n\tread_body(Req, #{}).\n\n-spec read_body(Req, read_body_opts()) -> {ok, binary(), Req} | {more, binary(), Req} when Req::req().\nread_body(Req=#{has_body := false}, _) ->\n\t{ok, <<>>, Req};\nread_body(Req=#{has_read_body := true}, _) ->\n\t{ok, <<>>, Req};\nread_body(Req, Opts) ->\n\tLength = maps:get(length, Opts, 8000000),\n\tPeriod = maps:get(period, Opts, 15000),\n\tDefaultTimeout = case Period of\n\t\tinfinity -> infinity; %% infinity + 1000 = infinity.\n\t\t_ -> Period + 1000\n\tend,\n\tTimeout = maps:get(timeout, Opts, DefaultTimeout),\n\tRef = make_ref(),\n\tcast({read_body, self(), Ref, Length, Period}, Req),\n\treceive\n\t\t{request_body, Ref, nofin, Body} ->\n\t\t\t{more, Body, Req};\n\t\t{request_body, Ref, fin, BodyLength, Body} ->\n\t\t\t{ok, Body, set_body_length(Req, BodyLength)}\n\tafter Timeout ->\n\t\texit(timeout)\n\tend.\n\nset_body_length(Req=#{headers := Headers}, BodyLength) ->\n\tReq#{\n\t\theaders => Headers#{<<\"content-length\">> => integer_to_binary(BodyLength)},\n\t\tbody_length => BodyLength,\n\t\thas_read_body => true\n\t}.\n\n-spec read_urlencoded_body(Req) -> {ok, [{binary(), binary() | true}], Req} when Req::req().\nread_urlencoded_body(Req) ->\n\tread_urlencoded_body(Req, #{length => 64000, period => 5000}).\n\n-spec read_urlencoded_body(Req, read_body_opts()) -> {ok, [{binary(), binary() | true}], Req} when Req::req().\nread_urlencoded_body(Req0, Opts) ->\n\tcase read_body(Req0, Opts) of\n\t\t{ok, Body, Req} ->\n\t\t\ttry\n\t\t\t\t{ok, cow_qs:parse_qs(Body), Req}\n\t\t\tcatch _:_:Stacktrace ->\n\t\t\t\terlang:raise(exit, {request_error, urlencoded_body,\n\t\t\t\t\t'Malformed body; application/x-www-form-urlencoded expected.'\n\t\t\t\t}, Stacktrace)\n\t\t\tend;\n\t\t{more, Body, _} ->\n\t\t\tLength = maps:get(length, Opts, 64000),\n\t\t\tif\n\t\t\t\tbyte_size(Body) < Length ->\n\t\t\t\t\texit({request_error, timeout,\n\t\t\t\t\t\t'The request body was not received within the configured time.'});\n\t\t\t\ttrue ->\n\t\t\t\t\texit({request_error, payload_too_large,\n\t\t\t\t\t\t'The request body is larger than allowed by configuration.'})\n\t\t\tend\n\tend.\n\n-spec read_and_match_urlencoded_body(cowboy:fields(), Req)\n\t-> {ok, map(), Req} when Req::req().\nread_and_match_urlencoded_body(Fields, Req) ->\n\tread_and_match_urlencoded_body(Fields, Req, #{length => 64000, period => 5000}).\n\n-spec read_and_match_urlencoded_body(cowboy:fields(), Req, read_body_opts())\n\t-> {ok, map(), Req} when Req::req().\nread_and_match_urlencoded_body(Fields, Req0, Opts) ->\n\t{ok, Qs, Req} = read_urlencoded_body(Req0, Opts),\n\tcase filter(Fields, kvlist_to_map(Fields, Qs)) of\n\t\t{ok, Map} ->\n\t\t\t{ok, Map, Req};\n\t\t{error, Errors} ->\n\t\t\texit({request_error, {read_and_match_urlencoded_body, Errors},\n\t\t\t\t'Urlencoded request body validation constraints failed for the reasons provided.'})\n\tend.\n\n%% Multipart.\n\n-spec read_part(Req)\n\t-> {ok, cowboy:http_headers(), Req} | {done, Req}\n\twhen Req::req().\nread_part(Req) ->\n\tread_part(Req, #{length => 64000, period => 5000}).\n\n-spec read_part(Req, read_body_opts())\n\t-> {ok, cowboy:http_headers(), Req} | {done, Req}\n\twhen Req::req().\nread_part(Req, Opts) ->\n\tcase maps:is_key(multipart, Req) of\n\t\ttrue ->\n\t\t\t{Data, Req2} = stream_multipart(Req, Opts, headers),\n\t\t\tread_part(Data, Opts, Req2);\n\t\tfalse ->\n\t\t\tread_part(init_multipart(Req), Opts)\n\tend.\n\nread_part(Buffer, Opts, Req=#{multipart := {Boundary, _}}) ->\n\ttry cow_multipart:parse_headers(Buffer, Boundary) of\n\t\tmore ->\n\t\t\t{Data, Req2} = stream_multipart(Req, Opts, headers),\n\t\t\tread_part(<< Buffer/binary, Data/binary >>, Opts, Req2);\n\t\t{more, Buffer2} ->\n\t\t\t{Data, Req2} = stream_multipart(Req, Opts, headers),\n\t\t\tread_part(<< Buffer2/binary, Data/binary >>, Opts, Req2);\n\t\t{ok, Headers0, Rest} ->\n\t\t\tHeaders = maps:from_list(Headers0),\n\t\t\t%% Reject multipart content containing duplicate headers.\n\t\t\ttrue = map_size(Headers) =:= length(Headers0),\n\t\t\t{ok, Headers, Req#{multipart => {Boundary, Rest}}};\n\t\t%% Ignore epilogue.\n\t\t{done, _} ->\n\t\t\t{done, Req#{multipart => done}}\n\tcatch _:_:Stacktrace ->\n\t\terlang:raise(exit, {request_error, {multipart, headers},\n\t\t\t'Malformed body; multipart expected.'\n\t\t}, Stacktrace)\n\tend.\n\n-spec read_part_body(Req)\n\t-> {ok, binary(), Req} | {more, binary(), Req}\n\twhen Req::req().\nread_part_body(Req) ->\n\tread_part_body(Req, #{}).\n\n-spec read_part_body(Req, read_body_opts())\n\t-> {ok, binary(), Req} | {more, binary(), Req}\n\twhen Req::req().\nread_part_body(Req, Opts) ->\n\tcase maps:is_key(multipart, Req) of\n\t\ttrue ->\n\t\t\tread_part_body(<<>>, Opts, Req, <<>>);\n\t\tfalse ->\n\t\t\tread_part_body(init_multipart(Req), Opts)\n\tend.\n\nread_part_body(Buffer, Opts, Req=#{multipart := {Boundary, _}}, Acc) ->\n\tLength = maps:get(length, Opts, 8000000),\n\tcase byte_size(Acc) > Length of\n\t\ttrue ->\n\t\t\t{more, Acc, Req#{multipart => {Boundary, Buffer}}};\n\t\tfalse ->\n\t\t\t{Data, Req2} = stream_multipart(Req, Opts, body),\n\t\t\tcase cow_multipart:parse_body(<< Buffer/binary, Data/binary >>, Boundary) of\n\t\t\t\t{ok, Body} ->\n\t\t\t\t\tread_part_body(<<>>, Opts, Req2, << Acc/binary, Body/binary >>);\n\t\t\t\t{ok, Body, Rest} ->\n\t\t\t\t\tread_part_body(Rest, Opts, Req2, << Acc/binary, Body/binary >>);\n\t\t\t\tdone ->\n\t\t\t\t\t{ok, Acc, Req2};\n\t\t\t\t{done, Body} ->\n\t\t\t\t\t{ok, << Acc/binary, Body/binary >>, Req2};\n\t\t\t\t{done, Body, Rest} ->\n\t\t\t\t\t{ok, << Acc/binary, Body/binary >>,\n\t\t\t\t\t\tReq2#{multipart => {Boundary, Rest}}}\n\t\t\tend\n\tend.\n\ninit_multipart(Req) ->\n\t{<<\"multipart\">>, _, Params} = parse_header(<<\"content-type\">>, Req),\n\tcase lists:keyfind(<<\"boundary\">>, 1, Params) of\n\t\t{_, Boundary} ->\n\t\t\tReq#{multipart => {Boundary, <<>>}};\n\t\tfalse ->\n\t\t\texit({request_error, {multipart, boundary},\n\t\t\t\t'Missing boundary parameter for multipart media type.'})\n\tend.\n\nstream_multipart(Req=#{multipart := done}, _, _) ->\n\t{<<>>, Req};\nstream_multipart(Req=#{multipart := {_, <<>>}}, Opts, Type) ->\n\tcase read_body(Req, Opts) of\n\t\t{more, Data, Req2} ->\n\t\t\t{Data, Req2};\n\t\t%% We crash when the data ends unexpectedly.\n\t\t{ok, <<>>, _} ->\n\t\t\texit({request_error, {multipart, Type},\n\t\t\t\t'Malformed body; multipart expected.'});\n\t\t{ok, Data, Req2} ->\n\t\t\t{Data, Req2}\n\tend;\nstream_multipart(Req=#{multipart := {Boundary, Buffer}}, _, _) ->\n\t{Buffer, Req#{multipart => {Boundary, <<>>}}}.\n\n%% Response.\n\n-spec set_resp_cookie(iodata(), iodata(), Req)\n\t-> Req when Req::req().\nset_resp_cookie(Name, Value, Req) ->\n\tset_resp_cookie(Name, Value, Req, #{}).\n\n%% The cookie name cannot contain any of the following characters:\n%%   =,;\\s\\t\\r\\n\\013\\014\n%%\n%% The cookie value cannot contain any of the following characters:\n%%   ,; \\t\\r\\n\\013\\014\n-spec set_resp_cookie(binary(), iodata(), Req, cow_cookie:cookie_opts())\n\t-> Req when Req::req().\nset_resp_cookie(Name, Value, Req, Opts) ->\n\tCookie = cow_cookie:setcookie(Name, Value, Opts),\n\tRespCookies = maps:get(resp_cookies, Req, #{}),\n\tReq#{resp_cookies => RespCookies#{Name => Cookie}}.\n\n%% @todo We could add has_resp_cookie and unset_resp_cookie now.\n\n-spec set_resp_header(binary(), iodata(), Req)\n\t-> Req when Req::req().\nset_resp_header(<<\"set-cookie\">>, _, _) ->\n\texit({response_error, invalid_header,\n\t\t'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'});\nset_resp_header(Name, Value, Req=#{resp_headers := RespHeaders}) ->\n\tReq#{resp_headers => RespHeaders#{Name => Value}};\nset_resp_header(Name,Value, Req) ->\n\tReq#{resp_headers => #{Name => Value}}.\n\n-spec set_resp_headers(cowboy:http_headers() | [{binary(), iodata()}], Req)\n\t-> Req when Req::req().\nset_resp_headers(Headers, Req) when is_list(Headers) ->\n\tset_resp_headers_list(Headers, Req, #{});\nset_resp_headers(#{<<\"set-cookie\">> := _}, _) ->\n\texit({response_error, invalid_header,\n\t\t'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'});\nset_resp_headers(Headers, Req=#{resp_headers := RespHeaders}) ->\n\tReq#{resp_headers => maps:merge(RespHeaders, Headers)};\nset_resp_headers(Headers, Req) ->\n\tReq#{resp_headers => Headers}.\n\nset_resp_headers_list([], Req, Acc) ->\n\tset_resp_headers(Acc, Req);\nset_resp_headers_list([{<<\"set-cookie\">>, _}|_], _, _) ->\n\texit({response_error, invalid_header,\n\t\t'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'});\nset_resp_headers_list([{Name, Value}|Tail], Req, Acc) ->\n\tcase Acc of\n\t\t#{Name := ValueAcc} ->\n\t\t\tset_resp_headers_list(Tail, Req, Acc#{Name => [ValueAcc, <<\", \">>, Value]});\n\t\t_ ->\n\t\t\tset_resp_headers_list(Tail, Req, Acc#{Name => Value})\n\tend.\n\n-spec resp_header(binary(), req()) -> binary() | undefined.\nresp_header(Name, Req) ->\n\tresp_header(Name, Req, undefined).\n\n-spec resp_header(binary(), req(), Default)\n\t-> binary() | Default when Default::any().\nresp_header(Name, #{resp_headers := Headers}, Default) ->\n\tmaps:get(Name, Headers, Default);\nresp_header(_, #{}, Default) ->\n\tDefault.\n\n-spec resp_headers(req()) -> cowboy:http_headers().\nresp_headers(#{resp_headers := RespHeaders}) ->\n\tRespHeaders;\nresp_headers(#{}) ->\n\t#{}.\n\n-spec set_resp_body(resp_body(), Req) -> Req when Req::req().\nset_resp_body(Body, Req) ->\n\tReq#{resp_body => Body}.\n\n-spec has_resp_header(binary(), req()) -> boolean().\nhas_resp_header(Name, #{resp_headers := RespHeaders}) ->\n\tmaps:is_key(Name, RespHeaders);\nhas_resp_header(_, _) ->\n\tfalse.\n\n-spec has_resp_body(req()) -> boolean().\nhas_resp_body(#{resp_body := {sendfile, _, _, _}}) ->\n\ttrue;\nhas_resp_body(#{resp_body := RespBody}) ->\n\tiolist_size(RespBody) > 0;\nhas_resp_body(_) ->\n\tfalse.\n\n-spec delete_resp_header(binary(), Req)\n\t-> Req when Req::req().\ndelete_resp_header(Name, Req=#{resp_headers := RespHeaders}) ->\n\tReq#{resp_headers => maps:remove(Name, RespHeaders)};\n%% There are no resp headers so we have nothing to delete.\ndelete_resp_header(_, Req) ->\n\tReq.\n\n-spec inform(cowboy:http_status(), req()) -> ok.\ninform(Status, Req) ->\n\tinform(Status, #{}, Req).\n\n-spec inform(cowboy:http_status(), cowboy:http_headers(), req()) -> ok.\ninform(_, _, #{has_sent_resp := _}) ->\n\texit({response_error, response_already_sent,\n\t\t'The final response has already been sent.'});\ninform(_, #{<<\"set-cookie\">> := _}, _) ->\n\texit({response_error, invalid_header,\n\t\t'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'});\ninform(Status, Headers, Req) when is_integer(Status); is_binary(Status) ->\n\tcast({inform, Status, Headers}, Req).\n\n-spec reply(cowboy:http_status(), Req) -> Req when Req::req().\nreply(Status, Req) ->\n\treply(Status, #{}, Req).\n\n-spec reply(cowboy:http_status(), cowboy:http_headers(), Req)\n\t-> Req when Req::req().\nreply(Status, Headers, Req=#{resp_body := Body}) ->\n\treply(Status, Headers, Body, Req);\nreply(Status, Headers, Req) ->\n\treply(Status, Headers, <<>>, Req).\n\n-spec reply(cowboy:http_status(), cowboy:http_headers(), resp_body(), Req)\n\t-> Req when Req::req().\nreply(_, _, _, #{has_sent_resp := _}) ->\n\texit({response_error, response_already_sent,\n\t\t'The final response has already been sent.'});\nreply(_, #{<<\"set-cookie\">> := _}, _, _) ->\n\texit({response_error, invalid_header,\n\t\t'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'});\nreply(Status, Headers, {sendfile, _, 0, _}, Req)\n\t\twhen is_integer(Status); is_binary(Status) ->\n\tdo_reply(Status, Headers#{\n\t\t<<\"content-length\">> => <<\"0\">>\n\t}, <<>>, Req);\nreply(Status, Headers, SendFile = {sendfile, _, Len, _}, Req)\n\t\twhen is_integer(Status); is_binary(Status) ->\n\tdo_reply(Status, Headers#{\n\t\t<<\"content-length\">> => integer_to_binary(Len)\n\t}, SendFile, Req);\n%% 204 responses must not include content-length. 304 responses may\n%% but only when set explicitly. (RFC7230 3.3.1, RFC7230 3.3.2)\n%% Neither status code must include a response body. (RFC7230 3.3)\nreply(Status, Headers, Body, Req)\n\t\twhen Status =:= 204; Status =:= 304 ->\n\tdo_reply_ensure_no_body(Status, Headers, Body, Req);\nreply(Status = <<\"204\",_/bits>>, Headers, Body, Req) ->\n\tdo_reply_ensure_no_body(Status, Headers, Body, Req);\nreply(Status = <<\"304\",_/bits>>, Headers, Body, Req) ->\n\tdo_reply_ensure_no_body(Status, Headers, Body, Req);\nreply(Status, Headers, Body, Req)\n\t\twhen is_integer(Status); is_binary(Status) ->\n\tdo_reply(Status, Headers#{\n\t\t<<\"content-length\">> => integer_to_binary(iolist_size(Body))\n\t}, Body, Req).\n\ndo_reply_ensure_no_body(Status, Headers, Body, Req) ->\n\tcase iolist_size(Body) of\n\t\t0 ->\n\t\t\tdo_reply(Status, Headers, Body, Req);\n\t\t_ ->\n\t\t\texit({response_error, payload_too_large,\n\t\t\t\t'204 and 304 responses must not include a body. (RFC7230 3.3)'})\n\tend.\n\n%% Don't send any body for HEAD responses. While the protocol code is\n%% supposed to enforce this rule, we prefer to avoid copying too much\n%% data around if we can avoid it.\ndo_reply(Status, Headers, _, Req=#{method := <<\"HEAD\">>}) ->\n\tcast({response, Status, response_headers(Headers, Req), <<>>}, Req),\n\tdone_replying(Req, true);\ndo_reply(Status, Headers, Body, Req) ->\n\tcast({response, Status, response_headers(Headers, Req), Body}, Req),\n\tdone_replying(Req, true).\n\ndone_replying(Req, HasSentResp) ->\n\tmaps:without([resp_cookies, resp_headers, resp_body], Req#{has_sent_resp => HasSentResp}).\n\n-spec stream_reply(cowboy:http_status(), Req) -> Req when Req::req().\nstream_reply(Status, Req) ->\n\tstream_reply(Status, #{}, Req).\n\n-spec stream_reply(cowboy:http_status(), cowboy:http_headers(), Req)\n\t-> Req when Req::req().\nstream_reply(_, _, #{has_sent_resp := _}) ->\n\texit({response_error, response_already_sent,\n\t\t'The final response has already been sent.'});\nstream_reply(_, #{<<\"set-cookie\">> := _}, _) ->\n\texit({response_error, invalid_header,\n\t\t'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'});\n%% 204 and 304 responses must NOT send a body. We therefore\n%% transform the call to a full response and expect the user\n%% to NOT call stream_body/3 afterwards. (RFC7230 3.3)\nstream_reply(Status, Headers=#{}, Req)\n\t\twhen Status =:= 204; Status =:= 304 ->\n\treply(Status, Headers, <<>>, Req);\nstream_reply(Status = <<\"204\",_/bits>>, Headers=#{}, Req) ->\n\treply(Status, Headers, <<>>, Req);\nstream_reply(Status = <<\"304\",_/bits>>, Headers=#{}, Req) ->\n\treply(Status, Headers, <<>>, Req);\nstream_reply(Status, Headers=#{}, Req) when is_integer(Status); is_binary(Status) ->\n\tcast({headers, Status, response_headers(Headers, Req)}, Req),\n\tdone_replying(Req, headers).\n\n-spec stream_body(resp_body(), fin | nofin, req()) -> ok.\n%% Error out if headers were not sent.\n%% Don't send any body for HEAD responses.\nstream_body(_, _, #{method := <<\"HEAD\">>, has_sent_resp := headers}) ->\n\tok;\n%% Don't send a message if the data is empty, except for the\n%% very last message with IsFin=fin. When using sendfile this\n%% is converted to a data tuple, however.\nstream_body({sendfile, _, 0, _}, nofin, _) ->\n\tok;\nstream_body({sendfile, _, 0, _}, IsFin=fin, Req=#{has_sent_resp := headers}) ->\n\tstream_body({data, self(), IsFin, <<>>}, Req);\nstream_body({sendfile, O, B, P}, IsFin, Req=#{has_sent_resp := headers})\n\t\twhen is_integer(O), O >= 0, is_integer(B), B > 0 ->\n\tstream_body({data, self(), IsFin, {sendfile, O, B, P}}, Req);\nstream_body(Data, IsFin=nofin, Req=#{has_sent_resp := headers})\n\t\twhen not is_tuple(Data) ->\n\tcase iolist_size(Data) of\n\t\t0 -> ok;\n\t\t_ -> stream_body({data, self(), IsFin, Data}, Req)\n\tend;\nstream_body(Data, IsFin, Req=#{has_sent_resp := headers})\n\t\twhen not is_tuple(Data) ->\n\tstream_body({data, self(), IsFin, Data}, Req).\n\n%% @todo Do we need a timeout?\nstream_body(Msg, Req=#{pid := Pid}) ->\n\tcast(Msg, Req),\n\treceive {data_ack, Pid} -> ok end.\n\n-spec stream_events(cow_sse:event() | [cow_sse:event()], fin | nofin, req()) -> ok.\nstream_events(Event, IsFin, Req) when is_map(Event) ->\n\tstream_events([Event], IsFin, Req);\nstream_events(Events, IsFin, Req=#{has_sent_resp := headers}) ->\n\tstream_body({data, self(), IsFin, cow_sse:events(Events)}, Req).\n\n-spec stream_trailers(cowboy:http_headers(), req()) -> ok.\nstream_trailers(#{<<\"set-cookie\">> := _}, _) ->\n\texit({response_error, invalid_header,\n\t\t'Response cookies must be set using cowboy_req:set_resp_cookie/3,4.'});\nstream_trailers(Trailers, Req=#{has_sent_resp := headers}) ->\n\tcast({trailers, Trailers}, Req).\n\n-spec push(iodata(), cowboy:http_headers(), req()) -> ok.\npush(Path, Headers, Req) ->\n\tpush(Path, Headers, Req, #{}).\n\n%% @todo Optimization: don't send anything at all for HTTP/1.0 and HTTP/1.1.\n%% @todo Path, Headers, Opts, everything should be in proper binary,\n%% or normalized when creating the Req object.\n-spec push(iodata(), cowboy:http_headers(), req(), push_opts()) -> ok.\npush(_, _, #{has_sent_resp := _}, _) ->\n\texit({response_error, response_already_sent,\n\t\t'The final response has already been sent.'});\npush(Path, Headers, Req=#{scheme := Scheme0, host := Host0, port := Port0}, Opts) ->\n\tMethod = maps:get(method, Opts, <<\"GET\">>),\n\tScheme = maps:get(scheme, Opts, Scheme0),\n\tHost = maps:get(host, Opts, Host0),\n\tPort = maps:get(port, Opts, Port0),\n\tQs = maps:get(qs, Opts, <<>>),\n\tcast({push, Method, Scheme, Host, Port, Path, Qs, Headers}, Req).\n\n%% Stream handlers.\n\n-spec cast(any(), req()) -> ok.\ncast(Msg, #{pid := Pid, streamid := StreamID}) ->\n\tPid ! {{Pid, StreamID}, Msg},\n\tok.\n\n%% Internal.\n\n%% @todo What about set-cookie headers set through set_resp_header or reply?\n-spec response_headers(Headers, req()) -> Headers when Headers::cowboy:http_headers().\nresponse_headers(Headers0, Req) ->\n\tRespHeaders = maps:get(resp_headers, Req, #{}),\n\tHeaders = maps:merge(#{\n\t\t<<\"date\">> => cowboy_clock:rfc1123(),\n\t\t<<\"server\">> => <<\"Cowboy\">>\n\t}, maps:merge(RespHeaders, Headers0)),\n\t%% The set-cookie header is special; we can only send one cookie per header.\n\t%% We send the list of values for many cookies in one key of the map,\n\t%% and let the protocols deal with it directly.\n\tcase maps:get(resp_cookies, Req, undefined) of\n\t\tundefined -> Headers;\n\t\tRespCookies -> Headers#{<<\"set-cookie\">> => maps:values(RespCookies)}\n\tend.\n\n%% Create map, convert keys to atoms and group duplicate keys into lists.\n%% Keys that are not found in the user provided list are entirely skipped.\n%% @todo Can probably be done directly while parsing.\nkvlist_to_map(Fields, KvList) ->\n\tKeys = [case K of\n\t\t{Key, _} -> Key;\n\t\t{Key, _, _} -> Key;\n\t\tKey -> Key\n\tend || K <- Fields],\n\tkvlist_to_map(Keys, KvList, #{}).\n\nkvlist_to_map(_, [], Map) ->\n\tMap;\nkvlist_to_map(Keys, [{Key, Value}|Tail], Map) ->\n\ttry binary_to_existing_atom(Key, utf8) of\n\t\tAtom ->\n\t\t\tcase lists:member(Atom, Keys) of\n\t\t\t\ttrue ->\n\t\t\t\t\tcase maps:find(Atom, Map) of\n\t\t\t\t\t\t{ok, MapValue} when is_list(MapValue) ->\n\t\t\t\t\t\t\tkvlist_to_map(Keys, Tail,\n\t\t\t\t\t\t\t\tMap#{Atom => [Value|MapValue]});\n\t\t\t\t\t\t{ok, MapValue} ->\n\t\t\t\t\t\t\tkvlist_to_map(Keys, Tail,\n\t\t\t\t\t\t\t\tMap#{Atom => [Value, MapValue]});\n\t\t\t\t\t\terror ->\n\t\t\t\t\t\t\tkvlist_to_map(Keys, Tail,\n\t\t\t\t\t\t\t\tMap#{Atom => Value})\n\t\t\t\t\tend;\n\t\t\t\tfalse ->\n\t\t\t\t\tkvlist_to_map(Keys, Tail, Map)\n\t\t\tend\n\tcatch error:badarg ->\n\t\tkvlist_to_map(Keys, Tail, Map)\n\tend.\n\nfilter(Fields, Map0) ->\n\tfilter(Fields, Map0, #{}).\n\n%% Loop through fields, if value is missing and no default,\n%% record the error; else if value is missing and has a\n%% default, set default; otherwise apply constraints. If\n%% constraint fails, record the error.\n%%\n%% When there is an error at the end, crash.\nfilter([], Map, Errors) ->\n\tcase maps:size(Errors) of\n\t\t0 -> {ok, Map};\n\t\t_ -> {error, Errors}\n\tend;\nfilter([{Key, Constraints}|Tail], Map, Errors) ->\n\tcase maps:find(Key, Map) of\n\t\t{ok, Value} ->\n\t\t\tfilter_constraints(Tail, Map, Errors, Key, Value, Constraints);\n\t\terror ->\n\t\t\tfilter(Tail, Map, Errors#{Key => required})\n\tend;\nfilter([{Key, Constraints, Default}|Tail], Map, Errors) ->\n\tcase maps:find(Key, Map) of\n\t\t{ok, Value} ->\n\t\t\tfilter_constraints(Tail, Map, Errors, Key, Value, Constraints);\n\t\terror ->\n\t\t\tfilter(Tail, Map#{Key => Default}, Errors)\n\tend;\nfilter([Key|Tail], Map, Errors) ->\n\tcase maps:is_key(Key, Map) of\n\t\ttrue ->\n\t\t\tfilter(Tail, Map, Errors);\n\t\tfalse ->\n\t\t\tfilter(Tail, Map, Errors#{Key => required})\n\tend.\n\nfilter_constraints(Tail, Map, Errors, Key, Value0, Constraints) ->\n\tcase cowboy_constraints:validate(Value0, Constraints) of\n\t\t{ok, Value} ->\n\t\t\tfilter(Tail, Map#{Key => Value}, Errors);\n\t\t{error, Reason} ->\n\t\t\tfilter(Tail, Map, Errors#{Key => Reason})\n\tend.\n"
  },
  {
    "path": "src/cowboy_rest.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% Originally based on the Webmachine Diagram from Alan Dean and\n%% Justin Sheehy.\n-module(cowboy_rest).\n-behaviour(cowboy_sub_protocol).\n\n-export([upgrade/4]).\n-export([upgrade/5]).\n\n-type switch_handler() :: {switch_handler, module()}\n\t| {switch_handler, module(), any()}.\n\n%% Common handler callbacks.\n\n-callback init(Req, any())\n\t-> {ok | module(), Req, any()}\n\t| {module(), Req, any(), any()}\n\twhen Req::cowboy_req:req().\n\n-callback terminate(any(), cowboy_req:req(), any()) -> ok.\n-optional_callbacks([terminate/3]).\n\n%% REST handler callbacks.\n\n-callback allowed_methods(Req, State)\n\t-> {[binary()], Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([allowed_methods/2]).\n\n-callback allow_missing_post(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([allow_missing_post/2]).\n\n-callback charsets_provided(Req, State)\n\t-> {[binary()], Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([charsets_provided/2]).\n\n-callback content_types_accepted(Req, State)\n\t-> {[{'*' | binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([content_types_accepted/2]).\n\n-callback content_types_provided(Req, State)\n\t-> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([content_types_provided/2]).\n\n-callback delete_completed(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([delete_completed/2]).\n\n-callback delete_resource(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([delete_resource/2]).\n\n-callback expires(Req, State)\n\t-> {calendar:datetime() | binary() | undefined, Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([expires/2]).\n\n-callback forbidden(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([forbidden/2]).\n\n-callback generate_etag(Req, State)\n\t-> {binary() | {weak | strong, binary()} | undefined, Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([generate_etag/2]).\n\n-callback is_authorized(Req, State)\n\t-> {true | {false, iodata()}, Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([is_authorized/2]).\n\n-callback is_conflict(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([is_conflict/2]).\n\n-callback known_methods(Req, State)\n\t-> {[binary()], Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([known_methods/2]).\n\n-callback languages_provided(Req, State)\n\t-> {[binary()], Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([languages_provided/2]).\n\n-callback last_modified(Req, State)\n\t-> {calendar:datetime() | undefined, Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([last_modified/2]).\n\n-callback malformed_request(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([malformed_request/2]).\n\n-callback moved_permanently(Req, State)\n\t-> {{true, iodata()} | false, Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([moved_permanently/2]).\n\n-callback moved_temporarily(Req, State)\n\t-> {{true, iodata()} | false, Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([moved_temporarily/2]).\n\n-callback multiple_choices(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([multiple_choices/2]).\n\n-callback options(Req, State)\n\t-> {ok, Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([options/2]).\n\n-callback previously_existed(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([previously_existed/2]).\n\n-callback range_satisfiable(Req, State)\n\t-> {boolean() | {false, non_neg_integer() | iodata()}, Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([range_satisfiable/2]).\n\n-callback ranges_provided(Req, State)\n\t-> {[{binary(), atom()}], Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([ranges_provided/2]).\n\n-callback rate_limited(Req, State)\n\t-> {{true, non_neg_integer() | calendar:datetime()} | false, Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([rate_limited/2]).\n\n-callback resource_exists(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([resource_exists/2]).\n\n-callback service_available(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([service_available/2]).\n\n-callback uri_too_long(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([uri_too_long/2]).\n\n-callback valid_content_headers(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([valid_content_headers/2]).\n\n-callback valid_entity_length(Req, State)\n\t-> {boolean(), Req, State}\n\t| {stop, Req, State}\n\t| {switch_handler(), Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([valid_entity_length/2]).\n\n-callback variances(Req, State)\n\t-> {[binary()], Req, State}\n\twhen Req::cowboy_req:req(), State::any().\n-optional_callbacks([variances/2]).\n\n%% End of REST callbacks. Whew!\n\n-record(state, {\n\tmethod = undefined :: binary(),\n\n\t%% Handler.\n\thandler :: atom(),\n\thandler_state :: any(),\n\n\t%% Media type.\n\tcontent_types_p = [] ::\n\t\t[{binary() | {binary(), binary(), [{binary(), binary()}] | '*'},\n\t\t\tatom()}],\n\tcontent_type_a :: undefined\n\t\t| {binary() | {binary(), binary(), [{binary(), binary()}] | '*'},\n\t\t\tatom()},\n\n\t%% Language.\n\tlanguages_p = [] :: [binary()],\n\tlanguage_a :: undefined | binary(),\n\n\t%% Charset.\n\tcharsets_p = undefined :: undefined | [binary()],\n\tcharset_a :: undefined | binary(),\n\n\t%% Range units.\n\tranges_a = [] :: [{binary(), atom()}],\n\n\t%% Whether the resource exists.\n\texists = false :: boolean(),\n\n\t%% Cached resource calls.\n\tetag :: undefined | no_call | {strong | weak, binary()},\n\tlast_modified :: undefined | no_call | calendar:datetime(),\n\texpires :: undefined | no_call | calendar:datetime() | binary()\n}).\n\n-spec upgrade(Req, Env, module(), any())\n\t-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().\nupgrade(Req0, Env, Handler, HandlerState0) ->\n\tMethod = cowboy_req:method(Req0),\n\tcase service_available(Req0, #state{method=Method,\n\t\t\thandler=Handler, handler_state=HandlerState0}) of\n\t\t{ok, Req, Result} ->\n\t\t\t{ok, Req, Env#{result => Result}};\n\t\t{Mod, Req, HandlerState} ->\n\t\t\tMod:upgrade(Req, Env, Handler, HandlerState);\n\t\t{Mod, Req, HandlerState, Opts} ->\n\t\t\tMod:upgrade(Req, Env, Handler, HandlerState, Opts)\n\tend.\n\n-spec upgrade(Req, Env, module(), any(), any())\n\t-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().\n%% cowboy_rest takes no options.\nupgrade(Req, Env, Handler, HandlerState, _Opts) ->\n\tupgrade(Req, Env, Handler, HandlerState).\n\nservice_available(Req, State) ->\n\texpect(Req, State, service_available, true, fun known_methods/2, 503).\n\n%% known_methods/2 should return a list of binary methods.\nknown_methods(Req, State=#state{method=Method}) ->\n\tcase call(Req, State, known_methods) of\n\t\tno_call when Method =:= <<\"HEAD\">>; Method =:= <<\"GET\">>;\n\t\t\t\tMethod =:= <<\"POST\">>; Method =:= <<\"PUT\">>;\n\t\t\t\tMethod =:= <<\"PATCH\">>; Method =:= <<\"DELETE\">>;\n\t\t\t\tMethod =:= <<\"OPTIONS\">> ->\n\t\t\turi_too_long(Req, State);\n\t\tno_call ->\n\t\t\trespond(Req, State, 501);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{List, Req2, State2} ->\n\t\t\tcase lists:member(Method, List) of\n\t\t\t\ttrue -> uri_too_long(Req2, State2);\n\t\t\t\tfalse -> respond(Req2, State2, 501)\n\t\t\tend\n\tend.\n\nuri_too_long(Req, State) ->\n\texpect(Req, State, uri_too_long, false, fun allowed_methods/2, 414).\n\n%% allowed_methods/2 should return a list of binary methods.\nallowed_methods(Req, State=#state{method=Method}) ->\n\tcase call(Req, State, allowed_methods) of\n\t\tno_call when Method =:= <<\"HEAD\">>; Method =:= <<\"GET\">>; Method =:= <<\"OPTIONS\">> ->\n\t\t\tReq2 = cowboy_req:set_resp_header(<<\"allow\">>, <<\"HEAD, GET, OPTIONS\">>, Req),\n\t\t\tmalformed_request(Req2, State);\n\t\tno_call ->\n\t\t\tReq2 = cowboy_req:set_resp_header(<<\"allow\">>, <<\"HEAD, GET, OPTIONS\">>, Req),\n\t\t\trespond(Req2, State, 405);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{List, Req2, State2} ->\n\t\t\tReq3 = cowboy_req:set_resp_header(<<\"allow\">>, cow_http_hd:allow(List), Req2),\n\t\t\tcase lists:member(Method, List) of\n\t\t\t\ttrue ->\n\t\t\t\t\tmalformed_request(Req3, State2);\n\t\t\t\tfalse ->\n\t\t\t\t\trespond(Req3, State2, 405)\n\t\t\tend\n\tend.\n\nmalformed_request(Req, State) ->\n\texpect(Req, State, malformed_request, false, fun is_authorized/2, 400).\n\n%% is_authorized/2 should return true or {false, WwwAuthenticateHeader}.\nis_authorized(Req, State) ->\n\tcase call(Req, State, is_authorized) of\n\t\tno_call ->\n\t\t\tforbidden(Req, State);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{true, Req2, State2} ->\n\t\t\tforbidden(Req2, State2);\n\t\t{{false, AuthHead}, Req2, State2} ->\n\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t<<\"www-authenticate\">>, AuthHead, Req2),\n\t\t\trespond(Req3, State2, 401)\n\tend.\n\nforbidden(Req, State) ->\n\texpect(Req, State, forbidden, false, fun rate_limited/2, 403).\n\nrate_limited(Req, State) ->\n\tcase call(Req, State, rate_limited) of\n\t\tno_call ->\n\t\t\tvalid_content_headers(Req, State);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{false, Req2, State2} ->\n\t\t\tvalid_content_headers(Req2, State2);\n\t\t{{true, RetryAfter0}, Req2, State2} ->\n\t\t\tRetryAfter = if\n\t\t\t\tis_integer(RetryAfter0), RetryAfter0 >= 0 ->\n\t\t\t\t\tinteger_to_binary(RetryAfter0);\n\t\t\t\tis_tuple(RetryAfter0) ->\n\t\t\t\t\tcowboy_clock:rfc1123(RetryAfter0)\n\t\t\tend,\n\t\t\tReq3 = cowboy_req:set_resp_header(<<\"retry-after\">>, RetryAfter, Req2),\n\t\t\trespond(Req3, State2, 429)\n\tend.\n\nvalid_content_headers(Req, State) ->\n\texpect(Req, State, valid_content_headers, true,\n\t\tfun valid_entity_length/2, 501).\n\nvalid_entity_length(Req, State) ->\n\texpect(Req, State, valid_entity_length, true, fun options/2, 413).\n\n%% If you need to add additional headers to the response at this point,\n%% you should do it directly in the options/2 call using set_resp_headers.\noptions(Req, State=#state{method= <<\"OPTIONS\">>}) ->\n\tcase call(Req, State, options) of\n\t\tno_call ->\n\t\t\trespond(Req, State, 200);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{ok, Req2, State2} ->\n\t\t\trespond(Req2, State2, 200)\n\tend;\noptions(Req, State) ->\n\tcontent_types_provided(Req, State).\n\n%% content_types_provided/2 should return a list of content types and their\n%% associated callback function as a tuple: {{Type, SubType, Params}, Fun}.\n%% Type and SubType are the media type as binary. Params is a list of\n%% Key/Value tuple, with Key and Value a binary. Fun is the name of the\n%% callback that will be used to return the content of the response. It is\n%% given as an atom.\n%%\n%% An example of such return value would be:\n%%    {{<<\"text\">>, <<\"html\">>, []}, to_html}\n%%\n%% Note that it is also possible to return a binary content type that will\n%% then be parsed by Cowboy. However note that while this may make your\n%% resources a little more readable, this is a lot less efficient.\n%%\n%% An example of such return value would be:\n%%    {<<\"text/html\">>, to_html}\ncontent_types_provided(Req, State) ->\n\tcase call(Req, State, content_types_provided) of\n\t\tno_call ->\n\t\t\tState2 = State#state{\n\t\t\t\tcontent_types_p=[{{<<\"text\">>, <<\"html\">>, '*'}, to_html}]},\n\t\t\ttry cowboy_req:parse_header(<<\"accept\">>, Req) of\n\t\t\t\tundefined ->\n\t\t\t\t\tlanguages_provided(\n\t\t\t\t\t\tReq#{media_type => {<<\"text\">>, <<\"html\">>, []}},\n\t\t\t\t\t\tState2#state{content_type_a={{<<\"text\">>, <<\"html\">>, []}, to_html}});\n\t\t\t\tAccept ->\n\t\t\t\t\tchoose_media_type(Req, State2, prioritize_accept(Accept))\n\t\t\tcatch _:_ ->\n\t\t\t\trespond(Req, State2, 400)\n\t\t\tend;\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{[], Req2, State2} ->\n\t\t\tnot_acceptable(Req2, State2);\n\t\t{CTP, Req2, State2} ->\n\t\t\tCTP2 = [normalize_content_types(P, provide) || P <- CTP],\n\t\t\tState3 = State2#state{content_types_p=CTP2},\n\t\t\ttry cowboy_req:parse_header(<<\"accept\">>, Req2) of\n\t\t\t\tundefined ->\n\t\t\t\t\t{PMT0, _Fun} = HeadCTP = hd(CTP2),\n\t\t\t\t\t%% We replace the wildcard by an empty list of parameters.\n\t\t\t\t\tPMT = case PMT0 of\n\t\t\t\t\t\t{Type, SubType, '*'} -> {Type, SubType, []};\n\t\t\t\t\t\t_ -> PMT0\n\t\t\t\t\tend,\n\t\t\t\t\tlanguages_provided(\n\t\t\t\t\t\tReq2#{media_type => PMT},\n\t\t\t\t\t\tState3#state{content_type_a=HeadCTP});\n\t\t\t\tAccept ->\n\t\t\t\t\tchoose_media_type(Req2, State3, prioritize_accept(Accept))\n\t\t\tcatch _:_ ->\n\t\t\t\trespond(Req2, State3, 400)\n\t\t\tend\n\tend.\n\nnormalize_content_types({ContentType, Callback}, _)\n\t\twhen is_binary(ContentType) ->\n\t{cow_http_hd:parse_content_type(ContentType), Callback};\nnormalize_content_types(Normalized = {{Type, SubType, _}, _}, _)\n\t\twhen is_binary(Type), is_binary(SubType) ->\n\tNormalized;\n%% Wildcard for content_types_accepted.\nnormalize_content_types(Normalized = {'*', _}, accept) ->\n\tNormalized.\n\nprioritize_accept(Accept) ->\n\tlists:sort(\n\t\tfun ({MediaTypeA, Quality, _AcceptParamsA},\n\t\t\t {MediaTypeB, Quality, _AcceptParamsB}) ->\n\t\t\t\t%% Same quality, check precedence in more details.\n\t\t\t\tprioritize_mediatype(MediaTypeA, MediaTypeB);\n\t\t\t({_MediaTypeA, QualityA, _AcceptParamsA},\n\t\t\t {_MediaTypeB, QualityB, _AcceptParamsB}) ->\n\t\t\t\t%% Just compare the quality.\n\t\t\t\tQualityA > QualityB\n\t\tend, Accept).\n\n%% Media ranges can be overridden by more specific media ranges or\n%% specific media types. If more than one media range applies to a given\n%% type, the most specific reference has precedence.\n%%\n%% We always choose B over A when we can't decide between the two.\nprioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) ->\n\tcase TypeB of\n\t\tTypeA ->\n\t\t\tcase SubTypeB of\n\t\t\t\tSubTypeA -> length(ParamsA) > length(ParamsB);\n\t\t\t\t<<\"*\">> -> true;\n\t\t\t\t_Any -> false\n\t\t\tend;\n\t\t<<\"*\">> -> true;\n\t\t_Any -> false\n\tend.\n\n%% Ignoring the rare AcceptParams. Not sure what should be done about them.\nchoose_media_type(Req, State, []) ->\n\tnot_acceptable(Req, State);\nchoose_media_type(Req, State=#state{content_types_p=CTP},\n\t\t[MediaType|Tail]) ->\n\tmatch_media_type(Req, State, Tail, CTP, MediaType).\n\nmatch_media_type(Req, State, Accept, [], _MediaType) ->\n\tchoose_media_type(Req, State, Accept);\nmatch_media_type(Req, State, Accept, CTP,\n\t\tMediaType = {{<<\"*\">>, <<\"*\">>, _Params_A}, _QA, _APA}) ->\n\tmatch_media_type_params(Req, State, Accept, CTP, MediaType);\nmatch_media_type(Req, State, Accept,\n\t\t\tCTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail],\n\t\t\tMediaType = {{Type, SubType_A, _PA}, _QA, _APA})\n\t\twhen SubType_P =:= SubType_A; SubType_A =:= <<\"*\">> ->\n\tmatch_media_type_params(Req, State, Accept, CTP, MediaType);\nmatch_media_type(Req, State, Accept, [_Any|Tail], MediaType) ->\n\tmatch_media_type(Req, State, Accept, Tail, MediaType).\n\nmatch_media_type_params(Req, State, Accept,\n\t\t[Provided = {{TP, STP, '*'}, _Fun}|Tail],\n\t\tMediaType = {{TA, _STA, Params_A0}, _QA, _APA}) ->\n\tcase lists:keytake(<<\"charset\">>, 1, Params_A0) of\n\t\t{value, {_, Charset}, Params_A} when TA =:= <<\"text\">> ->\n\t\t\t%% When we match against a wildcard, the media type is text\n\t\t\t%% and has a charset parameter, we call charsets_provided\n\t\t\t%% and check that the charset is provided. If the callback\n\t\t\t%% is not exported, we accept inconditionally but ignore\n\t\t\t%% the given charset so as to not send a wrong value back.\n\t\t\tcase call(Req, State, charsets_provided) of\n\t\t\t\tno_call ->\n\t\t\t\t\tlanguages_provided(Req#{media_type => {TP, STP, Params_A0}},\n\t\t\t\t\t\tState#state{content_type_a=Provided});\n\t\t\t\t{stop, Req2, State2} ->\n\t\t\t\t\tterminate(Req2, State2);\n\t\t\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t\t\t{CP, Req2, State2} ->\n\t\t\t\t\tState3 = State2#state{charsets_p=CP},\n\t\t\t\t\tcase lists:member(Charset, CP) of\n\t\t\t\t\t\tfalse ->\n\t\t\t\t\t\t\tmatch_media_type(Req2, State3, Accept, Tail, MediaType);\n\t\t\t\t\t\ttrue ->\n\t\t\t\t\t\t\tlanguages_provided(Req2#{media_type => {TP, STP, Params_A}},\n\t\t\t\t\t\t\t\tState3#state{content_type_a=Provided,\n\t\t\t\t\t\t\t\t\tcharset_a=Charset})\n\t\t\t\t\tend\n\t\t\tend;\n\t\t_ ->\n\t\t\tlanguages_provided(Req#{media_type => {TP, STP, Params_A0}},\n\t\t\t\tState#state{content_type_a=Provided})\n\tend;\nmatch_media_type_params(Req, State, Accept,\n\t\t[Provided = {PMT = {TP, STP, Params_P0}, Fun}|Tail],\n\t\tMediaType = {{_TA, _STA, Params_A}, _QA, _APA}) ->\n\tcase lists:sort(Params_P0) =:= lists:sort(Params_A) of\n\t\ttrue when TP =:= <<\"text\">> ->\n\t\t\t%% When a charset was provided explicitly in both the charset header\n\t\t\t%% and the media types provided and the negotiation is successful,\n\t\t\t%% we keep the charset and don't call charsets_provided. This only\n\t\t\t%% applies to text media types, however.\n\t\t\t{Charset, Params_P} = case lists:keytake(<<\"charset\">>, 1, Params_P0) of\n\t\t\t\tfalse -> {undefined, Params_P0};\n\t\t\t\t{value, {_, Charset0}, Params_P1} -> {Charset0, Params_P1}\n\t\t\tend,\n\t\t\tlanguages_provided(Req#{media_type => {TP, STP, Params_P}},\n\t\t\t\tState#state{content_type_a={{TP, STP, Params_P}, Fun},\n\t\t\t\t\tcharset_a=Charset});\n\t\ttrue ->\n\t\t\tlanguages_provided(Req#{media_type => PMT},\n\t\t\t\tState#state{content_type_a=Provided});\n\t\tfalse ->\n\t\t\tmatch_media_type(Req, State, Accept, Tail, MediaType)\n\tend.\n\n%% languages_provided should return a list of binary values indicating\n%% which languages are accepted by the resource.\n%%\n%% @todo I suppose we should also ask the resource if it wants to\n%% set a language itself or if it wants it to be automatically chosen.\nlanguages_provided(Req, State) ->\n\tcase call(Req, State, languages_provided) of\n\t\tno_call ->\n\t\t\tcharsets_provided(Req, State);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{[], Req2, State2} ->\n\t\t\tnot_acceptable(Req2, State2);\n\t\t{LP, Req2, State2} ->\n\t\t\tState3 = State2#state{languages_p=LP},\n\t\t\tcase cowboy_req:parse_header(<<\"accept-language\">>, Req2) of\n\t\t\t\tundefined ->\n\t\t\t\t\tset_language(Req2, State3#state{language_a=hd(LP)});\n\t\t\t\tAcceptLanguage ->\n\t\t\t\t\tAcceptLanguage2 = prioritize_languages(AcceptLanguage),\n\t\t\t\t\tchoose_language(Req2, State3, AcceptLanguage2)\n\t\t\tend\n\tend.\n\n%% A language-range matches a language-tag if it exactly equals the tag,\n%% or if it exactly equals a prefix of the tag such that the first tag\n%% character following the prefix is \"-\". The special range \"*\", if\n%% present in the Accept-Language field, matches every tag not matched\n%% by any other range present in the Accept-Language field.\n%%\n%% @todo The last sentence probably means we should always put '*'\n%% at the end of the list.\nprioritize_languages(AcceptLanguages) ->\n\tlists:sort(\n\t\tfun ({_TagA, QualityA}, {_TagB, QualityB}) ->\n\t\t\tQualityA > QualityB\n\t\tend, AcceptLanguages).\n\nchoose_language(Req, State, []) ->\n\tnot_acceptable(Req, State);\nchoose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) ->\n\tmatch_language(Req, State, Tail, LP, Language).\n\nmatch_language(Req, State, Accept, [], _Language) ->\n\tchoose_language(Req, State, Accept);\nmatch_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) ->\n\tset_language(Req, State#state{language_a=Provided});\nmatch_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) ->\n\tset_language(Req, State#state{language_a=Provided});\nmatch_language(Req, State, Accept, [Provided|Tail],\n\t\tLanguage = {Tag, _Quality}) ->\n\tLength = byte_size(Tag),\n\tcase Provided of\n\t\t<< Tag:Length/binary, $-, _Any/bits >> ->\n\t\t\tset_language(Req, State#state{language_a=Provided});\n\t\t_Any ->\n\t\t\tmatch_language(Req, State, Accept, Tail, Language)\n\tend.\n\nset_language(Req, State=#state{language_a=Language}) ->\n\tReq2 = cowboy_req:set_resp_header(<<\"content-language\">>, Language, Req),\n\tcharsets_provided(Req2#{language => Language}, State).\n\n%% charsets_provided should return a list of binary values indicating\n%% which charsets are accepted by the resource.\n%%\n%% A charset may have been selected while negotiating the accept header.\n%% There's no need to select one again.\ncharsets_provided(Req, State=#state{charset_a=Charset})\n\t\twhen Charset =/= undefined ->\n\tset_content_type(Req, State);\n%% If charsets_p is defined, use it instead of calling charsets_provided\n%% again. We also call this clause during normal execution to avoid\n%% duplicating code.\ncharsets_provided(Req, State=#state{charsets_p=[]}) ->\n\tnot_acceptable(Req, State);\ncharsets_provided(Req, State=#state{charsets_p=CP})\n\t\twhen CP =/= undefined ->\n\tcase cowboy_req:parse_header(<<\"accept-charset\">>, Req) of\n\t\tundefined ->\n\t\t\tset_content_type(Req, State#state{charset_a=hd(CP)});\n\t\tAcceptCharset0 ->\n\t\t\tAcceptCharset = prioritize_charsets(AcceptCharset0),\n\t\t\tchoose_charset(Req, State, AcceptCharset)\n\tend;\ncharsets_provided(Req, State) ->\n\tcase call(Req, State, charsets_provided) of\n\t\tno_call ->\n\t\t\tset_content_type(Req, State);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{CP, Req2, State2} ->\n\t\t\tcharsets_provided(Req2, State2#state{charsets_p=CP})\n\tend.\n\nprioritize_charsets(AcceptCharsets) ->\n\tlists:sort(\n\t\tfun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) ->\n\t\t\tQualityA > QualityB\n\t\tend, AcceptCharsets).\n\nchoose_charset(Req, State, []) ->\n\tnot_acceptable(Req, State);\n%% A q-value of 0 means not acceptable.\nchoose_charset(Req, State, [{_, 0}|Tail]) ->\n\tchoose_charset(Req, State, Tail);\nchoose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) ->\n\tmatch_charset(Req, State, Tail, CP, Charset).\n\nmatch_charset(Req, State, Accept, [], _Charset) ->\n\tchoose_charset(Req, State, Accept);\nmatch_charset(Req, State, _Accept, [Provided|_], {<<\"*\">>, _}) ->\n\tset_content_type(Req, State#state{charset_a=Provided});\nmatch_charset(Req, State, _Accept, [Provided|_], {Provided, _}) ->\n\tset_content_type(Req, State#state{charset_a=Provided});\nmatch_charset(Req, State, Accept, [_|Tail], Charset) ->\n\tmatch_charset(Req, State, Accept, Tail, Charset).\n\nset_content_type(Req, State=#state{\n\t\tcontent_type_a={{Type, SubType, Params}, _Fun},\n\t\tcharset_a=Charset}) ->\n\tParamsBin = set_content_type_build_params(Params, []),\n\tContentType = [Type, <<\"/\">>, SubType, ParamsBin],\n\tContentType2 = case {Type, Charset} of\n\t\t{<<\"text\">>, Charset} when Charset =/= undefined ->\n\t\t\t[ContentType, <<\"; charset=\">>, Charset];\n\t\t_ ->\n\t\t\tContentType\n\tend,\n\tReq2 = cowboy_req:set_resp_header(<<\"content-type\">>, ContentType2, Req),\n\tencodings_provided(Req2#{charset => Charset}, State).\n\nset_content_type_build_params('*', []) ->\n\t<<>>;\nset_content_type_build_params([], []) ->\n\t<<>>;\nset_content_type_build_params([], Acc) ->\n\tlists:reverse(Acc);\nset_content_type_build_params([{Attr, Value}|Tail], Acc) ->\n\tset_content_type_build_params(Tail, [[Attr, <<\"=\">>, Value], <<\";\">>|Acc]).\n\n%% @todo Match for identity as we provide nothing else for now.\n%% @todo Don't forget to set the Content-Encoding header when we reply a body\n%% and the found encoding is something other than identity.\nencodings_provided(Req, State) ->\n\tranges_provided(Req, State).\n\nnot_acceptable(Req, State) ->\n\trespond(Req, State, 406).\n\nranges_provided(Req, State) ->\n\tcase call(Req, State, ranges_provided) of\n\t\tno_call ->\n\t\t\tvariances(Req, State);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{[], Req2, State2} ->\n\t\t\tReq3 = cowboy_req:set_resp_header(<<\"accept-ranges\">>, <<\"none\">>, Req2),\n\t\t\tvariances(Req3, State2#state{ranges_a=[]});\n\t\t{RP, Req2, State2} ->\n\t\t\t<<\", \", AcceptRanges/binary>> = <<<<\", \", R/binary>> || {R, _} <- RP>>,\n\t\t\tReq3 = cowboy_req:set_resp_header(<<\"accept-ranges\">>, AcceptRanges, Req2),\n\t\t\tvariances(Req3, State2#state{ranges_a=RP})\n\tend.\n\n%% variances/2 should return a list of headers that will be added\n%% to the Vary response header. The Accept, Accept-Language,\n%% Accept-Charset and Accept-Encoding headers do not need to be\n%% specified.\n%%\n%% @todo Do Accept-Encoding too when we handle it.\n%% @todo Does the order matter?\nvariances(Req, State=#state{content_types_p=CTP,\n\t\tlanguages_p=LP, charsets_p=CP}) ->\n\tVariances = case CTP of\n\t\t[] -> [];\n\t\t[_] -> [];\n\t\t[_|_] -> [<<\"accept\">>]\n\tend,\n\tVariances2 = case LP of\n\t\t[] -> Variances;\n\t\t[_] -> Variances;\n\t\t[_|_] -> [<<\"accept-language\">>|Variances]\n\tend,\n\tVariances3 = case CP of\n\t\tundefined -> Variances2;\n\t\t[] -> Variances2;\n\t\t[_] -> Variances2;\n\t\t[_|_] -> [<<\"accept-charset\">>|Variances2]\n\tend,\n\ttry variances(Req, State, Variances3) of\n\t\t{Variances4, Req2, State2} ->\n\t\t\tcase [[<<\", \">>, V] || V <- Variances4] of\n\t\t\t\t[] ->\n\t\t\t\t\tresource_exists(Req2, State2);\n\t\t\t\t[[<<\", \">>, H]|Variances5] ->\n\t\t\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t\t\t<<\"vary\">>, [H|Variances5], Req2),\n\t\t\t\t\tresource_exists(Req3, State2)\n\t\t\tend\n\tcatch Class:Reason:Stacktrace ->\n\t\terror_terminate(Req, State, Class, Reason, Stacktrace)\n\tend.\n\nvariances(Req, State, Variances) ->\n\tcase unsafe_call(Req, State, variances) of\n\t\tno_call ->\n\t\t\t{Variances, Req, State};\n\t\t{HandlerVariances, Req2, State2} ->\n\t\t\t{Variances ++ HandlerVariances, Req2, State2}\n\tend.\n\nresource_exists(Req, State) ->\n\texpect(Req, State, resource_exists, true,\n\t\tfun if_match_exists/2, fun if_match_must_not_exist/2).\n\nif_match_exists(Req, State) ->\n\tState2 = State#state{exists=true},\n\tcase cowboy_req:parse_header(<<\"if-match\">>, Req) of\n\t\tundefined ->\n\t\t\tif_unmodified_since_exists(Req, State2);\n\t\t'*' ->\n\t\t\tif_unmodified_since_exists(Req, State2);\n\t\tETagsList ->\n\t\t\tif_match(Req, State2, ETagsList)\n\tend.\n\nif_match(Req, State, EtagsList) ->\n\ttry generate_etag(Req, State) of\n\t\t%% Strong Etag comparison: weak Etag never matches.\n\t\t{{weak, _}, Req2, State2} ->\n\t\t\tprecondition_failed(Req2, State2);\n\t\t{Etag, Req2, State2} ->\n\t\t\tcase lists:member(Etag, EtagsList) of\n\t\t\t\ttrue -> if_none_match_exists(Req2, State2);\n\t\t\t\t%% Etag may be `undefined' which cannot be a member.\n\t\t\t\tfalse -> precondition_failed(Req2, State2)\n\t\t\tend\n\tcatch Class:Reason:Stacktrace ->\n\t\terror_terminate(Req, State, Class, Reason, Stacktrace)\n\tend.\n\nif_match_must_not_exist(Req, State) ->\n\tcase cowboy_req:header(<<\"if-match\">>, Req) of\n\t\tundefined -> is_put_to_missing_resource(Req, State);\n\t\t_ -> precondition_failed(Req, State)\n\tend.\n\nif_unmodified_since_exists(Req, State) ->\n\ttry cowboy_req:parse_header(<<\"if-unmodified-since\">>, Req) of\n\t\tundefined ->\n\t\t\tif_none_match_exists(Req, State);\n\t\tIfUnmodifiedSince ->\n\t\t\tif_unmodified_since(Req, State, IfUnmodifiedSince)\n\tcatch _:_ ->\n\t\tif_none_match_exists(Req, State)\n\tend.\n\n%% If LastModified is the atom 'no_call', we continue.\nif_unmodified_since(Req, State, IfUnmodifiedSince) ->\n\ttry last_modified(Req, State) of\n\t\t{LastModified, Req2, State2} ->\n\t\t\tcase LastModified > IfUnmodifiedSince of\n\t\t\t\ttrue -> precondition_failed(Req2, State2);\n\t\t\t\tfalse -> if_none_match_exists(Req2, State2)\n\t\t\tend\n\tcatch Class:Reason:Stacktrace ->\n\t\terror_terminate(Req, State, Class, Reason, Stacktrace)\n\tend.\n\nif_none_match_exists(Req, State) ->\n\tcase cowboy_req:parse_header(<<\"if-none-match\">>, Req) of\n\t\tundefined ->\n\t\t\tif_modified_since_exists(Req, State);\n\t\t'*' ->\n\t\t\tprecondition_is_head_get(Req, State);\n\t\tEtagsList ->\n\t\t\tif_none_match(Req, State, EtagsList)\n\tend.\n\nif_none_match(Req, State, EtagsList) ->\n\ttry generate_etag(Req, State) of\n\t\t{Etag, Req2, State2} ->\n\t\t\tcase Etag of\n\t\t\t\tundefined ->\n\t\t\t\t\tprecondition_failed(Req2, State2);\n\t\t\t\tEtag ->\n\t\t\t\t\tcase is_weak_match(Etag, EtagsList) of\n\t\t\t\t\t\ttrue -> precondition_is_head_get(Req2, State2);\n\t\t\t\t\t\tfalse -> method(Req2, State2)\n\t\t\t\t\tend\n\t\t\tend\n\tcatch Class:Reason:Stacktrace ->\n\t\terror_terminate(Req, State, Class, Reason, Stacktrace)\n\tend.\n\n%% Weak Etag comparison: only check the opaque tag.\nis_weak_match(_, []) ->\n\tfalse;\nis_weak_match({_, Tag}, [{_, Tag}|_]) ->\n\ttrue;\nis_weak_match(Etag, [_|Tail]) ->\n\tis_weak_match(Etag, Tail).\n\nprecondition_is_head_get(Req, State=#state{method=Method})\n\t\twhen Method =:= <<\"HEAD\">>; Method =:= <<\"GET\">> ->\n\tnot_modified(Req, State);\nprecondition_is_head_get(Req, State) ->\n\tprecondition_failed(Req, State).\n\nif_modified_since_exists(Req, State) ->\n\ttry cowboy_req:parse_header(<<\"if-modified-since\">>, Req) of\n\t\tundefined ->\n\t\t\tmethod(Req, State);\n\t\tIfModifiedSince ->\n\t\t\tif_modified_since_now(Req, State, IfModifiedSince)\n\tcatch _:_ ->\n\t\tmethod(Req, State)\n\tend.\n\nif_modified_since_now(Req, State, IfModifiedSince) ->\n\tcase IfModifiedSince > erlang:universaltime() of\n\t\ttrue -> method(Req, State);\n\t\tfalse -> if_modified_since(Req, State, IfModifiedSince)\n\tend.\n\nif_modified_since(Req, State, IfModifiedSince) ->\n\ttry last_modified(Req, State) of\n\t\t{undefined, Req2, State2} ->\n\t\t\tmethod(Req2, State2);\n\t\t{LastModified, Req2, State2} ->\n\t\t\tcase LastModified > IfModifiedSince of\n\t\t\t\ttrue -> method(Req2, State2);\n\t\t\t\tfalse -> not_modified(Req2, State2)\n\t\t\tend\n\tcatch Class:Reason:Stacktrace ->\n\t\terror_terminate(Req, State, Class, Reason, Stacktrace)\n\tend.\n\nnot_modified(Req, State) ->\n\tReq2 = cowboy_req:delete_resp_header(<<\"content-type\">>, Req),\n\ttry set_resp_etag(Req2, State) of\n\t\t{Req3, State2} ->\n\t\t\ttry set_resp_expires(Req3, State2) of\n\t\t\t\t{Req4, State3} ->\n\t\t\t\t\trespond(Req4, State3, 304)\n\t\t\tcatch Class:Reason:Stacktrace ->\n\t\t\t\terror_terminate(Req, State2, Class, Reason, Stacktrace)\n\t\t\tend\n\tcatch Class:Reason:Stacktrace ->\n\t\terror_terminate(Req, State, Class, Reason, Stacktrace)\n\tend.\n\nprecondition_failed(Req, State) ->\n\trespond(Req, State, 412).\n\nis_put_to_missing_resource(Req, State=#state{method= <<\"PUT\">>}) ->\n\tmoved_permanently(Req, State, fun is_conflict/2);\nis_put_to_missing_resource(Req, State) ->\n\tpreviously_existed(Req, State).\n\n%% moved_permanently/2 should return either false or {true, Location}\n%% with Location the full new URI of the resource.\nmoved_permanently(Req, State, OnFalse) ->\n\tcase call(Req, State, moved_permanently) of\n\t\t{{true, Location}, Req2, State2} ->\n\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t<<\"location\">>, Location, Req2),\n\t\t\trespond(Req3, State2, 301);\n\t\t{false, Req2, State2} ->\n\t\t\tOnFalse(Req2, State2);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\tno_call ->\n\t\t\tOnFalse(Req, State)\n\tend.\n\npreviously_existed(Req, State) ->\n\texpect(Req, State, previously_existed, false,\n\t\tfun (R, S) -> is_post_to_missing_resource(R, S, 404) end,\n\t\tfun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end).\n\n%% moved_temporarily/2 should return either false or {true, Location}\n%% with Location the full new URI of the resource.\nmoved_temporarily(Req, State) ->\n\tcase call(Req, State, moved_temporarily) of\n\t\t{{true, Location}, Req2, State2} ->\n\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t<<\"location\">>, Location, Req2),\n\t\t\trespond(Req3, State2, 307);\n\t\t{false, Req2, State2} ->\n\t\t\tis_post_to_missing_resource(Req2, State2, 410);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\tno_call ->\n\t\t\tis_post_to_missing_resource(Req, State, 410)\n\tend.\n\nis_post_to_missing_resource(Req, State=#state{method= <<\"POST\">>}, OnFalse) ->\n\tallow_missing_post(Req, State, OnFalse);\nis_post_to_missing_resource(Req, State, OnFalse) ->\n\trespond(Req, State, OnFalse).\n\nallow_missing_post(Req, State, OnFalse) ->\n\texpect(Req, State, allow_missing_post, true, fun accept_resource/2, OnFalse).\n\nmethod(Req, State=#state{method= <<\"DELETE\">>}) ->\n\tdelete_resource(Req, State);\nmethod(Req, State=#state{method= <<\"PUT\">>}) ->\n\tis_conflict(Req, State);\nmethod(Req, State=#state{method=Method})\n\t\twhen Method =:= <<\"POST\">>; Method =:= <<\"PATCH\">> ->\n\taccept_resource(Req, State);\nmethod(Req, State=#state{method=Method})\n\t\twhen Method =:= <<\"GET\">>; Method =:= <<\"HEAD\">> ->\n\tset_resp_body_etag(Req, State);\nmethod(Req, State) ->\n\tmultiple_choices(Req, State).\n\n%% delete_resource/2 should start deleting the resource and return.\ndelete_resource(Req, State) ->\n\texpect(Req, State, delete_resource, false, 500, fun delete_completed/2).\n\n%% delete_completed/2 indicates whether the resource has been deleted yet.\ndelete_completed(Req, State) ->\n\texpect(Req, State, delete_completed, true, fun has_resp_body/2, 202).\n\nis_conflict(Req, State) ->\n\texpect(Req, State, is_conflict, false, fun accept_resource/2, 409).\n\n%% content_types_accepted should return a list of media types and their\n%% associated callback functions in the same format as content_types_provided.\n%%\n%% The callback will then be called and is expected to process the content\n%% pushed to the resource in the request body.\n%%\n%% content_types_accepted SHOULD return a different list\n%% for each HTTP method.\naccept_resource(Req, State) ->\n\tcase call(Req, State, content_types_accepted) of\n\t\tno_call ->\n\t\t\trespond(Req, State, 415);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{CTA, Req2, State2} ->\n\t\t\tCTA2 = [normalize_content_types(P, accept) || P <- CTA],\n\t\t\ttry cowboy_req:parse_header(<<\"content-type\">>, Req2) of\n\t\t\t\t%% We do not match against the boundary parameter for multipart.\n\t\t\t\t{Type = <<\"multipart\">>, SubType, Params} ->\n\t\t\t\t\tContentType = {Type, SubType, lists:keydelete(<<\"boundary\">>, 1, Params)},\n\t\t\t\t\tchoose_content_type(Req2, State2, ContentType, CTA2);\n\t\t\t\tContentType ->\n\t\t\t\t\tchoose_content_type(Req2, State2, ContentType, CTA2)\n\t\t\tcatch _:_ ->\n\t\t\t\trespond(Req2, State2, 415)\n\t\t\tend\n\tend.\n\n%% The special content type '*' will always match. It can be used as a\n%% catch-all content type for accepting any kind of request content.\n%% Note that because it will always match, it should be the last of the\n%% list of content types, otherwise it'll shadow the ones following.\nchoose_content_type(Req, State, _ContentType, []) ->\n\trespond(Req, State, 415);\nchoose_content_type(Req, State, ContentType, [{Accepted, Fun}|_Tail])\n\t\twhen Accepted =:= '*'; Accepted =:= ContentType ->\n\tprocess_content_type(Req, State, Fun);\n%% The special parameter '*' will always match any kind of content type\n%% parameters.\n%% Note that because it will always match, it should be the last of the\n%% list for specific content type, otherwise it'll shadow the ones following.\nchoose_content_type(Req, State, {Type, SubType, Param},\n\t\t[{{Type, SubType, AcceptedParam}, Fun}|_Tail])\n\t\twhen AcceptedParam =:= '*'; AcceptedParam =:= Param ->\n\tprocess_content_type(Req, State, Fun);\nchoose_content_type(Req, State, ContentType, [_Any|Tail]) ->\n\tchoose_content_type(Req, State, ContentType, Tail).\n\nprocess_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) ->\n\ttry case call(Req, State, Fun) of\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{true, Req2, State2} when Exists ->\n\t\t\thas_resp_body(Req2, State2);\n\t\t{true, Req2, State2} ->\n\t\t\tmaybe_created(Req2, State2);\n\t\t{false, Req2, State2} ->\n\t\t\trespond(Req2, State2, 400);\n\t\t{{created, ResURL}, Req2, State2} when Method =:= <<\"POST\">> ->\n\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t<<\"location\">>, ResURL, Req2),\n\t\t\trespond(Req3, State2, 201);\n\t\t{{see_other, ResURL}, Req2, State2} when Method =:= <<\"POST\">> ->\n\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t<<\"location\">>, ResURL, Req2),\n\t\t\trespond(Req3, State2, 303);\n\t\t{{true, ResURL}, Req2, State2} when Method =:= <<\"POST\">> ->\n\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t<<\"location\">>, ResURL, Req2),\n\t\t\tif\n\t\t\t\tExists -> respond(Req3, State2, 303);\n\t\t\t\ttrue -> respond(Req3, State2, 201)\n\t\t\tend\n\tend catch Class:Reason = {case_clause, no_call}:Stacktrace ->\n\t\terror_terminate(Req, State, Class, Reason, Stacktrace)\n\tend.\n\n%% If PUT was used then the resource has been created at the current URL.\n%% Otherwise, if a location header has been set then the resource has been\n%% created at a new URL. If not, send a 200 or 204 as expected from a\n%% POST or PATCH request.\nmaybe_created(Req, State=#state{method= <<\"PUT\">>}) ->\n\trespond(Req, State, 201);\nmaybe_created(Req, State) ->\n\tcase cowboy_req:has_resp_header(<<\"location\">>, Req) of\n\t\ttrue -> respond(Req, State, 201);\n\t\tfalse -> has_resp_body(Req, State)\n\tend.\n\nhas_resp_body(Req, State) ->\n\tcase cowboy_req:has_resp_body(Req) of\n\t\ttrue -> multiple_choices(Req, State);\n\t\tfalse -> respond(Req, State, 204)\n\tend.\n\n%% Set the Etag header if any for the response provided.\nset_resp_body_etag(Req, State) ->\n\ttry set_resp_etag(Req, State) of\n\t\t{Req2, State2} ->\n\t\t\tset_resp_body_last_modified(Req2, State2)\n\tcatch Class:Reason:Stacktrace ->\n\t\terror_terminate(Req, State, Class, Reason, Stacktrace)\n\tend.\n\n%% Set the Last-Modified header if any for the response provided.\nset_resp_body_last_modified(Req, State) ->\n\ttry last_modified(Req, State) of\n\t\t{LastModified, Req2, State2} ->\n\t\t\tcase LastModified of\n\t\t\t\tLastModified when is_atom(LastModified) ->\n\t\t\t\t\tset_resp_body_expires(Req2, State2);\n\t\t\t\tLastModified ->\n\t\t\t\t\tLastModifiedBin = cowboy_clock:rfc1123(LastModified),\n\t\t\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t\t\t<<\"last-modified\">>, LastModifiedBin, Req2),\n\t\t\t\t\tset_resp_body_expires(Req3, State2)\n\t\t\tend\n\tcatch Class:Reason:Stacktrace ->\n\t\terror_terminate(Req, State, Class, Reason, Stacktrace)\n\tend.\n\n%% Set the Expires header if any for the response provided.\nset_resp_body_expires(Req, State) ->\n\ttry set_resp_expires(Req, State) of\n\t\t{Req2, State2} ->\n\t\t\tif_range(Req2, State2)\n\tcatch Class:Reason:Stacktrace ->\n\t\terror_terminate(Req, State, Class, Reason, Stacktrace)\n\tend.\n\n%% When both the if-range and range headers are set, we perform\n%% a strong comparison. If it fails, we send a full response.\nif_range(Req=#{headers := #{<<\"if-range\">> := _, <<\"range\">> := _}},\n\t\tState=#state{etag=Etag}) ->\n\ttry cowboy_req:parse_header(<<\"if-range\">>, Req) of\n\t\t%% Strong etag comparison is an exact match with the generate_etag result.\n\t\tEtag={strong, _} ->\n\t\t\trange(Req, State);\n\t\t%% We cannot do a strong date comparison because we have\n\t\t%% no way of knowing whether the representation changed\n\t\t%% twice during the second covered by the presented\n\t\t%% validator. (RFC7232 2.2.2)\n\t\t_ ->\n\t\t\tset_resp_body(Req, State)\n\tcatch _:_ ->\n\t\tset_resp_body(Req, State)\n\tend;\nif_range(Req, State) ->\n\trange(Req, State).\n\n%% @todo This can probably be moved to if_range directly.\nrange(Req, State=#state{ranges_a=[]}) ->\n\tset_resp_body(Req, State);\nrange(Req, State) ->\n\ttry cowboy_req:parse_header(<<\"range\">>, Req) of\n\t\tundefined ->\n\t\t\tset_resp_body(Req, State);\n\t\t%% @todo Maybe change parse_header to return <<\"bytes\">> in 3.0.\n\t\t{bytes, BytesRange} ->\n\t\t\tchoose_range(Req, State, {<<\"bytes\">>, BytesRange});\n\t\tRange ->\n\t\t\tchoose_range(Req, State, Range)\n\tcatch _:_ ->\n\t\t%% We send a 416 response back when we can't parse the\n\t\t%% range header at all. I'm not sure this is the right\n\t\t%% way to go but at least this can help clients identify\n\t\t%% what went wrong when their range requests never work.\n\t\trange_not_satisfiable(Req, State, undefined)\n\tend.\n\nchoose_range(Req, State=#state{ranges_a=RangesAccepted}, Range={RangeUnit, _}) ->\n\tcase lists:keyfind(RangeUnit, 1, RangesAccepted) of\n\t\t{_, Callback} ->\n\t\t\t%% We pass the selected range onward in the Req.\n\t\t\trange_satisfiable(Req#{range => Range}, State, Callback);\n\t\tfalse ->\n\t\t\tset_resp_body(Req, State)\n\tend.\n\nrange_satisfiable(Req, State, Callback) ->\n\tcase call(Req, State, range_satisfiable) of\n\t\tno_call ->\n\t\t\tset_ranged_body(Req, State, Callback);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{true, Req2, State2} ->\n\t\t\tset_ranged_body(Req2, State2, Callback);\n\t\t{false, Req2, State2} ->\n\t\t\trange_not_satisfiable(Req2, State2, undefined);\n\t\t{{false, Int}, Req2, State2} when is_integer(Int) ->\n\t\t\trange_not_satisfiable(Req2, State2, [<<\"*/\">>, integer_to_binary(Int)]);\n\t\t{{false, Iodata}, Req2, State2} when is_binary(Iodata); is_list(Iodata) ->\n\t\t\trange_not_satisfiable(Req2, State2, Iodata)\n\tend.\n\n%% When the callback selected is 'auto' and the range unit\n%% is bytes, we call the normal provide callback and split\n%% the content automatically.\nset_ranged_body(Req=#{range := {<<\"bytes\">>, _}}, State, auto) ->\n\tset_ranged_body_auto(Req, State);\nset_ranged_body(Req, State, Callback) ->\n\tset_ranged_body_callback(Req, State, Callback).\n\nset_ranged_body_auto(Req, State=#state{handler=Handler, content_type_a={_, Callback}}) ->\n\ttry case call(Req, State, Callback) of\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{Body, Req2, State2} ->\n\t\t\tmaybe_set_ranged_body_auto(Req2, State2, Body)\n\tend catch Class:{case_clause, no_call}:Stacktrace ->\n\t\terror_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},\n\t\t\t'A callback specified in content_types_provided/2 is not exported.'},\n\t\t\tStacktrace)\n\tend.\n\nmaybe_set_ranged_body_auto(Req=#{range := {_, Ranges}}, State, Body) ->\n\tSize = case Body of\n\t\t{sendfile, _, Bytes, _} -> Bytes;\n\t\t_ -> iolist_size(Body)\n\tend,\n\tChecks = [case Range of\n\t\t{From, infinity} -> From < Size;\n\t\t{From, To} -> (From < Size) andalso (From =< To) andalso (To =< Size);\n\t\tNeg -> (Neg =/= 0) andalso (-Neg < Size)\n\tend || Range <- Ranges],\n\tcase lists:usort(Checks) of\n\t\t[true] -> set_ranged_body_auto(Req, State, Body);\n\t\t_ -> range_not_satisfiable(Req, State, [<<\"*/\">>, integer_to_binary(Size)])\n\tend.\n\n%% We might also want to have some checks about range order,\n%% number of ranges, and perhaps also join ranges that are\n%% too close into one contiguous range. Some of these can\n%% be done before calling the ProvideCallback.\n\nset_ranged_body_auto(Req=#{range := {_, Ranges}}, State, Body) ->\n\tParts = [ranged_partition(Range, Body) || Range <- Ranges],\n\tcase Parts of\n\t\t[OnePart] -> set_one_ranged_body(Req, State, OnePart);\n\t\t_ when is_tuple(Body) -> send_multipart_ranged_body(Req, State, Parts);\n\t\t_ -> set_multipart_ranged_body(Req, State, Parts)\n\tend.\n\nranged_partition(Range, {sendfile, Offset0, Bytes0, Path}) ->\n\t{From, To, Offset, Bytes} = case Range of\n\t\t{From0, infinity} -> {From0, Bytes0 - 1, Offset0 + From0, Bytes0 - From0};\n\t\t{From0, To0} -> {From0, To0, Offset0 + From0, 1 + To0 - From0};\n\t\tNeg -> {Bytes0 + Neg, Bytes0 - 1, Offset0 + Bytes0 + Neg, -Neg}\n\tend,\n\t{{From, To, Bytes0}, {sendfile, Offset, Bytes, Path}};\nranged_partition(Range, Data0) ->\n\tTotal = iolist_size(Data0),\n\t{From, To, Data} = case Range of\n\t\t{From0, infinity} ->\n\t\t\t{_, Data1} = cow_iolists:split(From0, Data0),\n\t\t\t{From0, Total - 1, Data1};\n\t\t{From0, To0} ->\n\t\t\t{_, Data1} = cow_iolists:split(From0, Data0),\n\t\t\t{Data2, _} = cow_iolists:split(To0 - From0 + 1, Data1),\n\t\t\t{From0, To0, Data2};\n\t\tNeg ->\n\t\t\t{_, Data1} = cow_iolists:split(Total + Neg, Data0),\n\t\t\t{Total + Neg, Total - 1, Data1}\n\tend,\n\t{{From, To, Total}, Data}.\n\n-ifdef(TEST).\nranged_partition_test_() ->\n\tTests = [\n\t\t%% Sendfile with open-ended range.\n\t\t{{0, infinity}, {sendfile, 0, 12, \"t\"}, {{0, 11, 12}, {sendfile, 0, 12, \"t\"}}},\n\t\t{{6, infinity}, {sendfile, 0, 12, \"t\"}, {{6, 11, 12}, {sendfile, 6, 6, \"t\"}}},\n\t\t{{11, infinity}, {sendfile, 0, 12, \"t\"}, {{11, 11, 12}, {sendfile, 11, 1, \"t\"}}},\n\t\t%% Sendfile with open-ended range. Sendfile tuple has an offset originally.\n\t\t{{0, infinity}, {sendfile, 3, 12, \"t\"}, {{0, 11, 12}, {sendfile, 3, 12, \"t\"}}},\n\t\t{{6, infinity}, {sendfile, 3, 12, \"t\"}, {{6, 11, 12}, {sendfile, 9, 6, \"t\"}}},\n\t\t{{11, infinity}, {sendfile, 3, 12, \"t\"}, {{11, 11, 12}, {sendfile, 14, 1, \"t\"}}},\n\t\t%% Sendfile with a specific range.\n\t\t{{0, 11}, {sendfile, 0, 12, \"t\"}, {{0, 11, 12}, {sendfile, 0, 12, \"t\"}}},\n\t\t{{6, 11}, {sendfile, 0, 12, \"t\"}, {{6, 11, 12}, {sendfile, 6, 6, \"t\"}}},\n\t\t{{11, 11}, {sendfile, 0, 12, \"t\"}, {{11, 11, 12}, {sendfile, 11, 1, \"t\"}}},\n\t\t{{1, 10}, {sendfile, 0, 12, \"t\"}, {{1, 10, 12}, {sendfile, 1, 10, \"t\"}}},\n\t\t%% Sendfile with a specific range. Sendfile tuple has an offset originally.\n\t\t{{0, 11}, {sendfile, 3, 12, \"t\"}, {{0, 11, 12}, {sendfile, 3, 12, \"t\"}}},\n\t\t{{6, 11}, {sendfile, 3, 12, \"t\"}, {{6, 11, 12}, {sendfile, 9, 6, \"t\"}}},\n\t\t{{11, 11}, {sendfile, 3, 12, \"t\"}, {{11, 11, 12}, {sendfile, 14, 1, \"t\"}}},\n\t\t{{1, 10}, {sendfile, 3, 12, \"t\"}, {{1, 10, 12}, {sendfile, 4, 10, \"t\"}}},\n\t\t%% Sendfile with negative range.\n\t\t{-12, {sendfile, 0, 12, \"t\"}, {{0, 11, 12}, {sendfile, 0, 12, \"t\"}}},\n\t\t{-6, {sendfile, 0, 12, \"t\"}, {{6, 11, 12}, {sendfile, 6, 6, \"t\"}}},\n\t\t{-1, {sendfile, 0, 12, \"t\"}, {{11, 11, 12}, {sendfile, 11, 1, \"t\"}}},\n\t\t%% Sendfile with negative range. Sendfile tuple has an offset originally.\n\t\t{-12, {sendfile, 3, 12, \"t\"}, {{0, 11, 12}, {sendfile, 3, 12, \"t\"}}},\n\t\t{-6, {sendfile, 3, 12, \"t\"}, {{6, 11, 12}, {sendfile, 9, 6, \"t\"}}},\n\t\t{-1, {sendfile, 3, 12, \"t\"}, {{11, 11, 12}, {sendfile, 14, 1, \"t\"}}},\n\t\t%% Iodata with open-ended range.\n\t\t{{0, infinity}, <<\"Hello world!\">>, {{0, 11, 12}, <<\"Hello world!\">>}},\n\t\t{{6, infinity}, <<\"Hello world!\">>, {{6, 11, 12}, <<\"world!\">>}},\n\t\t{{11, infinity}, <<\"Hello world!\">>, {{11, 11, 12}, <<\"!\">>}},\n\t\t%% Iodata with a specific range. The resulting data is\n\t\t%% wrapped in a list because of how cow_iolists:split/2 works.\n\t\t{{0, 11}, <<\"Hello world!\">>, {{0, 11, 12}, [<<\"Hello world!\">>]}},\n\t\t{{6, 11}, <<\"Hello world!\">>, {{6, 11, 12}, [<<\"world!\">>]}},\n\t\t{{11, 11}, <<\"Hello world!\">>, {{11, 11, 12}, [<<\"!\">>]}},\n\t\t{{1, 10}, <<\"Hello world!\">>, {{1, 10, 12}, [<<\"ello world\">>]}},\n\t\t%% Iodata with negative range.\n\t\t{-12, <<\"Hello world!\">>, {{0, 11, 12}, <<\"Hello world!\">>}},\n\t\t{-6, <<\"Hello world!\">>, {{6, 11, 12}, <<\"world!\">>}},\n\t\t{-1, <<\"Hello world!\">>, {{11, 11, 12}, <<\"!\">>}}\n\t],\n\t[{iolist_to_binary(io_lib:format(\"range ~p data ~p\", [VR, VD])),\n\t\tfun() -> R = ranged_partition(VR, VD) end} || {VR, VD, R} <- Tests].\n-endif.\n\nset_ranged_body_callback(Req, State=#state{handler=Handler}, Callback) ->\n\ttry case call(Req, State, Callback) of\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t%% When we receive a single range, we send it directly.\n\t\t{[OneRange], Req2, State2} ->\n\t\t\tset_one_ranged_body(Req2, State2, OneRange);\n\t\t%% When we receive multiple ranges we have to send them as multipart/byteranges.\n\t\t%% This also applies to non-bytes units. (RFC7233 A) If users don't want to use\n\t\t%% this for non-bytes units they can always return a single range with a binary\n\t\t%% content-range information.\n\t\t{Ranges, Req2, State2} when length(Ranges) > 1 ->\n\t\t\t%% We have to check whether there are sendfile tuples in the\n\t\t\t%% ranges to be sent. If there are we must use stream_reply.\n\t\t\tHasSendfile = [] =/= [true || {_, {sendfile, _, _, _}} <- Ranges],\n\t\t\tcase HasSendfile of\n\t\t\t\ttrue -> send_multipart_ranged_body(Req2, State2, Ranges);\n\t\t\t\tfalse -> set_multipart_ranged_body(Req2, State2, Ranges)\n\t\t\tend\n\tend catch Class:{case_clause, no_call}:Stacktrace ->\n\t\terror_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},\n\t\t\t'A callback specified in ranges_provided/2 is not exported.'},\n\t\t\tStacktrace)\n\tend.\n\nset_one_ranged_body(Req0, State, OneRange) ->\n\t{ContentRange, Body} = prepare_range(Req0, OneRange),\n\tReq1 = cowboy_req:set_resp_header(<<\"content-range\">>, ContentRange, Req0),\n\tReq = cowboy_req:set_resp_body(Body, Req1),\n\trespond(Req, State, 206).\n\nset_multipart_ranged_body(Req, State, [FirstRange|MoreRanges]) ->\n\tBoundary = cow_multipart:boundary(),\n\tContentType = cowboy_req:resp_header(<<\"content-type\">>, Req),\n\t{FirstContentRange, FirstPartBody} = prepare_range(Req, FirstRange),\n\tFirstPartHead = cow_multipart:first_part(Boundary, [\n\t\t{<<\"content-type\">>, ContentType},\n\t\t{<<\"content-range\">>, FirstContentRange}\n\t]),\n\tMoreParts = [begin\n\t\t{NextContentRange, NextPartBody} = prepare_range(Req, NextRange),\n\t\tNextPartHead = cow_multipart:part(Boundary, [\n\t\t\t{<<\"content-type\">>, ContentType},\n\t\t\t{<<\"content-range\">>, NextContentRange}\n\t\t]),\n\t\t[NextPartHead, NextPartBody]\n\tend || NextRange <- MoreRanges],\n\tBody = [FirstPartHead, FirstPartBody, MoreParts, cow_multipart:close(Boundary)],\n\tReq2 = cowboy_req:set_resp_header(<<\"content-type\">>,\n\t\t[<<\"multipart/byteranges; boundary=\">>, Boundary], Req),\n\tReq3 = cowboy_req:set_resp_body(Body, Req2),\n\trespond(Req3, State, 206).\n\n%% Similar to set_multipart_ranged_body except we have to stream\n%% the data because the parts contain sendfile tuples.\nsend_multipart_ranged_body(Req, State, [FirstRange|MoreRanges]) ->\n\tBoundary = cow_multipart:boundary(),\n\tContentType = cowboy_req:resp_header(<<\"content-type\">>, Req),\n\tReq2 = cowboy_req:set_resp_header(<<\"content-type\">>,\n\t\t[<<\"multipart/byteranges; boundary=\">>, Boundary], Req),\n\tReq3 = cowboy_req:stream_reply(206, Req2),\n\t{FirstContentRange, FirstPartBody} = prepare_range(Req, FirstRange),\n\tFirstPartHead = cow_multipart:first_part(Boundary, [\n\t\t{<<\"content-type\">>, ContentType},\n\t\t{<<\"content-range\">>, FirstContentRange}\n\t]),\n\tcowboy_req:stream_body(FirstPartHead, nofin, Req3),\n\tcowboy_req:stream_body(FirstPartBody, nofin, Req3),\n\t_ = [begin\n\t\t{NextContentRange, NextPartBody} = prepare_range(Req, NextRange),\n\t\tNextPartHead = cow_multipart:part(Boundary, [\n\t\t\t{<<\"content-type\">>, ContentType},\n\t\t\t{<<\"content-range\">>, NextContentRange}\n\t\t]),\n\t\tcowboy_req:stream_body(NextPartHead, nofin, Req3),\n\t\tcowboy_req:stream_body(NextPartBody, nofin, Req3),\n\t\t[NextPartHead, NextPartBody]\n\tend || NextRange <- MoreRanges],\n\tcowboy_req:stream_body(cow_multipart:close(Boundary), fin, Req3),\n\tterminate(Req3, State).\n\nprepare_range(#{range := {RangeUnit, _}}, {{From, To, Total0}, Body}) ->\n\tTotal = case Total0 of\n\t\t'*' -> <<\"*\">>;\n\t\t_ -> integer_to_binary(Total0)\n\tend,\n\tContentRange = [RangeUnit, $\\s, integer_to_binary(From),\n\t\t$-, integer_to_binary(To), $/, Total],\n\t{ContentRange, Body};\nprepare_range(#{range := {RangeUnit, _}}, {RangeData, Body}) ->\n\t{[RangeUnit, $\\s, RangeData], Body}.\n\n%% We send the content-range header when we can on error.\nrange_not_satisfiable(Req, State, undefined) ->\n\trespond(Req, State, 416);\nrange_not_satisfiable(Req0=#{range := {RangeUnit, _}}, State, RangeData) ->\n\tReq = cowboy_req:set_resp_header(<<\"content-range\">>,\n\t\t[RangeUnit, $\\s, RangeData], Req0),\n\trespond(Req, State, 416).\n\n%% Set the response headers and call the callback found using\n%% content_types_provided/2 to obtain the request body and add\n%% it to the response.\nset_resp_body(Req, State=#state{handler=Handler, content_type_a={_, Callback}}) ->\n\ttry case call(Req, State, Callback) of\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{Body, Req2, State2} ->\n\t\t\tReq3 = cowboy_req:set_resp_body(Body, Req2),\n\t\t\tmultiple_choices(Req3, State2)\n\tend catch Class:{case_clause, no_call}:Stacktrace ->\n\t\terror_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}},\n\t\t\t'A callback specified in content_types_provided/2 is not exported.'},\n\t\t\tStacktrace)\n\tend.\n\nmultiple_choices(Req, State) ->\n\texpect(Req, State, multiple_choices, false, 200, 300).\n\n%% Response utility functions.\n\nset_resp_etag(Req, State) ->\n\t{Etag, Req2, State2} = generate_etag(Req, State),\n\tcase Etag of\n\t\tundefined ->\n\t\t\t{Req2, State2};\n\t\tEtag ->\n\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t<<\"etag\">>, encode_etag(Etag), Req2),\n\t\t\t{Req3, State2}\n\tend.\n\n-spec encode_etag({strong | weak, binary()}) -> iolist().\nencode_etag({strong, Etag}) -> [$\",Etag,$\"];\nencode_etag({weak, Etag}) -> [\"W/\\\"\",Etag,$\"].\n\nset_resp_expires(Req, State) ->\n\t{Expires, Req2, State2} = expires(Req, State),\n\tcase Expires of\n\t\tExpires when is_atom(Expires) ->\n\t\t\t{Req2, State2};\n\t\tExpires when is_binary(Expires) ->\n\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t<<\"expires\">>, Expires, Req2),\n\t\t\t{Req3, State2};\n\t\tExpires ->\n\t\t\tExpiresBin = cowboy_clock:rfc1123(Expires),\n\t\t\tReq3 = cowboy_req:set_resp_header(\n\t\t\t\t<<\"expires\">>, ExpiresBin, Req2),\n\t\t\t{Req3, State2}\n\tend.\n\n%% Info retrieval. No logic.\n\ngenerate_etag(Req, State=#state{etag=no_call}) ->\n\t{undefined, Req, State};\ngenerate_etag(Req, State=#state{etag=undefined}) ->\n\tcase unsafe_call(Req, State, generate_etag) of\n\t\tno_call ->\n\t\t\t{undefined, Req, State#state{etag=no_call}};\n\t\t%% We allow the callback to return 'undefined'\n\t\t%% to allow conditionally generating etags. We\n\t\t%% handle 'undefined' the same as if the function\n\t\t%% was not exported.\n\t\t{undefined, Req2, State2} ->\n\t\t\t{undefined, Req2, State2#state{etag=no_call}};\n\t\t{Etag, Req2, State2} when is_binary(Etag) ->\n\t\t\tEtag2 = cow_http_hd:parse_etag(Etag),\n\t\t\t{Etag2, Req2, State2#state{etag=Etag2}};\n\t\t{Etag, Req2, State2} ->\n\t\t\t{Etag, Req2, State2#state{etag=Etag}}\n\tend;\ngenerate_etag(Req, State=#state{etag=Etag}) ->\n\t{Etag, Req, State}.\n\nlast_modified(Req, State=#state{last_modified=no_call}) ->\n\t{undefined, Req, State};\nlast_modified(Req, State=#state{last_modified=undefined}) ->\n\tcase unsafe_call(Req, State, last_modified) of\n\t\tno_call ->\n\t\t\t{undefined, Req, State#state{last_modified=no_call}};\n\t\t%% We allow the callback to return 'undefined',\n\t\t%% in which case the generated header would be missing\n\t\t%% as if the callback was not called.\n\t\t{undefined, Req2, State2} ->\n\t\t\t{undefined, Req2, State2#state{last_modified=no_call}};\n\t\t{LastModified, Req2, State2} ->\n\t\t\t{LastModified, Req2, State2#state{last_modified=LastModified}}\n\tend;\nlast_modified(Req, State=#state{last_modified=LastModified}) ->\n\t{LastModified, Req, State}.\n\nexpires(Req, State=#state{expires=no_call}) ->\n\t{undefined, Req, State};\nexpires(Req, State=#state{expires=undefined}) ->\n\tcase unsafe_call(Req, State, expires) of\n\t\tno_call ->\n\t\t\t{undefined, Req, State#state{expires=no_call}};\n\t\t{Expires, Req2, State2} ->\n\t\t\t{Expires, Req2, State2#state{expires=Expires}}\n\tend;\nexpires(Req, State=#state{expires=Expires}) ->\n\t{Expires, Req, State}.\n\n%% REST primitives.\n\nexpect(Req, State, Callback, Expected, OnTrue, OnFalse) ->\n\tcase call(Req, State, Callback) of\n\t\tno_call ->\n\t\t\tnext(Req, State, OnTrue);\n\t\t{stop, Req2, State2} ->\n\t\t\tterminate(Req2, State2);\n\t\t{Switch, Req2, State2} when element(1, Switch) =:= switch_handler ->\n\t\t\tswitch_handler(Switch, Req2, State2);\n\t\t{Expected, Req2, State2} ->\n\t\t\tnext(Req2, State2, OnTrue);\n\t\t{_Unexpected, Req2, State2} ->\n\t\t\tnext(Req2, State2, OnFalse)\n\tend.\n\ncall(Req0, State=#state{handler=Handler,\n\t\thandler_state=HandlerState0}, Callback) ->\n\tcase erlang:function_exported(Handler, Callback, 2) of\n\t\ttrue ->\n\t\t\ttry Handler:Callback(Req0, HandlerState0) of\n\t\t\t\tno_call ->\n\t\t\t\t\tno_call;\n\t\t\t\t{Result, Req, HandlerState} ->\n\t\t\t\t\t{Result, Req, State#state{handler_state=HandlerState}}\n\t\t\tcatch Class:Reason:Stacktrace ->\n\t\t\t\terror_terminate(Req0, State, Class, Reason, Stacktrace)\n\t\t\tend;\n\t\tfalse ->\n\t\t\tno_call\n\tend.\n\nunsafe_call(Req0, State=#state{handler=Handler,\n\t\thandler_state=HandlerState0}, Callback) ->\n\tcase erlang:function_exported(Handler, Callback, 2) of\n\t\tfalse ->\n\t\t\tno_call;\n\t\ttrue ->\n\t\t\tcase Handler:Callback(Req0, HandlerState0) of\n\t\t\t\tno_call ->\n\t\t\t\t\tno_call;\n\t\t\t\t{Result, Req, HandlerState} ->\n\t\t\t\t\t{Result, Req, State#state{handler_state=HandlerState}}\n\t\t\tend\n\tend.\n\nnext(Req, State, Next) when is_function(Next) ->\n\tNext(Req, State);\nnext(Req, State, StatusCode) when is_integer(StatusCode) ->\n\trespond(Req, State, StatusCode).\n\nrespond(Req0, State, StatusCode) ->\n\t%% We remove the content-type header when there is no body,\n\t%% except when the status code is 200 because it might have\n\t%% been intended (for example sending an empty file).\n\tReq = case cowboy_req:has_resp_body(Req0) of\n\t\ttrue when StatusCode =:= 200 -> Req0;\n\t\ttrue -> Req0;\n\t\tfalse -> cowboy_req:delete_resp_header(<<\"content-type\">>, Req0)\n\tend,\n\tterminate(cowboy_req:reply(StatusCode, Req), State).\n\nswitch_handler({switch_handler, Mod}, Req, #state{handler_state=HandlerState}) ->\n\t{Mod, Req, HandlerState};\nswitch_handler({switch_handler, Mod, Opts}, Req, #state{handler_state=HandlerState}) ->\n\t{Mod, Req, HandlerState, Opts}.\n\n-spec error_terminate(cowboy_req:req(), #state{}, atom(), any(), any()) -> no_return().\nerror_terminate(Req, #state{handler=Handler, handler_state=HandlerState}, Class, Reason, Stacktrace) ->\n\tcowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler),\n\terlang:raise(Class, Reason, Stacktrace).\n\nterminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->\n\t%% @todo I don't think the result is used anywhere?\n\tResult = cowboy_handler:terminate(normal, Req, HandlerState, Handler),\n\t{ok, Req, Result}.\n"
  },
  {
    "path": "src/cowboy_router.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% Routing middleware.\n%%\n%% Resolve the handler to be used for the request based on the\n%% routing information found in the <em>dispatch</em> environment value.\n%% When found, the handler module and associated data are added to\n%% the environment as the <em>handler</em> and <em>handler_opts</em> values\n%% respectively.\n%%\n%% If the route cannot be found, processing stops with either\n%% a 400 or a 404 reply.\n-module(cowboy_router).\n-behaviour(cowboy_middleware).\n\n-export([compile/1]).\n-export([execute/2]).\n\n-type bindings() :: #{atom() => any()}.\n-type tokens() :: [binary()].\n-export_type([bindings/0]).\n-export_type([tokens/0]).\n\n-type route_match() :: '_' | iodata().\n-type route_path() :: {Path::route_match(), Handler::module(), Opts::any()}\n\t| {Path::route_match(), cowboy:fields(), Handler::module(), Opts::any()}.\n-type route_rule() :: {Host::route_match(), Paths::[route_path()]}\n\t| {Host::route_match(), cowboy:fields(), Paths::[route_path()]}.\n-type routes() :: [route_rule()].\n-export_type([routes/0]).\n\n-type dispatch_match() :: '_' | <<_:8>> | [binary() | '_' | '...' | atom()].\n-type dispatch_path() :: {dispatch_match(), cowboy:fields(), module(), any()}.\n-type dispatch_rule() :: {Host::dispatch_match(), cowboy:fields(), Paths::[dispatch_path()]}.\n-opaque dispatch_rules() :: [dispatch_rule()].\n-export_type([dispatch_rules/0]).\n\n-spec compile(routes()) -> dispatch_rules().\ncompile(Routes) ->\n\tcompile(Routes, []).\n\ncompile([], Acc) ->\n\tlists:reverse(Acc);\ncompile([{Host, Paths}|Tail], Acc) ->\n\tcompile([{Host, [], Paths}|Tail], Acc);\ncompile([{HostMatch, Fields, Paths}|Tail], Acc) ->\n\tHostRules = case HostMatch of\n\t\t'_' -> '_';\n\t\t_ -> compile_host(HostMatch)\n\tend,\n\tPathRules = compile_paths(Paths, []),\n\tHosts = case HostRules of\n\t\t'_' -> [{'_', Fields, PathRules}];\n\t\t_ -> [{R, Fields, PathRules} || R <- HostRules]\n\tend,\n\tcompile(Tail, Hosts ++ Acc).\n\ncompile_host(HostMatch) when is_list(HostMatch) ->\n\tcompile_host(list_to_binary(HostMatch));\ncompile_host(HostMatch) when is_binary(HostMatch) ->\n\tcompile_rules(HostMatch, $., [], [], <<>>).\n\ncompile_paths([], Acc) ->\n\tlists:reverse(Acc);\ncompile_paths([{PathMatch, Handler, Opts}|Tail], Acc) ->\n\tcompile_paths([{PathMatch, [], Handler, Opts}|Tail], Acc);\ncompile_paths([{PathMatch, Fields, Handler, Opts}|Tail], Acc)\n\t\twhen is_list(PathMatch) ->\n\tcompile_paths([{iolist_to_binary(PathMatch),\n\t\tFields, Handler, Opts}|Tail], Acc);\ncompile_paths([{'_', Fields, Handler, Opts}|Tail], Acc) ->\n\tcompile_paths(Tail, [{'_', Fields, Handler, Opts}] ++ Acc);\ncompile_paths([{<<\"*\">>, Fields, Handler, Opts}|Tail], Acc) ->\n\tcompile_paths(Tail, [{<<\"*\">>, Fields, Handler, Opts}|Acc]);\ncompile_paths([{<< $/, PathMatch/bits >>, Fields, Handler, Opts}|Tail],\n\t\tAcc) ->\n\tPathRules = compile_rules(PathMatch, $/, [], [], <<>>),\n\tPaths = [{lists:reverse(R), Fields, Handler, Opts} || R <- PathRules],\n\tcompile_paths(Tail, Paths ++ Acc);\ncompile_paths([{PathMatch, _, _, _}|_], _) ->\n\terror({badarg, \"The following route MUST begin with a slash: \"\n\t\t++ binary_to_list(PathMatch)}).\n\ncompile_rules(<<>>, _, Segments, Rules, <<>>) ->\n\t[Segments|Rules];\ncompile_rules(<<>>, _, Segments, Rules, Acc) ->\n\t[[Acc|Segments]|Rules];\ncompile_rules(<< S, Rest/bits >>, S, Segments, Rules, <<>>) ->\n\tcompile_rules(Rest, S, Segments, Rules, <<>>);\ncompile_rules(<< S, Rest/bits >>, S, Segments, Rules, Acc) ->\n\tcompile_rules(Rest, S, [Acc|Segments], Rules, <<>>);\n%% Colon on path segment start is special, otherwise allow.\ncompile_rules(<< $:, Rest/bits >>, S, Segments, Rules, <<>>) ->\n\t{NameBin, Rest2} = compile_binding(Rest, S, <<>>),\n\tName = binary_to_atom(NameBin, utf8),\n\tcompile_rules(Rest2, S, Segments, Rules, Name);\ncompile_rules(<< $[, $., $., $., $], Rest/bits >>, S, Segments, Rules, Acc)\n\t\twhen Acc =:= <<>> ->\n\tcompile_rules(Rest, S, ['...'|Segments], Rules, Acc);\ncompile_rules(<< $[, $., $., $., $], Rest/bits >>, S, Segments, Rules, Acc) ->\n\tcompile_rules(Rest, S, ['...', Acc|Segments], Rules, Acc);\ncompile_rules(<< $[, S, Rest/bits >>, S, Segments, Rules, Acc) ->\n\tcompile_brackets(Rest, S, [Acc|Segments], Rules);\ncompile_rules(<< $[, Rest/bits >>, S, Segments, Rules, <<>>) ->\n\tcompile_brackets(Rest, S, Segments, Rules);\n%% Open bracket in the middle of a segment.\ncompile_rules(<< $[, _/bits >>, _, _, _, _) ->\n\terror(badarg);\n%% Missing an open bracket.\ncompile_rules(<< $], _/bits >>, _, _, _, _) ->\n\terror(badarg);\ncompile_rules(<< C, Rest/bits >>, S, Segments, Rules, Acc) ->\n\tcompile_rules(Rest, S, Segments, Rules, << Acc/binary, C >>).\n\n%% Everything past $: until the segment separator ($. for hosts,\n%% $/ for paths) or $[ or $] or end of binary is the binding name.\ncompile_binding(<<>>, _, <<>>) ->\n\terror(badarg);\ncompile_binding(Rest = <<>>, _, Acc) ->\n\t{Acc, Rest};\ncompile_binding(Rest = << C, _/bits >>, S, Acc)\n\t\twhen C =:= S; C =:= $[; C =:= $] ->\n\t{Acc, Rest};\ncompile_binding(<< C, Rest/bits >>, S, Acc) ->\n\tcompile_binding(Rest, S, << Acc/binary, C >>).\n\ncompile_brackets(Rest, S, Segments, Rules) ->\n\t{Bracket, Rest2} = compile_brackets_split(Rest, <<>>, 0),\n\tRules1 = compile_rules(Rest2, S, Segments, [], <<>>),\n\tRules2 = compile_rules(<< Bracket/binary, Rest2/binary >>,\n\t\tS, Segments, [], <<>>),\n\tRules ++ Rules2 ++ Rules1.\n\n%% Missing a close bracket.\ncompile_brackets_split(<<>>, _, _) ->\n\terror(badarg);\n%% Make sure we don't confuse the closing bracket we're looking for.\ncompile_brackets_split(<< C, Rest/bits >>, Acc, N) when C =:= $[ ->\n\tcompile_brackets_split(Rest, << Acc/binary, C >>, N + 1);\ncompile_brackets_split(<< C, Rest/bits >>, Acc, N) when C =:= $], N > 0 ->\n\tcompile_brackets_split(Rest, << Acc/binary, C >>, N - 1);\n%% That's the right one.\ncompile_brackets_split(<< $], Rest/bits >>, Acc, 0) ->\n\t{Acc, Rest};\ncompile_brackets_split(<< C, Rest/bits >>, Acc, N) ->\n\tcompile_brackets_split(Rest, << Acc/binary, C >>, N).\n\n-spec execute(Req, Env)\n\t-> {ok, Req, Env} | {stop, Req}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\nexecute(Req=#{host := Host, path := Path}, Env=#{dispatch := Dispatch0}) ->\n\tDispatch = case Dispatch0 of\n\t\t{persistent_term, Key} -> persistent_term:get(Key);\n\t\t_ -> Dispatch0\n\tend,\n\tcase match(Dispatch, Host, Path) of\n\t\t{ok, Handler, HandlerOpts, Bindings, HostInfo, PathInfo} ->\n\t\t\t{ok, Req#{\n\t\t\t\thost_info => HostInfo,\n\t\t\t\tpath_info => PathInfo,\n\t\t\t\tbindings => Bindings\n\t\t\t}, Env#{\n\t\t\t\thandler => Handler,\n\t\t\t\thandler_opts => HandlerOpts\n\t\t\t}};\n\t\t{error, notfound, host} ->\n\t\t\t{stop, cowboy_req:reply(400, Req)};\n\t\t{error, badrequest, path} ->\n\t\t\t{stop, cowboy_req:reply(400, Req)};\n\t\t{error, notfound, path} ->\n\t\t\t{stop, cowboy_req:reply(404, Req)}\n\tend.\n\n%% Internal.\n\n%% Match hostname tokens and path tokens against dispatch rules.\n%%\n%% It is typically used for matching tokens for the hostname and path of\n%% the request against a global dispatch rule for your listener.\n%%\n%% Dispatch rules are a list of <em>{Hostname, PathRules}</em> tuples, with\n%% <em>PathRules</em> being a list of <em>{Path, HandlerMod, HandlerOpts}</em>.\n%%\n%% <em>Hostname</em> and <em>Path</em> are match rules and can be either the\n%% atom <em>'_'</em>, which matches everything, `<<\"*\">>', which match the\n%% wildcard path, or a list of tokens.\n%%\n%% Each token can be either a binary, the atom <em>'_'</em>,\n%% the atom '...' or a named atom. A binary token must match exactly,\n%% <em>'_'</em> matches everything for a single token, <em>'...'</em> matches\n%% everything for the rest of the tokens and a named atom will bind the\n%% corresponding token value and return it.\n%%\n%% The list of hostname tokens is reversed before matching. For example, if\n%% we were to match \"www.ninenines.eu\", we would first match \"eu\", then\n%% \"ninenines\", then \"www\". This means that in the context of hostnames,\n%% the <em>'...'</em> atom matches properly the lower levels of the domain\n%% as would be expected.\n%%\n%% When a result is found, this function will return the handler module and\n%% options found in the dispatch list, a key-value list of bindings and\n%% the tokens that were matched by the <em>'...'</em> atom for both the\n%% hostname and path.\n-spec match(dispatch_rules(), Host::binary() | tokens(), Path::binary())\n\t-> {ok, module(), any(), bindings(),\n\t\tHostInfo::undefined | tokens(),\n\t\tPathInfo::undefined | tokens()}\n\t| {error, notfound, host} | {error, notfound, path}\n\t| {error, badrequest, path}.\nmatch([], _, _) ->\n\t{error, notfound, host};\n%% If the host is '_' then there can be no constraints.\nmatch([{'_', [], PathMatchs}|_Tail], _, Path) ->\n\tmatch_path(PathMatchs, undefined, Path, #{});\nmatch([{HostMatch, Fields, PathMatchs}|Tail], Tokens, Path)\n\t\twhen is_list(Tokens) ->\n\tcase list_match(Tokens, HostMatch, #{}) of\n\t\tfalse ->\n\t\t\tmatch(Tail, Tokens, Path);\n\t\t{true, Bindings, HostInfo} ->\n\t\t\tHostInfo2 = case HostInfo of\n\t\t\t\tundefined -> undefined;\n\t\t\t\t_ -> lists:reverse(HostInfo)\n\t\t\tend,\n\t\t\tcase check_constraints(Fields, Bindings) of\n\t\t\t\t{ok, Bindings2} ->\n\t\t\t\t\tmatch_path(PathMatchs, HostInfo2, Path, Bindings2);\n\t\t\t\tnomatch ->\n\t\t\t\t\tmatch(Tail, Tokens, Path)\n\t\t\tend\n\tend;\nmatch(Dispatch, Host, Path) ->\n\tmatch(Dispatch, split_host(Host), Path).\n\n-spec match_path([dispatch_path()],\n\tHostInfo::undefined | tokens(), binary() | tokens(), bindings())\n\t-> {ok, module(), any(), bindings(),\n\t\tHostInfo::undefined | tokens(),\n\t\tPathInfo::undefined | tokens()}\n\t| {error, notfound, path} | {error, badrequest, path}.\nmatch_path([], _, _, _) ->\n\t{error, notfound, path};\n%% If the path is '_' then there can be no constraints.\nmatch_path([{'_', [], Handler, Opts}|_Tail], HostInfo, _, Bindings) ->\n\t{ok, Handler, Opts, Bindings, HostInfo, undefined};\nmatch_path([{<<\"*\">>, _, Handler, Opts}|_Tail], HostInfo, <<\"*\">>, Bindings) ->\n\t{ok, Handler, Opts, Bindings, HostInfo, undefined};\nmatch_path([_|Tail], HostInfo, <<\"*\">>, Bindings) ->\n\tmatch_path(Tail, HostInfo, <<\"*\">>, Bindings);\nmatch_path([{PathMatch, Fields, Handler, Opts}|Tail], HostInfo, Tokens,\n\t\tBindings) when is_list(Tokens) ->\n\tcase list_match(Tokens, PathMatch, Bindings) of\n\t\tfalse ->\n\t\t\tmatch_path(Tail, HostInfo, Tokens, Bindings);\n\t\t{true, PathBinds, PathInfo} ->\n\t\t\tcase check_constraints(Fields, PathBinds) of\n\t\t\t\t{ok, PathBinds2} ->\n\t\t\t\t\t{ok, Handler, Opts, PathBinds2, HostInfo, PathInfo};\n\t\t\t\tnomatch ->\n\t\t\t\t\tmatch_path(Tail, HostInfo, Tokens, Bindings)\n\t\t\tend\n\tend;\nmatch_path(_Dispatch, _HostInfo, badrequest, _Bindings) ->\n\t{error, badrequest, path};\nmatch_path(Dispatch, HostInfo, Path, Bindings) ->\n\tmatch_path(Dispatch, HostInfo, split_path(Path), Bindings).\n\ncheck_constraints([], Bindings) ->\n\t{ok, Bindings};\ncheck_constraints([Field|Tail], Bindings) when is_atom(Field) ->\n\tcheck_constraints(Tail, Bindings);\ncheck_constraints([Field|Tail], Bindings) ->\n\tName = element(1, Field),\n\tcase Bindings of\n\t\t#{Name := Value0} ->\n\t\t\tConstraints = element(2, Field),\n\t\t\tcase cowboy_constraints:validate(Value0, Constraints) of\n\t\t\t\t{ok, Value} ->\n\t\t\t\t\tcheck_constraints(Tail, Bindings#{Name => Value});\n\t\t\t\t{error, _} ->\n\t\t\t\t\tnomatch\n\t\t\tend;\n\t\t_ ->\n\t\t\tcheck_constraints(Tail, Bindings)\n\tend.\n\n-spec split_host(binary()) -> tokens().\nsplit_host(Host) ->\n\tsplit_host(Host, []).\n\nsplit_host(Host, Acc) ->\n\tcase binary:match(Host, <<\".\">>) of\n\t\tnomatch when Host =:= <<>> ->\n\t\t\tAcc;\n\t\tnomatch ->\n\t\t\t[Host|Acc];\n\t\t{Pos, _} ->\n\t\t\t<< Segment:Pos/binary, _:8, Rest/bits >> = Host,\n\t\t\tfalse = byte_size(Segment) == 0,\n\t\t\tsplit_host(Rest, [Segment|Acc])\n\tend.\n\n%% Following RFC2396, this function may return path segments containing any\n%% character, including <em>/</em> if, and only if, a <em>/</em> was escaped\n%% and part of a path segment.\n-spec split_path(binary()) -> tokens() | badrequest.\nsplit_path(<< $/, Path/bits >>) ->\n\tsplit_path(Path, []);\nsplit_path(_) ->\n\tbadrequest.\n\nsplit_path(Path, Acc) ->\n\ttry\n\t\tcase binary:match(Path, <<\"/\">>) of\n\t\t\tnomatch when Path =:= <<>> ->\n\t\t\t\tremove_dot_segments(lists:reverse([cow_uri:urldecode(S) || S <- Acc]), []);\n\t\t\tnomatch ->\n\t\t\t\tremove_dot_segments(lists:reverse([cow_uri:urldecode(S) || S <- [Path|Acc]]), []);\n\t\t\t{Pos, _} ->\n\t\t\t\t<< Segment:Pos/binary, _:8, Rest/bits >> = Path,\n\t\t\t\tsplit_path(Rest, [Segment|Acc])\n\t\tend\n\tcatch error:_ ->\n\t\tbadrequest\n\tend.\n\nremove_dot_segments([], Acc) ->\n\tlists:reverse(Acc);\nremove_dot_segments([<<\".\">>|Segments], Acc) ->\n\tremove_dot_segments(Segments, Acc);\nremove_dot_segments([<<\"..\">>|Segments], Acc=[]) ->\n\tremove_dot_segments(Segments, Acc);\nremove_dot_segments([<<\"..\">>|Segments], [_|Acc]) ->\n\tremove_dot_segments(Segments, Acc);\nremove_dot_segments([S|Segments], Acc) ->\n\tremove_dot_segments(Segments, [S|Acc]).\n\n-ifdef(TEST).\nremove_dot_segments_test_() ->\n\tTests = [\n\t\t{[<<\"a\">>, <<\"b\">>, <<\"c\">>, <<\".\">>, <<\"..\">>, <<\"..\">>, <<\"g\">>], [<<\"a\">>, <<\"g\">>]},\n\t\t{[<<\"mid\">>, <<\"content=5\">>, <<\"..\">>, <<\"6\">>], [<<\"mid\">>, <<\"6\">>]},\n\t\t{[<<\"..\">>, <<\"a\">>], [<<\"a\">>]}\n\t],\n\t[fun() -> R = remove_dot_segments(S, []) end || {S, R} <- Tests].\n-endif.\n\n-spec list_match(tokens(), dispatch_match(), bindings())\n\t-> {true, bindings(), undefined | tokens()} | false.\n%% Atom '...' matches any trailing path, stop right now.\nlist_match(List, ['...'], Binds) ->\n\t{true, Binds, List};\n%% Atom '_' matches anything, continue.\nlist_match([_E|Tail], ['_'|TailMatch], Binds) ->\n\tlist_match(Tail, TailMatch, Binds);\n%% Both values match, continue.\nlist_match([E|Tail], [E|TailMatch], Binds) ->\n\tlist_match(Tail, TailMatch, Binds);\n%% Bind E to the variable name V and continue,\n%% unless V was already defined and E isn't identical to the previous value.\nlist_match([E|Tail], [V|TailMatch], Binds) when is_atom(V) ->\n\tcase Binds of\n\t\t%% @todo This isn't right, the constraint must be applied FIRST\n\t\t%% otherwise we can't check for example ints in both host/path.\n\t\t#{V := E} ->\n\t\t\tlist_match(Tail, TailMatch, Binds);\n\t\t#{V := _} ->\n\t\t\tfalse;\n\t\t_ ->\n\t\t\tlist_match(Tail, TailMatch, Binds#{V => E})\n\tend;\n%% Match complete.\nlist_match([], [], Binds) ->\n\t{true, Binds, undefined};\n%% Values don't match, stop.\nlist_match(_List, _Match, _Binds) ->\n\tfalse.\n\n%% Tests.\n\n-ifdef(TEST).\ncompile_test_() ->\n\tTests = [\n\t\t%% Match any host and path.\n\t\t{[{'_', [{'_', h, o}]}],\n\t\t\t[{'_', [], [{'_', [], h, o}]}]},\n\t\t{[{\"cowboy.example.org\",\n\t\t\t\t[{\"/\", ha, oa}, {\"/path/to/resource\", hb, ob}]}],\n\t\t\t[{[<<\"org\">>, <<\"example\">>, <<\"cowboy\">>], [], [\n\t\t\t\t{[], [], ha, oa},\n\t\t\t\t{[<<\"path\">>, <<\"to\">>, <<\"resource\">>], [], hb, ob}]}]},\n\t\t{[{'_', [{\"/path/to/resource/\", h, o}]}],\n\t\t\t[{'_', [], [{[<<\"path\">>, <<\"to\">>, <<\"resource\">>], [], h, o}]}]},\n\t\t% Cyrillic from a latin1 encoded file.\n\t\t{[{'_', [{[47,208,191,209,131,209,130,209,140,47,208,186,47,209,128,\n\t\t\t\t208,181,209,129,209,131,209,128,209,129,209,131,47], h, o}]}],\n\t\t\t[{'_', [], [{[<<208,191,209,131,209,130,209,140>>, <<208,186>>,\n\t\t\t\t<<209,128,208,181,209,129,209,131,209,128,209,129,209,131>>],\n\t\t\t\t[], h, o}]}]},\n\t\t{[{\"cowboy.example.org.\", [{'_', h, o}]}],\n\t\t\t[{[<<\"org\">>, <<\"example\">>, <<\"cowboy\">>], [], [{'_', [], h, o}]}]},\n\t\t{[{\".cowboy.example.org\", [{'_', h, o}]}],\n\t\t\t[{[<<\"org\">>, <<\"example\">>, <<\"cowboy\">>], [], [{'_', [], h, o}]}]},\n\t\t% Cyrillic from a latin1 encoded file.\n\t\t{[{[208,189,208,181,208,186,208,184,208,185,46,209,129,208,176,\n\t\t\t\t208,185,209,130,46,209,128,209,132,46], [{'_', h, o}]}],\n\t\t\t[{[<<209,128,209,132>>, <<209,129,208,176,208,185,209,130>>,\n\t\t\t\t<<208,189,208,181,208,186,208,184,208,185>>],\n\t\t\t\t[], [{'_', [], h, o}]}]},\n\t\t{[{\":subdomain.example.org\", [{\"/hats/:name/prices\", h, o}]}],\n\t\t\t[{[<<\"org\">>, <<\"example\">>, subdomain], [], [\n\t\t\t\t{[<<\"hats\">>, name, <<\"prices\">>], [], h, o}]}]},\n\t\t{[{\"ninenines.:_\", [{\"/hats/:_\", h, o}]}],\n\t\t\t[{['_', <<\"ninenines\">>], [], [{[<<\"hats\">>, '_'], [], h, o}]}]},\n\t\t{[{\"[www.]ninenines.eu\",\n\t\t\t[{\"/horses\", h, o}, {\"/hats/[page/:number]\", h, o}]}], [\n\t\t\t\t{[<<\"eu\">>, <<\"ninenines\">>], [], [\n\t\t\t\t\t{[<<\"horses\">>], [], h, o},\n\t\t\t\t\t{[<<\"hats\">>], [], h, o},\n\t\t\t\t\t{[<<\"hats\">>, <<\"page\">>, number], [], h, o}]},\n\t\t\t\t{[<<\"eu\">>, <<\"ninenines\">>, <<\"www\">>], [], [\n\t\t\t\t\t{[<<\"horses\">>], [], h, o},\n\t\t\t\t\t{[<<\"hats\">>], [], h, o},\n\t\t\t\t\t{[<<\"hats\">>, <<\"page\">>, number], [], h, o}]}]},\n\t\t{[{'_', [{\"/hats/:page/:number\", h, o}]}], [{'_', [], [\n\t\t\t{[<<\"hats\">>, page, number], [], h, o}]}]},\n\t\t{[{'_', [{\"/hats/[page/[:number]]\", h, o}]}], [{'_', [], [\n\t\t\t{[<<\"hats\">>], [], h, o},\n\t\t\t{[<<\"hats\">>, <<\"page\">>], [], h, o},\n\t\t\t{[<<\"hats\">>, <<\"page\">>, number], [], h, o}]}]},\n\t\t{[{\"[...]ninenines.eu\", [{\"/hats/[...]\", h, o}]}],\n\t\t\t[{[<<\"eu\">>, <<\"ninenines\">>, '...'], [], [\n\t\t\t\t{[<<\"hats\">>, '...'], [], h, o}]}]},\n\t\t%% Path segment containing a colon.\n\t\t{[{'_', [{\"/foo/bar:blah\", h, o}]}], [{'_', [], [\n\t\t\t{[<<\"foo\">>, <<\"bar:blah\">>], [], h, o}]}]}\n\t],\n\t[{lists:flatten(io_lib:format(\"~p\", [Rt])),\n\t\tfun() -> Rs = compile(Rt) end} || {Rt, Rs} <- Tests].\n\nsplit_host_test_() ->\n\tTests = [\n\t\t{<<\"\">>, []},\n\t\t{<<\"*\">>, [<<\"*\">>]},\n\t\t{<<\"cowboy.ninenines.eu\">>,\n\t\t\t[<<\"eu\">>, <<\"ninenines\">>, <<\"cowboy\">>]},\n\t\t{<<\"ninenines.eu\">>,\n\t\t\t[<<\"eu\">>, <<\"ninenines\">>]},\n\t\t{<<\"ninenines.eu.\">>,\n\t\t\t[<<\"eu\">>, <<\"ninenines\">>]},\n\t\t{<<\"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\">>,\n\t\t\t[<<\"z\">>, <<\"y\">>, <<\"x\">>, <<\"w\">>, <<\"v\">>, <<\"u\">>, <<\"t\">>,\n\t\t\t<<\"s\">>, <<\"r\">>, <<\"q\">>, <<\"p\">>, <<\"o\">>, <<\"n\">>, <<\"m\">>,\n\t\t\t<<\"l\">>, <<\"k\">>, <<\"j\">>, <<\"i\">>, <<\"h\">>, <<\"g\">>, <<\"f\">>,\n\t\t\t<<\"e\">>, <<\"d\">>, <<\"c\">>, <<\"b\">>, <<\"a\">>]}\n\t],\n\t[{H, fun() -> R = split_host(H) end} || {H, R} <- Tests].\n\nsplit_path_test_() ->\n\tTests = [\n\t\t{<<\"/\">>, []},\n\t\t{<<\"/extend//cowboy\">>, [<<\"extend\">>, <<>>, <<\"cowboy\">>]},\n\t\t{<<\"/users\">>, [<<\"users\">>]},\n\t\t{<<\"/users/42/friends\">>, [<<\"users\">>, <<\"42\">>, <<\"friends\">>]},\n\t\t{<<\"/users/a%20b/c%21d\">>, [<<\"users\">>, <<\"a b\">>, <<\"c!d\">>]}\n\t],\n\t[{P, fun() -> R = split_path(P) end} || {P, R} <- Tests].\n\nmatch_test_() ->\n\tDispatch = [\n\t\t{[<<\"eu\">>, <<\"ninenines\">>, '_', <<\"www\">>], [], [\n\t\t\t{[<<\"users\">>, '_', <<\"mails\">>], [], match_any_subdomain_users, []}\n\t\t]},\n\t\t{[<<\"eu\">>, <<\"ninenines\">>], [], [\n\t\t\t{[<<\"users\">>, id, <<\"friends\">>], [], match_extend_users_friends, []},\n\t\t\t{'_', [], match_extend, []}\n\t\t]},\n\t\t{[var, <<\"ninenines\">>], [], [\n\t\t\t{[<<\"threads\">>, var], [], match_duplicate_vars,\n\t\t\t\t[we, {expect, two}, var, here]}\n\t\t]},\n\t\t{[ext, <<\"erlang\">>], [], [\n\t\t\t{'_', [], match_erlang_ext, []}\n\t\t]},\n\t\t{'_', [], [\n\t\t\t{[<<\"users\">>, id, <<\"friends\">>], [], match_users_friends, []},\n\t\t\t{'_', [], match_any, []}\n\t\t]}\n\t],\n\tTests = [\n\t\t{<<\"any\">>, <<\"/\">>, {ok, match_any, [], #{}}},\n\t\t{<<\"www.any.ninenines.eu\">>, <<\"/users/42/mails\">>,\n\t\t\t{ok, match_any_subdomain_users, [], #{}}},\n\t\t{<<\"www.ninenines.eu\">>, <<\"/users/42/mails\">>,\n\t\t\t{ok, match_any, [], #{}}},\n\t\t{<<\"www.ninenines.eu\">>, <<\"/\">>,\n\t\t\t{ok, match_any, [], #{}}},\n\t\t{<<\"www.any.ninenines.eu\">>, <<\"/not_users/42/mails\">>,\n\t\t\t{error, notfound, path}},\n\t\t{<<\"ninenines.eu\">>, <<\"/\">>,\n\t\t\t{ok, match_extend, [], #{}}},\n\t\t{<<\"ninenines.eu\">>, <<\"/users/42/friends\">>,\n\t\t\t{ok, match_extend_users_friends, [], #{id => <<\"42\">>}}},\n\t\t{<<\"erlang.fr\">>, '_',\n\t\t\t{ok, match_erlang_ext, [], #{ext => <<\"fr\">>}}},\n\t\t{<<\"any\">>, <<\"/users/444/friends\">>,\n\t\t\t{ok, match_users_friends, [], #{id => <<\"444\">>}}},\n\t\t{<<\"any\">>, <<\"/users//friends\">>,\n\t\t\t{ok, match_users_friends, [], #{id => <<>>}}}\n\t],\n\t[{lists:flatten(io_lib:format(\"~p, ~p\", [H, P])), fun() ->\n\t\t{ok, Handler, Opts, Binds, undefined, undefined}\n\t\t\t= match(Dispatch, H, P)\n\tend} || {H, P, {ok, Handler, Opts, Binds}} <- Tests].\n\nmatch_info_test_() ->\n\tDispatch = [\n\t\t{[<<\"eu\">>, <<\"ninenines\">>, <<\"www\">>], [], [\n\t\t\t{[<<\"pathinfo\">>, <<\"is\">>, <<\"next\">>, '...'], [], match_path, []}\n\t\t]},\n\t\t{[<<\"eu\">>, <<\"ninenines\">>, '...'], [], [\n\t\t\t{'_', [], match_any, []}\n\t\t]}\n\t],\n\tTests = [\n\t\t{<<\"ninenines.eu\">>, <<\"/\">>,\n\t\t\t{ok, match_any, [], #{}, [], undefined}},\n\t\t{<<\"bugs.ninenines.eu\">>, <<\"/\">>,\n\t\t\t{ok, match_any, [], #{}, [<<\"bugs\">>], undefined}},\n\t\t{<<\"cowboy.bugs.ninenines.eu\">>, <<\"/\">>,\n\t\t\t{ok, match_any, [], #{}, [<<\"cowboy\">>, <<\"bugs\">>], undefined}},\n\t\t{<<\"www.ninenines.eu\">>, <<\"/pathinfo/is/next\">>,\n\t\t\t{ok, match_path, [], #{}, undefined, []}},\n\t\t{<<\"www.ninenines.eu\">>, <<\"/pathinfo/is/next/path_info\">>,\n\t\t\t{ok, match_path, [], #{}, undefined, [<<\"path_info\">>]}},\n\t\t{<<\"www.ninenines.eu\">>, <<\"/pathinfo/is/next/foo/bar\">>,\n\t\t\t{ok, match_path, [], #{}, undefined, [<<\"foo\">>, <<\"bar\">>]}}\n\t],\n\t[{lists:flatten(io_lib:format(\"~p, ~p\", [H, P])), fun() ->\n\t\tR = match(Dispatch, H, P)\n\tend} || {H, P, R} <- Tests].\n\nmatch_constraints_test() ->\n\tDispatch0 = [{'_', [],\n\t\t[{[<<\"path\">>, value], [{value, int}], match, []}]}],\n\t{ok, _, [], #{value := 123}, _, _} = match(Dispatch0,\n\t\t<<\"ninenines.eu\">>, <<\"/path/123\">>),\n\t{ok, _, [], #{value := 123}, _, _} = match(Dispatch0,\n\t\t<<\"ninenines.eu\">>, <<\"/path/123/\">>),\n\t{error, notfound, path} = match(Dispatch0,\n\t\t<<\"ninenines.eu\">>, <<\"/path/NaN/\">>),\n\tDispatch1 = [{'_', [],\n\t\t[{[<<\"path\">>, value, <<\"more\">>], [{value, nonempty}], match, []}]}],\n\t{ok, _, [], #{value := <<\"something\">>}, _, _} = match(Dispatch1,\n\t\t<<\"ninenines.eu\">>, <<\"/path/something/more\">>),\n\t{error, notfound, path} = match(Dispatch1,\n\t\t<<\"ninenines.eu\">>, <<\"/path//more\">>),\n\tDispatch2 = [{'_', [], [{[<<\"path\">>, username],\n\t\t[{username, fun(_, Value) ->\n\t\t\tcase cowboy_bstr:to_lower(Value) of\n\t\t\t\tValue -> {ok, Value};\n\t\t\t\t_ -> {error, not_lowercase}\n\t\t\tend end}],\n\t\tmatch, []}]}],\n\t{ok, _, [], #{username := <<\"essen\">>}, _, _} = match(Dispatch2,\n\t\t<<\"ninenines.eu\">>, <<\"/path/essen\">>),\n\t{error, notfound, path} = match(Dispatch2,\n\t\t<<\"ninenines.eu\">>, <<\"/path/ESSEN\">>),\n\tok.\n\nmatch_same_bindings_test() ->\n\tDispatch = [{[same, same], [], [{'_', [], match, []}]}],\n\t{ok, _, [], #{same := <<\"eu\">>}, _, _} = match(Dispatch,\n\t\t<<\"eu.eu\">>, <<\"/\">>),\n\t{error, notfound, host} = match(Dispatch,\n\t\t<<\"ninenines.eu\">>, <<\"/\">>),\n\tDispatch2 = [{[<<\"eu\">>, <<\"ninenines\">>, user], [],\n\t\t[{[<<\"path\">>, user], [], match, []}]}],\n\t{ok, _, [], #{user := <<\"essen\">>}, _, _} = match(Dispatch2,\n\t\t<<\"essen.ninenines.eu\">>, <<\"/path/essen\">>),\n\t{ok, _, [], #{user := <<\"essen\">>}, _, _} = match(Dispatch2,\n\t\t<<\"essen.ninenines.eu\">>, <<\"/path/essen/\">>),\n\t{error, notfound, path} = match(Dispatch2,\n\t\t<<\"essen.ninenines.eu\">>, <<\"/path/notessen\">>),\n\tDispatch3 = [{'_', [], [{[same, same], [], match, []}]}],\n\t{ok, _, [], #{same := <<\"path\">>}, _, _} = match(Dispatch3,\n\t\t<<\"ninenines.eu\">>, <<\"/path/path\">>),\n\t{error, notfound, path} = match(Dispatch3,\n\t\t<<\"ninenines.eu\">>, <<\"/path/to\">>),\n\tok.\n-endif.\n"
  },
  {
    "path": "src/cowboy_static.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%% Copyright (c) Magnus Klaar <magnus.klaar@gmail.com>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_static).\n\n-export([init/2]).\n-export([malformed_request/2]).\n-export([forbidden/2]).\n-export([content_types_provided/2]).\n-export([charsets_provided/2]).\n-export([ranges_provided/2]).\n-export([resource_exists/2]).\n-export([last_modified/2]).\n-export([generate_etag/2]).\n-export([get_file/2]).\n\n-type extra_charset() :: {charset, module(), function()} | {charset, binary()}.\n-type extra_etag() :: {etag, module(), function()} | {etag, false}.\n-type extra_mimetypes() :: {mimetypes, module(), function()}\n\t| {mimetypes, binary() | {binary(), binary(), '*' | [{binary(), binary()}]}}.\n-type extra() :: [extra_charset() | extra_etag() | extra_mimetypes()].\n-type opts() :: {file | dir, string() | binary()}\n\t| {file | dir, string() | binary(), extra()}\n\t| {priv_file | priv_dir, atom(), string() | binary()}\n\t| {priv_file | priv_dir, atom(), string() | binary(), extra()}.\n-export_type([opts/0]).\n\n-include_lib(\"kernel/include/file.hrl\").\n\n-type state() :: {binary(), {direct | archive, #file_info{}}\n\t| {error, atom()}, extra()}.\n\n%% Resolve the file that will be sent and get its file information.\n%% If the handler is configured to manage a directory, check that the\n%% requested file is inside the configured directory.\n\n-spec init(Req, opts()) -> {cowboy_rest, Req, error | state()} when Req::cowboy_req:req().\ninit(Req, {Name, Path}) ->\n\tinit_opts(Req, {Name, Path, []});\ninit(Req, {Name, App, Path})\n\t\twhen Name =:= priv_file; Name =:= priv_dir ->\n\tinit_opts(Req, {Name, App, Path, []});\ninit(Req, Opts) ->\n\tinit_opts(Req, Opts).\n\ninit_opts(Req, {priv_file, App, Path, Extra}) ->\n\t{PrivPath, HowToAccess} = priv_path(App, Path),\n\tinit_info(Req, absname(PrivPath), HowToAccess, Extra);\ninit_opts(Req, {file, Path, Extra}) ->\n\tinit_info(Req, absname(Path), direct, Extra);\ninit_opts(Req, {priv_dir, App, Path, Extra}) ->\n\t{PrivPath, HowToAccess} = priv_path(App, Path),\n\tinit_dir(Req, PrivPath, HowToAccess, Extra);\ninit_opts(Req, {dir, Path, Extra}) ->\n\tinit_dir(Req, Path, direct, Extra).\n\npriv_path(App, Path) ->\n\tcase code:priv_dir(App) of\n\t\t{error, bad_name} ->\n\t\t\terror({badarg, \"Can't resolve the priv_dir of application \"\n\t\t\t\t++ atom_to_list(App)});\n\t\tPrivDir when is_list(Path) ->\n\t\t\t{\n\t\t\t\tPrivDir ++ \"/\" ++ Path,\n\t\t\t\thow_to_access_app_priv(PrivDir)\n\t\t\t};\n\t\tPrivDir when is_binary(Path) ->\n\t\t\t{\n\t\t\t\t<< (list_to_binary(PrivDir))/binary, $/, Path/binary >>,\n\t\t\t\thow_to_access_app_priv(PrivDir)\n\t\t\t}\n\tend.\n\nhow_to_access_app_priv(PrivDir) ->\n\t%% If the priv directory is not a directory, it must be\n\t%% inside an Erlang application .ez archive. We call\n\t%% how_to_access_app_priv1() to find the corresponding archive.\n\tcase filelib:is_dir(PrivDir) of\n\t\ttrue  -> direct;\n\t\tfalse -> how_to_access_app_priv1(PrivDir)\n\tend.\n\nhow_to_access_app_priv1(Dir) ->\n\t%% We go \"up\" by one path component at a time and look for a\n\t%% regular file.\n\tArchive = filename:dirname(Dir),\n\tcase Archive of\n\t\tDir ->\n\t\t\t%% filename:dirname() returned its argument:\n\t\t\t%% we reach the root directory. We found no\n\t\t\t%% archive so we return 'direct': the given priv\n\t\t\t%% directory doesn't exist.\n\t\t\tdirect;\n\t\t_ ->\n\t\t\tcase filelib:is_regular(Archive) of\n\t\t\t\ttrue  -> {archive, Archive};\n\t\t\t\tfalse -> how_to_access_app_priv1(Archive)\n\t\t\tend\n\tend.\n\nabsname(Path) when is_list(Path) ->\n\tfilename:absname(list_to_binary(Path));\nabsname(Path) when is_binary(Path) ->\n\tfilename:absname(Path).\n\ninit_dir(Req, Path, HowToAccess, Extra) when is_list(Path) ->\n\tinit_dir(Req, list_to_binary(Path), HowToAccess, Extra);\ninit_dir(Req, Path, HowToAccess, Extra) ->\n\tDir = fullpath(filename:absname(Path)),\n\tcase cowboy_req:path_info(Req) of\n\t\t%% When dir/priv_dir are used and there is no path_info\n\t\t%% this is a configuration error and we abort immediately.\n\t\tundefined ->\n\t\t\t{ok, cowboy_req:reply(500, Req), error};\n\t\tPathInfo ->\n\t\t\tcase validate_reserved(PathInfo) of\n\t\t\t\terror ->\n\t\t\t\t\t{cowboy_rest, Req, error};\n\t\t\t\tok ->\n\t\t\t\t\tFilepath = filename:join([Dir|PathInfo]),\n\t\t\t\t\tLen = byte_size(Dir),\n\t\t\t\t\tcase fullpath(Filepath) of\n\t\t\t\t\t\t<< Dir:Len/binary, $/, _/binary >> ->\n\t\t\t\t\t\t\tinit_info(Req, Filepath, HowToAccess, Extra);\n\t\t\t\t\t\t<< Dir:Len/binary >> ->\n\t\t\t\t\t\t\tinit_info(Req, Filepath, HowToAccess, Extra);\n\t\t\t\t\t\t_ ->\n\t\t\t\t\t\t\t{cowboy_rest, Req, error}\n\t\t\t\t\tend\n\t\t\tend\n\tend.\n\nvalidate_reserved([]) ->\n\tok;\nvalidate_reserved([P|Tail]) ->\n\tcase validate_reserved1(P) of\n\t\tok -> validate_reserved(Tail);\n\t\terror -> error\n\tend.\n\n%% We always reject forward slash, backward slash and NUL as\n%% those have special meanings across the supported platforms.\n%% We could support the backward slash on some platforms but\n%% for the sake of consistency and simplicity we don't.\nvalidate_reserved1(<<>>) ->\n\tok;\nvalidate_reserved1(<<$/, _/bits>>) ->\n\terror;\nvalidate_reserved1(<<$\\\\, _/bits>>) ->\n\terror;\nvalidate_reserved1(<<0, _/bits>>) ->\n\terror;\nvalidate_reserved1(<<_, Rest/bits>>) ->\n\tvalidate_reserved1(Rest).\n\nfullpath(Path) ->\n\tfullpath(filename:split(Path), []).\nfullpath([], Acc) ->\n\tfilename:join(lists:reverse(Acc));\nfullpath([<<\".\">>|Tail], Acc) ->\n\tfullpath(Tail, Acc);\nfullpath([<<\"..\">>|Tail], Acc=[_]) ->\n\tfullpath(Tail, Acc);\nfullpath([<<\"..\">>|Tail], [_|Acc]) ->\n\tfullpath(Tail, Acc);\nfullpath([Segment|Tail], Acc) ->\n\tfullpath(Tail, [Segment|Acc]).\n\ninit_info(Req, Path, HowToAccess, Extra) ->\n\tInfo = read_file_info(Path, HowToAccess),\n\t{cowboy_rest, Req, {Path, Info, Extra}}.\n\nread_file_info(Path, direct) ->\n\tcase file:read_file_info(Path, [raw, {time, universal}]) of\n\t\t{ok, Info} -> {direct, Info};\n\t\tError      -> Error\n\tend;\nread_file_info(Path, {archive, Archive}) ->\n\tcase file:read_file_info(Archive, [raw, {time, universal}]) of\n\t\t{ok, ArchiveInfo} ->\n\t\t\t%% The Erlang application archive is fine.\n\t\t\t%% Now check if the requested file is in that\n\t\t\t%% archive. We also need the file_info to merge\n\t\t\t%% them with the archive's one.\n\t\t\tPathS = binary_to_list(Path),\n\t\t\tcase erl_prim_loader:read_file_info(PathS) of\n\t\t\t\t{ok, ContainedFileInfo} ->\n\t\t\t\t\tInfo = fix_archived_file_info(\n\t\t\t\t\t\tArchiveInfo,\n\t\t\t\t\t\tContainedFileInfo),\n\t\t\t\t\t{archive, Info};\n\t\t\t\terror ->\n\t\t\t\t\t{error, enoent}\n\t\t\tend;\n\t\tError ->\n\t\t\tError\n\tend.\n\nfix_archived_file_info(ArchiveInfo, ContainedFileInfo) ->\n\t%% We merge the archive and content #file_info because we are\n\t%% interested by the timestamps of the archive, but the type and\n\t%% size of the contained file/directory.\n\t%%\n\t%% We reset the access to 'read', because we won't rewrite the\n\t%% archive.\n\tArchiveInfo#file_info{\n\t\tsize = ContainedFileInfo#file_info.size,\n\t\ttype = ContainedFileInfo#file_info.type,\n\t\taccess = read\n\t}.\n\n-ifdef(TEST).\nfullpath_test_() ->\n\tTests = [\n\t\t{<<\"/home/cowboy\">>, <<\"/home/cowboy\">>},\n\t\t{<<\"/home/cowboy\">>, <<\"/home/cowboy/\">>},\n\t\t{<<\"/home/cowboy\">>, <<\"/home/cowboy/./\">>},\n\t\t{<<\"/home/cowboy\">>, <<\"/home/cowboy/./././././.\">>},\n\t\t{<<\"/home/cowboy\">>, <<\"/home/cowboy/abc/..\">>},\n\t\t{<<\"/home/cowboy\">>, <<\"/home/cowboy/abc/../\">>},\n\t\t{<<\"/home/cowboy\">>, <<\"/home/cowboy/abc/./../.\">>},\n\t\t{<<\"/\">>, <<\"/home/cowboy/../../../../../..\">>},\n\t\t{<<\"/etc/passwd\">>, <<\"/home/cowboy/../../etc/passwd\">>}\n\t],\n\t[{P, fun() -> R = fullpath(P) end} || {R, P} <- Tests].\n\ngood_path_check_test_() ->\n\tTests = [\n\t\t<<\"/home/cowboy/file\">>,\n\t\t<<\"/home/cowboy/file/\">>,\n\t\t<<\"/home/cowboy/./file\">>,\n\t\t<<\"/home/cowboy/././././././file\">>,\n\t\t<<\"/home/cowboy/abc/../file\">>,\n\t\t<<\"/home/cowboy/abc/../file\">>,\n\t\t<<\"/home/cowboy/abc/./.././file\">>\n\t],\n\t[{P, fun() ->\n\t\tcase fullpath(P) of\n\t\t\t<< \"/home/cowboy/\", _/bits >> -> ok\n\t\tend\n\tend} || P <- Tests].\n\nbad_path_check_test_() ->\n\tTests = [\n\t\t<<\"/home/cowboy/../../../../../../file\">>,\n\t\t<<\"/home/cowboy/../../etc/passwd\">>\n\t],\n\t[{P, fun() ->\n\t\terror = case fullpath(P) of\n\t\t\t<< \"/home/cowboy/\", _/bits >> -> ok;\n\t\t\t_ -> error\n\t\tend\n\tend} || P <- Tests].\n\ngood_path_win32_check_test_() ->\n\tTests = case os:type() of\n\t\t{unix, _} ->\n\t\t\t[];\n\t\t{win32, _} ->\n\t\t\t[\n\t\t\t\t<<\"c:/home/cowboy/file\">>,\n\t\t\t\t<<\"c:/home/cowboy/file/\">>,\n\t\t\t\t<<\"c:/home/cowboy/./file\">>,\n\t\t\t\t<<\"c:/home/cowboy/././././././file\">>,\n\t\t\t\t<<\"c:/home/cowboy/abc/../file\">>,\n\t\t\t\t<<\"c:/home/cowboy/abc/../file\">>,\n\t\t\t\t<<\"c:/home/cowboy/abc/./.././file\">>\n\t\t\t]\n\tend,\n\t[{P, fun() ->\n\t\tcase fullpath(P) of\n\t\t\t<< \"c:/home/cowboy/\", _/bits >> -> ok\n\t\tend\n\tend} || P <- Tests].\n\nbad_path_win32_check_test_() ->\n\tTests = case os:type() of\n\t\t{unix, _} ->\n\t\t\t[];\n\t\t{win32, _} ->\n\t\t\t[\n\t\t\t\t<<\"c:/home/cowboy/../../secretfile.bat\">>,\n\t\t\t\t<<\"c:/home/cowboy/c:/secretfile.bat\">>,\n\t\t\t\t<<\"c:/home/cowboy/..\\\\..\\\\secretfile.bat\">>,\n\t\t\t\t<<\"c:/home/cowboy/c:\\\\secretfile.bat\">>\n\t\t\t]\n\tend,\n\t[{P, fun() ->\n\t\terror = case fullpath(P) of\n\t\t\t<< \"c:/home/cowboy/\", _/bits >> -> ok;\n\t\t\t_ -> error\n\t\tend\n\tend} || P <- Tests].\n-endif.\n\n%% Reject requests that tried to access a file outside\n%% the target directory, or used reserved characters.\n\n-spec malformed_request(Req, State)\n\t-> {boolean(), Req, State}.\nmalformed_request(Req, State) ->\n\t{State =:= error, Req, State}.\n\n%% Directories, files that can't be accessed at all and\n%% files with no read flag are forbidden.\n\n-spec forbidden(Req, State)\n\t-> {boolean(), Req, State}\n\twhen State::state().\nforbidden(Req, State={_, {_, #file_info{type=directory}}, _}) ->\n\t{true, Req, State};\nforbidden(Req, State={_, {error, eacces}, _}) ->\n\t{true, Req, State};\nforbidden(Req, State={_, {_, #file_info{access=Access}}, _})\n\t\twhen Access =:= write; Access =:= none ->\n\t{true, Req, State};\nforbidden(Req, State) ->\n\t{false, Req, State}.\n\n%% Detect the mimetype of the file.\n\n-spec content_types_provided(Req, State)\n\t-> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, get_file}], Req, State}\n\twhen State::state().\ncontent_types_provided(Req, State={Path, _, Extra}) when is_list(Extra) ->\n\tcase lists:keyfind(mimetypes, 1, Extra) of\n\t\tfalse ->\n\t\t\t{[{cow_mimetypes:web(Path), get_file}], Req, State};\n\t\t{mimetypes, Module, Function} ->\n\t\t\t{[{Module:Function(Path), get_file}], Req, State};\n\t\t{mimetypes, Type} ->\n\t\t\t{[{Type, get_file}], Req, State}\n\tend.\n\n%% Detect the charset of the file.\n\n-spec charsets_provided(Req, State)\n\t-> {[binary()], Req, State} | no_call\n\twhen State::state().\ncharsets_provided(Req, State={Path, _, Extra}) ->\n\tcase lists:keyfind(charset, 1, Extra) of\n\t\t%% We simulate the callback not being exported.\n\t\tfalse ->\n\t\t\tno_call;\n\t\t{charset, Module, Function} ->\n\t\t\t{[Module:Function(Path)], Req, State};\n\t\t{charset, Charset} when is_binary(Charset) ->\n\t\t\t{[Charset], Req, State}\n\tend.\n\n%% Enable support for range requests.\n\n-spec ranges_provided(Req, State)\n\t-> {[{binary(), auto}], Req, State}\n\twhen State::state().\nranges_provided(Req, State) ->\n\t{[{<<\"bytes\">>, auto}], Req, State}.\n\n%% Assume the resource doesn't exist if it's not a regular file.\n\n-spec resource_exists(Req, State)\n\t-> {boolean(), Req, State}\n\twhen State::state().\nresource_exists(Req, State={_, {_, #file_info{type=regular}}, _}) ->\n\t{true, Req, State};\nresource_exists(Req, State) ->\n\t{false, Req, State}.\n\n%% Generate an etag for the file.\n\n-spec generate_etag(Req, State)\n\t-> {{strong | weak, binary() | undefined}, Req, State}\n\twhen State::state().\ngenerate_etag(Req, State={Path, {_, #file_info{size=Size, mtime=Mtime}},\n\t\tExtra}) ->\n\tcase lists:keyfind(etag, 1, Extra) of\n\t\tfalse ->\n\t\t\t{generate_default_etag(Size, Mtime), Req, State};\n\t\t{etag, Module, Function} ->\n\t\t\t{Module:Function(Path, Size, Mtime), Req, State};\n\t\t{etag, false} ->\n\t\t\t{undefined, Req, State}\n\tend.\n\ngenerate_default_etag(Size, Mtime) ->\n\t{strong, integer_to_binary(erlang:phash2({Size, Mtime}, 16#ffffffff))}.\n\n%% Return the time of last modification of the file.\n\n-spec last_modified(Req, State)\n\t-> {calendar:datetime(), Req, State}\n\twhen State::state().\nlast_modified(Req, State={_, {_, #file_info{mtime=Modified}}, _}) ->\n\t{Modified, Req, State}.\n\n%% Stream the file.\n\n-spec get_file(Req, State)\n\t-> {{sendfile, 0, non_neg_integer(), binary()} | binary(), Req, State}\n\twhen State::state().\nget_file(Req, State={Path, {direct, #file_info{size=Size}}, _}) ->\n\t{{sendfile, 0, Size, Path}, Req, State};\nget_file(Req, State={Path, {archive, _}, _}) ->\n\tPathS = binary_to_list(Path),\n\t{ok, Bin, _} = erl_prim_loader:get_file(PathS),\n\t{Bin, Req, State}.\n"
  },
  {
    "path": "src/cowboy_stream.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_stream).\n\n-type state() :: any().\n-type human_reason() :: atom().\n\n-type streamid() :: any().\n-export_type([streamid/0]).\n\n-type fin() :: fin | nofin.\n-export_type([fin/0]).\n\n%% @todo Perhaps it makes more sense to have resp_body in this module?\n\n-type resp_command()\n\t:: {response, cowboy:http_status(), cowboy:http_headers(), cowboy_req:resp_body()}.\n-export_type([resp_command/0]).\n\n-type commands() :: [{inform, cowboy:http_status(), cowboy:http_headers()}\n\t| resp_command()\n\t| {headers, cowboy:http_status(), cowboy:http_headers()}\n\t| {data, fin(), cowboy_req:resp_body()}\n\t| {trailers, cowboy:http_headers()}\n\t| {push, binary(), binary(), binary(), inet:port_number(),\n\t\tbinary(), binary(), cowboy:http_headers()}\n\t| {flow, pos_integer()}\n\t| {spawn, pid(), timeout()}\n\t| {error_response, cowboy:http_status(), cowboy:http_headers(), iodata()}\n\t| {switch_protocol, cowboy:http_headers(), module(), state()}\n\t| {internal_error, any(), human_reason()}\n\t| {set_options, map()}\n\t| {log, logger:level(), io:format(), list()}\n\t| stop].\n-export_type([commands/0]).\n\n-type reason() :: normal | switch_protocol\n\t| {internal_error, timeout | {error | exit | throw, any()}, human_reason()}\n\t| {socket_error, closed | atom(), human_reason()}\n\t%% @todo Or cow_http3:error().\n\t| {stream_error, cow_http2:error(), human_reason()}\n\t| {connection_error, cow_http2:error(), human_reason()}\n\t| {stop, cow_http2:frame() | {exit, any()}, human_reason()}.\n-export_type([reason/0]).\n\n-type partial_req() :: map(). %% @todo Take what's in cowboy_req with everything? optional.\n-export_type([partial_req/0]).\n\n-callback init(streamid(), cowboy_req:req(), cowboy:opts()) -> {commands(), state()}.\n-callback data(streamid(), fin(), binary(), State) -> {commands(), State} when State::state().\n-callback info(streamid(), any(), State) -> {commands(), State} when State::state().\n-callback terminate(streamid(), reason(), state()) -> any().\n-callback early_error(streamid(), reason(), partial_req(), Resp, cowboy:opts())\n\t-> Resp when Resp::resp_command().\n\n%% @todo To optimize the number of active timers we could have a command\n%% that enables a timeout that is called in the absence of any other call,\n%% similar to what gen_server does. However the nice thing about this is\n%% that the connection process can keep a single timer around (the same\n%% one that would be used to detect half-closed sockets) and use this\n%% timer and other events to trigger the timeout in streams at their\n%% intended time.\n%%\n%% This same timer can be used to try and send PING frames to help detect\n%% that the connection is indeed unresponsive.\n\n-export([init/3]).\n-export([data/4]).\n-export([info/3]).\n-export([terminate/3]).\n-export([early_error/5]).\n-export([make_error_log/5]).\n\n%% Note that this and other functions in this module do NOT catch\n%% exceptions. We want the exception to go all the way down to the\n%% protocol code.\n%%\n%% OK the failure scenario is not so clear. The problem is\n%% that the failure at any point in init/3 will result in the\n%% corresponding state being lost. I am unfortunately not\n%% confident we can do anything about this. If the crashing\n%% handler just created a process, we'll never know about it.\n%% Therefore at this time I choose to leave all failure handling\n%% to the protocol process.\n%%\n%% Note that a failure in init/3 will result in terminate/3\n%% NOT being called. This is because the state is not available.\n\n-spec init(streamid(), cowboy_req:req(), cowboy:opts())\n\t-> {commands(), {module(), state()} | undefined}.\ninit(StreamID, Req, Opts) ->\n\tcase maps:get(stream_handlers, Opts, [cowboy_stream_h]) of\n\t\t[] ->\n\t\t\t{[], undefined};\n\t\t[Handler|Tail] ->\n\t\t\t%% We call the next handler and remove it from the list of\n\t\t\t%% stream handlers. This means that handlers that run after\n\t\t\t%% it have no knowledge it exists. Should user require this\n\t\t\t%% knowledge they can just define a separate option that will\n\t\t\t%% be left untouched.\n\t\t\t{Commands, State} = Handler:init(StreamID, Req, Opts#{stream_handlers => Tail}),\n\t\t\t{Commands, {Handler, State}}\n\tend.\n\n-spec data(streamid(), fin(), binary(), {Handler, State} | undefined)\n\t-> {commands(), {Handler, State} | undefined}\n\twhen Handler::module(), State::state().\ndata(_, _, _, undefined) ->\n\t{[], undefined};\ndata(StreamID, IsFin, Data, {Handler, State0}) ->\n\t{Commands, State} = Handler:data(StreamID, IsFin, Data, State0),\n\t{Commands, {Handler, State}}.\n\n-spec info(streamid(), any(), {Handler, State} | undefined)\n\t-> {commands(), {Handler, State} | undefined}\n\twhen Handler::module(), State::state().\ninfo(_, _, undefined) ->\n\t{[], undefined};\ninfo(StreamID, Info, {Handler, State0}) ->\n\t{Commands, State} = Handler:info(StreamID, Info, State0),\n\t{Commands, {Handler, State}}.\n\n-spec terminate(streamid(), reason(), {module(), state()} | undefined) -> ok.\nterminate(_, _, undefined) ->\n\tok;\nterminate(StreamID, Reason, {Handler, State}) ->\n\t_ = Handler:terminate(StreamID, Reason, State),\n\tok.\n\n-spec early_error(streamid(), reason(), partial_req(), Resp, cowboy:opts())\n\t-> Resp when Resp::resp_command().\nearly_error(StreamID, Reason, PartialReq, Resp, Opts) ->\n\tcase maps:get(stream_handlers, Opts, [cowboy_stream_h]) of\n\t\t[] ->\n\t\t\tResp;\n\t\t[Handler|Tail] ->\n\t\t\t%% This is the same behavior as in init/3.\n\t\t\tHandler:early_error(StreamID, Reason,\n\t\t\t\tPartialReq, Resp, Opts#{stream_handlers => Tail})\n\tend.\n\n-spec make_error_log(init | data | info | terminate | early_error,\n\tlist(), error | exit | throw, any(), list())\n\t-> {log, error, string(), list()}.\nmake_error_log(init, [StreamID, Req, Opts], Class, Exception, Stacktrace) ->\n\t{log, error,\n\t\t\"Unhandled exception ~p:~p in cowboy_stream:init(~p, Req, Opts)~n\"\n\t\t\"Stacktrace: ~p~n\"\n\t\t\"Req: ~p~n\"\n\t\t\"Opts: ~p~n\",\n\t\t[Class, Exception, StreamID, Stacktrace, Req, Opts]};\nmake_error_log(data, [StreamID, IsFin, Data, State], Class, Exception, Stacktrace) ->\n\t{log, error,\n\t\t\"Unhandled exception ~p:~p in cowboy_stream:data(~p, ~p, Data, State)~n\"\n\t\t\"Stacktrace: ~p~n\"\n\t\t\"Data: ~p~n\"\n\t\t\"State: ~p~n\",\n\t\t[Class, Exception, StreamID, IsFin, Stacktrace, Data, State]};\nmake_error_log(info, [StreamID, Msg, State], Class, Exception, Stacktrace) ->\n\t{log, error,\n\t\t\"Unhandled exception ~p:~p in cowboy_stream:info(~p, Msg, State)~n\"\n\t\t\"Stacktrace: ~p~n\"\n\t\t\"Msg: ~p~n\"\n\t\t\"State: ~p~n\",\n\t\t[Class, Exception, StreamID, Stacktrace, Msg, State]};\nmake_error_log(terminate, [StreamID, Reason, State], Class, Exception, Stacktrace) ->\n\t{log, error,\n\t\t\"Unhandled exception ~p:~p in cowboy_stream:terminate(~p, Reason, State)~n\"\n\t\t\"Stacktrace: ~p~n\"\n\t\t\"Reason: ~p~n\"\n\t\t\"State: ~p~n\",\n\t\t[Class, Exception, StreamID, Stacktrace, Reason, State]};\nmake_error_log(early_error, [StreamID, Reason, PartialReq, Resp, Opts],\n\t\tClass, Exception, Stacktrace) ->\n\t{log, error,\n\t\t\"Unhandled exception ~p:~p in cowboy_stream:early_error(~p, Reason, PartialReq, Resp, Opts)~n\"\n\t\t\"Stacktrace: ~p~n\"\n\t\t\"Reason: ~p~n\"\n\t\t\"PartialReq: ~p~n\"\n\t\t\"Resp: ~p~n\"\n\t\t\"Opts: ~p~n\",\n\t\t[Class, Exception, StreamID, Stacktrace, Reason, PartialReq, Resp, Opts]}.\n"
  },
  {
    "path": "src/cowboy_stream_h.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_stream_h).\n-behavior(cowboy_stream).\n\n-export([init/3]).\n-export([data/4]).\n-export([info/3]).\n-export([terminate/3]).\n-export([early_error/5]).\n\n-export([request_process/3]).\n-export([resume/5]).\n\n-record(state, {\n\tnext :: any(),\n\tref = undefined :: ranch:ref(),\n\tpid = undefined :: pid(),\n\texpect = undefined :: undefined | continue,\n\tread_body_pid = undefined :: pid() | undefined,\n\tread_body_ref = undefined :: reference() | undefined,\n\tread_body_timer_ref = undefined :: reference() | undefined,\n\tread_body_length = 0 :: non_neg_integer() | infinity | auto,\n\tread_body_is_fin = nofin :: nofin | {fin, non_neg_integer()},\n\tread_body_buffer = <<>> :: binary(),\n\tbody_length = 0 :: non_neg_integer(),\n\tstream_body_pid = undefined :: pid() | undefined,\n\tstream_body_status = normal :: normal | blocking | blocked\n}).\n\n-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())\n\t-> {[{spawn, pid(), timeout()}], #state{}}.\ninit(StreamID, Req=#{ref := Ref}, Opts) ->\n\tEnv = maps:get(env, Opts, #{}),\n\tMiddlewares = maps:get(middlewares, Opts, [cowboy_router, cowboy_handler]),\n\tShutdown = maps:get(shutdown_timeout, Opts, 5000),\n\tPid = proc_lib:spawn_link(?MODULE, request_process, [Req, Env, Middlewares]),\n\tExpect = expect(Req),\n\t{Commands, Next} = cowboy_stream:init(StreamID, Req, Opts),\n\t{[{spawn, Pid, Shutdown}|Commands],\n\t\t#state{next=Next, ref=Ref, pid=Pid, expect=Expect}}.\n\n%% Ignore the expect header in HTTP/1.0.\nexpect(#{version := 'HTTP/1.0'}) ->\n\tundefined;\nexpect(Req) ->\n\ttry cowboy_req:parse_header(<<\"expect\">>, Req) of\n\t\tExpect ->\n\t\t\tExpect\n\tcatch _:_ ->\n\t\tundefined\n\tend.\n\n%% If we receive data and stream is waiting for data:\n%%   If we accumulated enough data or IsFin=fin, send it.\n%%   If we are in auto mode, send it and update flow control.\n%%   If not, buffer it.\n%% If not, buffer it.\n%%\n%% We always reset the expect field when we receive data,\n%% since the client started sending the request body before\n%% we could send a 100 continue response.\n\n-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)\n\t-> {cowboy_stream:commands(), State} when State::#state{}.\n%% Stream isn't waiting for data.\ndata(StreamID, IsFin, Data, State=#state{\n\t\tread_body_ref=undefined, read_body_buffer=Buffer, body_length=BodyLen}) ->\n\tdo_data(StreamID, IsFin, Data, [], State#state{\n\t\texpect=undefined,\n\t\tread_body_is_fin=IsFin,\n\t\tread_body_buffer= << Buffer/binary, Data/binary >>,\n\t\tbody_length=BodyLen + byte_size(Data)\n\t});\n%% Stream is waiting for data using auto mode.\n%%\n%% There is no buffering done in auto mode.\ndata(StreamID, IsFin, Data, State=#state{read_body_pid=Pid, read_body_ref=Ref,\n\t\tread_body_length=auto, body_length=BodyLen}) ->\n\tsend_request_body(Pid, Ref, IsFin, BodyLen, Data),\n\tdo_data(StreamID, IsFin, Data, [{flow, byte_size(Data)}], State#state{\n\t\tread_body_ref=undefined,\n\t\t%% @todo This is wrong, it's missing byte_size(Data).\n\t\tbody_length=BodyLen\n\t});\n%% Stream is waiting for data but we didn't receive enough to send yet.\ndata(StreamID, IsFin=nofin, Data, State=#state{\n\t\tread_body_length=ReadLen, read_body_buffer=Buffer, body_length=BodyLen})\n\t\twhen byte_size(Data) + byte_size(Buffer) < ReadLen ->\n\tdo_data(StreamID, IsFin, Data, [], State#state{\n\t\texpect=undefined,\n\t\tread_body_buffer= << Buffer/binary, Data/binary >>,\n\t\tbody_length=BodyLen + byte_size(Data)\n\t});\n%% Stream is waiting for data and we received enough to send.\ndata(StreamID, IsFin, Data, State=#state{read_body_pid=Pid, read_body_ref=Ref,\n\t\tread_body_timer_ref=TRef, read_body_buffer=Buffer, body_length=BodyLen0}) ->\n\tBodyLen = BodyLen0 + byte_size(Data),\n\tok = erlang:cancel_timer(TRef, [{async, true}, {info, false}]),\n\tsend_request_body(Pid, Ref, IsFin, BodyLen, <<Buffer/binary, Data/binary>>),\n\tdo_data(StreamID, IsFin, Data, [], State#state{\n\t\texpect=undefined,\n\t\tread_body_ref=undefined,\n\t\tread_body_timer_ref=undefined,\n\t\tread_body_buffer= <<>>,\n\t\tbody_length=BodyLen\n\t}).\n\ndo_data(StreamID, IsFin, Data, Commands1, State=#state{next=Next0}) ->\n\t{Commands2, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),\n\t{Commands1 ++ Commands2, State#state{next=Next}}.\n\n-spec info(cowboy_stream:streamid(), any(), State)\n\t-> {cowboy_stream:commands(), State} when State::#state{}.\ninfo(StreamID, Info={'EXIT', Pid, normal}, State=#state{pid=Pid}) ->\n\tdo_info(StreamID, Info, [stop], State);\ninfo(StreamID, Info={'EXIT', Pid, {{request_error, Reason, _HumanReadable}, _}},\n\t\tState=#state{pid=Pid}) ->\n\tStatus = case Reason of\n\t\ttimeout -> 408;\n\t\tpayload_too_large -> 413;\n\t\t_ -> 400\n\tend,\n\t%% @todo Headers? Details in body? Log the crash? More stuff in debug only?\n\tdo_info(StreamID, Info, [\n\t\t{error_response, Status, #{<<\"content-length\">> => <<\"0\">>}, <<>>},\n\t\tstop\n\t], State);\ninfo(StreamID, Exit={'EXIT', Pid, Reason}, State=#state{ref=Ref, pid=Pid}) ->\n\tCommands0 = [{internal_error, Exit, 'Stream process crashed.'}],\n\tCommands = case Reason of\n\t\tnormal -> Commands0;\n\t\tshutdown -> Commands0;\n\t\t{shutdown, _} -> Commands0;\n\t\t_ -> [{log, error,\n\t\t\t\t\"Ranch listener ~p, connection process ~p, stream ~p \"\n\t\t\t\t\"had its request process ~p exit with reason ~0p~n\",\n\t\t\t\t[Ref, self(), StreamID, Pid, Reason]}\n\t\t\t|Commands0]\n\tend,\n\t%% @todo We are trying to send a 500 response before resetting\n\t%%       the stream. But due to the way the RESET_STREAM frame\n\t%%       works in QUIC the data may be lost. The problem is\n\t%%       known and a draft RFC exists at\n\t%%       https://www.ietf.org/id/draft-ietf-quic-reliable-stream-reset-03.html\n\tdo_info(StreamID, Exit, [\n\t\t{error_response, 500, #{<<\"content-length\">> => <<\"0\">>}, <<>>}\n\t|Commands], State);\n%% Request body, auto mode, no body buffered.\ninfo(StreamID, Info={read_body, Pid, Ref, auto, infinity}, State=#state{read_body_buffer= <<>>}) ->\n\tdo_info(StreamID, Info, [], State#state{\n\t\tread_body_pid=Pid,\n\t\tread_body_ref=Ref,\n\t\tread_body_length=auto\n\t});\n%% Request body, auto mode, body buffered or complete.\ninfo(StreamID, Info={read_body, Pid, Ref, auto, infinity}, State=#state{\n\t\tread_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen}) ->\n\tsend_request_body(Pid, Ref, IsFin, BodyLen, Buffer),\n\tdo_info(StreamID, Info, [{flow, byte_size(Buffer)}],\n\t\tState#state{read_body_buffer= <<>>});\n%% Request body, body buffered large enough or complete.\n%%\n%% We do not send a 100 continue response if the client\n%% already started sending the body.\ninfo(StreamID, Info={read_body, Pid, Ref, Length, _}, State=#state{\n\t\tread_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen})\n\t\twhen IsFin =:= fin; byte_size(Buffer) >= Length ->\n\tsend_request_body(Pid, Ref, IsFin, BodyLen, Buffer),\n\tdo_info(StreamID, Info, [], State#state{read_body_buffer= <<>>});\n%% Request body, not enough to send yet.\ninfo(StreamID, Info={read_body, Pid, Ref, Length, Period}, State=#state{expect=Expect}) ->\n\tCommands = case Expect of\n\t\tcontinue -> [{inform, 100, #{}}, {flow, Length}];\n\t\tundefined -> [{flow, Length}]\n\tend,\n\tTRef = erlang:send_after(Period, self(), {{self(), StreamID}, {read_body_timeout, Ref}}),\n\tdo_info(StreamID, Info, Commands, State#state{\n\t\tread_body_pid=Pid,\n\t\tread_body_ref=Ref,\n\t\tread_body_timer_ref=TRef,\n\t\tread_body_length=Length\n\t});\n%% Request body reading timeout; send what we got.\ninfo(StreamID, Info={read_body_timeout, Ref}, State=#state{read_body_pid=Pid, read_body_ref=Ref,\n\t\tread_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen}) ->\n\tsend_request_body(Pid, Ref, IsFin, BodyLen, Buffer),\n\tdo_info(StreamID, Info, [], State#state{\n\t\tread_body_ref=undefined,\n\t\tread_body_timer_ref=undefined,\n\t\tread_body_buffer= <<>>\n\t});\ninfo(StreamID, Info={read_body_timeout, _}, State) ->\n\tdo_info(StreamID, Info, [], State);\n%% Response.\n%%\n%% We reset the expect field when a 100 continue response\n%% is sent or when any final response is sent.\ninfo(StreamID, Inform={inform, Status, _}, State0) ->\n\tState = case cow_http:status_to_integer(Status) of\n\t\t100 -> State0#state{expect=undefined};\n\t\t_ -> State0\n\tend,\n\tdo_info(StreamID, Inform, [Inform], State);\ninfo(StreamID, Response={response, _, _, _}, State) ->\n\tdo_info(StreamID, Response, [Response], State#state{expect=undefined});\ninfo(StreamID, Headers={headers, _, _}, State) ->\n\tdo_info(StreamID, Headers, [Headers], State#state{expect=undefined});\n%% Sending data involves the data message, the stream_buffer_full alarm\n%% and the connection_buffer_full alarm. We stop sending acks when an alarm is on.\n%%\n%% We only apply backpressure when the message includes a pid. Otherwise\n%% it is a message from Cowboy, or the user circumventing the backpressure.\n%%\n%% We currently do not support sending data from multiple processes concurrently.\ninfo(StreamID, Data={data, _, _}, State) ->\n\tdo_info(StreamID, Data, [Data], State);\ninfo(StreamID, Data0={data, Pid, _, _}, State0=#state{stream_body_status=Status}) ->\n\tState = case Status of\n\t\tnormal ->\n\t\t\tPid ! {data_ack, self()},\n\t\t\tState0;\n\t\tblocking ->\n\t\t\tState0#state{stream_body_pid=Pid, stream_body_status=blocked};\n\t\tblocked ->\n\t\t\tState0\n\tend,\n\tData = erlang:delete_element(2, Data0),\n\tdo_info(StreamID, Data, [Data], State);\ninfo(StreamID, Alarm={alarm, Name, on}, State0=#state{stream_body_status=Status})\n\t\twhen Name =:= connection_buffer_full; Name =:= stream_buffer_full ->\n\tState = case Status of\n\t\tnormal -> State0#state{stream_body_status=blocking};\n\t\t_ -> State0\n\tend,\n\tdo_info(StreamID, Alarm, [], State);\ninfo(StreamID, Alarm={alarm, Name, off}, State=#state{stream_body_pid=Pid, stream_body_status=Status})\n\t\twhen Name =:= connection_buffer_full; Name =:= stream_buffer_full ->\n\t_ = case Status of\n\t\tnormal -> ok;\n\t\tblocking -> ok;\n\t\tblocked -> Pid ! {data_ack, self()}\n\tend,\n\tdo_info(StreamID, Alarm, [], State#state{stream_body_pid=undefined, stream_body_status=normal});\ninfo(StreamID, Trailers={trailers, _}, State) ->\n\tdo_info(StreamID, Trailers, [Trailers], State);\ninfo(StreamID, Push={push, _, _, _, _, _, _, _}, State) ->\n\tdo_info(StreamID, Push, [Push], State);\ninfo(StreamID, SwitchProtocol={switch_protocol, _, _, _}, State) ->\n\tdo_info(StreamID, SwitchProtocol, [SwitchProtocol], State#state{expect=undefined});\n%% Convert the set_options message to a command.\ninfo(StreamID, SetOptions={set_options, _}, State) ->\n\tdo_info(StreamID, SetOptions, [SetOptions], State);\n%% Unknown message, either stray or meant for a handler down the line.\ninfo(StreamID, Info, State) ->\n\tdo_info(StreamID, Info, [], State).\n\ndo_info(StreamID, Info, Commands1, State0=#state{next=Next0}) ->\n\t{Commands2, Next} = cowboy_stream:info(StreamID, Info, Next0),\n\t{Commands1 ++ Commands2, State0#state{next=Next}}.\n\n-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> ok.\nterminate(StreamID, Reason, #state{next=Next}) ->\n\tcowboy_stream:terminate(StreamID, Reason, Next).\n\n-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),\n\tcowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp\n\twhen Resp::cowboy_stream:resp_command().\nearly_error(StreamID, Reason, PartialReq, Resp, Opts) ->\n\tcowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).\n\nsend_request_body(Pid, Ref, nofin, _, Data) ->\n\tPid ! {request_body, Ref, nofin, Data},\n\tok;\nsend_request_body(Pid, Ref, fin, BodyLen, Data) ->\n\tPid ! {request_body, Ref, fin, BodyLen, Data},\n\tok.\n\n%% Request process.\n\n%% We add the stacktrace to exit exceptions here in order\n%% to simplify the debugging of errors. The proc_lib library\n%% already adds the stacktrace to other types of exceptions.\n-spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok.\nrequest_process(Req, Env, Middlewares) ->\n\ttry\n\t\texecute(Req, Env, Middlewares)\n\tcatch\n\t\texit:Reason={shutdown, _}:Stacktrace ->\n\t\t\terlang:raise(exit, Reason, Stacktrace);\n\t\texit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown ->\n\t\t\terlang:raise(exit, {Reason, Stacktrace}, Stacktrace)\n\tend.\n\nexecute(_, _, []) ->\n\tok;\nexecute(Req, Env, [Middleware|Tail]) ->\n\tcase Middleware:execute(Req, Env) of\n\t\t{ok, Req2, Env2} ->\n\t\t\texecute(Req2, Env2, Tail);\n\t\t{suspend, Module, Function, Args} ->\n\t\t\tproc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]);\n\t\t{stop, _Req2} ->\n\t\t\tok\n\tend.\n\n-spec resume(cowboy_middleware:env(), [module()], module(), atom(), [any()]) -> ok.\nresume(Env, Tail, Module, Function, Args) ->\n\tcase apply(Module, Function, Args) of\n\t\t{ok, Req2, Env2} ->\n\t\t\texecute(Req2, Env2, Tail);\n\t\t{suspend, Module2, Function2, Args2} ->\n\t\t\tproc_lib:hibernate(?MODULE, resume, [Env, Tail, Module2, Function2, Args2]);\n\t\t{stop, _Req2} ->\n\t\t\tok\n\tend.\n"
  },
  {
    "path": "src/cowboy_sub_protocol.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%% Copyright (c) James Fish <james@fishcakez.com>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_sub_protocol).\n\n-callback upgrade(Req, Env, module(), any())\n\t-> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {stop, Req}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\n\n-callback upgrade(Req, Env, module(), any(), any())\n\t-> {ok, Req, Env} | {suspend, module(), atom(), [any()]} | {stop, Req}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\n"
  },
  {
    "path": "src/cowboy_sup.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_sup).\n-behaviour(supervisor).\n\n-export([start_link/0]).\n-export([init/1]).\n\n-spec start_link() -> {ok, pid()}.\nstart_link() ->\n\tsupervisor:start_link({local, ?MODULE}, ?MODULE, []).\n\n-spec init([])\n\t-> {ok, {{supervisor:strategy(), 10, 10}, [supervisor:child_spec()]}}.\ninit([]) ->\n\tProcs = [{cowboy_clock, {cowboy_clock, start_link, []},\n\t\tpermanent, 5000, worker, [cowboy_clock]}],\n\t{ok, {{one_for_one, 10, 10}, Procs}}.\n"
  },
  {
    "path": "src/cowboy_tls.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_tls).\n-behavior(ranch_protocol).\n\n-export([start_link/3]).\n-export([start_link/4]).\n-export([connection_process/4]).\n\n%% Ranch 1.\n-spec start_link(ranch:ref(), ssl:sslsocket(), module(), cowboy:opts()) -> {ok, pid()}.\nstart_link(Ref, _Socket, Transport, Opts) ->\n\tstart_link(Ref, Transport, Opts).\n\n%% Ranch 2.\n-spec start_link(ranch:ref(), module(), cowboy:opts()) -> {ok, pid()}.\nstart_link(Ref, Transport, Opts) ->\n\tPid = proc_lib:spawn_link(?MODULE, connection_process,\n\t\t[self(), Ref, Transport, Opts]),\n\t{ok, Pid}.\n\n-spec connection_process(pid(), ranch:ref(), module(), cowboy:opts()) -> ok.\nconnection_process(Parent, Ref, Transport, Opts) ->\n\tProxyInfo = get_proxy_info(Ref, Opts),\n\t{ok, Socket} = ranch:handshake(Ref),\n\tcase ssl:negotiated_protocol(Socket) of\n\t\t{ok, <<\"h2\">>} ->\n\t\t\tinit(Parent, Ref, Socket, Transport, ProxyInfo, Opts, cowboy_http2);\n\t\t_ -> %% http/1.1 or no protocol negotiated.\n\t\t\tProtocol = case maps:get(alpn_default_protocol, Opts, http) of\n\t\t\t\thttp -> cowboy_http;\n\t\t\t\thttp2 -> cowboy_http2\n\t\t\tend,\n\t\t\tinit(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol)\n\tend.\n\ninit(Parent, Ref, Socket, Transport, ProxyInfo, Opts, Protocol) ->\n\t_ = case maps:get(connection_type, Opts, supervisor) of\n\t\tworker -> ok;\n\t\tsupervisor -> process_flag(trap_exit, true)\n\tend,\n\tProtocol:init(Parent, Ref, Socket, Transport, ProxyInfo, Opts).\n\nget_proxy_info(Ref, #{proxy_header := true}) ->\n\tcase ranch:recv_proxy_header(Ref, 1000) of\n\t\t{ok, ProxyInfo} -> ProxyInfo;\n\t\t{error, closed} -> exit({shutdown, closed})\n\tend;\nget_proxy_info(_, _) ->\n\tundefined.\n"
  },
  {
    "path": "src/cowboy_tracer_h.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_tracer_h).\n-behavior(cowboy_stream).\n\n-export([init/3]).\n-export([data/4]).\n-export([info/3]).\n-export([terminate/3]).\n-export([early_error/5]).\n\n-export([set_trace_patterns/0]).\n\n-export([tracer_process/3]).\n-export([system_continue/3]).\n-export([system_terminate/4]).\n-export([system_code_change/4]).\n\n-type match_predicate()\n\t:: fun((cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts()) -> boolean()).\n\n-type tracer_match_specs() :: [match_predicate()\n\t| {method, binary()}\n\t| {host, binary()}\n\t| {path, binary()}\n\t| {path_start, binary()}\n\t| {header, binary()}\n\t| {header, binary(), binary()}\n\t| {peer_ip, inet:ip_address()}\n].\n-export_type([tracer_match_specs/0]).\n\n-type tracer_callback() :: fun((init | terminate | tuple(), any()) -> any()).\n-export_type([tracer_callback/0]).\n\n-spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())\n\t-> {cowboy_stream:commands(), any()}.\ninit(StreamID, Req, Opts) ->\n\tinit_tracer(StreamID, Req, Opts),\n\tcowboy_stream:init(StreamID, Req, Opts).\n\n-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)\n\t-> {cowboy_stream:commands(), State} when State::any().\ndata(StreamID, IsFin, Data, Next) ->\n\tcowboy_stream:data(StreamID, IsFin, Data, Next).\n\n-spec info(cowboy_stream:streamid(), any(), State)\n\t-> {cowboy_stream:commands(), State} when State::any().\ninfo(StreamID, Info, Next) ->\n\tcowboy_stream:info(StreamID, Info, Next).\n\n-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), any()) -> any().\nterminate(StreamID, Reason, Next) ->\n\tcowboy_stream:terminate(StreamID, Reason, Next).\n\n-spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),\n\tcowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp\n\twhen Resp::cowboy_stream:resp_command().\nearly_error(StreamID, Reason, PartialReq, Resp, Opts) ->\n\tcowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).\n\n%% API.\n\n%% These trace patterns are most likely not suitable for production.\n-spec set_trace_patterns() -> ok.\nset_trace_patterns() ->\n\terlang:trace_pattern({'_', '_', '_'}, [{'_', [], [{return_trace}]}], [local]),\n\terlang:trace_pattern(on_load, [{'_', [], [{return_trace}]}], [local]),\n\tok.\n\n%% Internal.\n\ninit_tracer(StreamID, Req, Opts=#{tracer_match_specs := List, tracer_callback := _}) ->\n\tcase match(List, StreamID, Req, Opts) of\n\t\tfalse ->\n\t\t\tok;\n\t\ttrue ->\n\t\t\tstart_tracer(StreamID, Req, Opts)\n\tend;\n%% When the options tracer_match_specs or tracer_callback\n%% are not provided we do not enable tracing.\ninit_tracer(_, _, _) ->\n\tok.\n\nmatch([], _, _, _) ->\n\ttrue;\nmatch([Predicate|Tail], StreamID, Req, Opts) when is_function(Predicate) ->\n\tcase Predicate(StreamID, Req, Opts) of\n\t\ttrue -> match(Tail, StreamID, Req, Opts);\n\t\tfalse -> false\n\tend;\nmatch([{method, Value}|Tail], StreamID, Req=#{method := Value}, Opts) ->\n\tmatch(Tail, StreamID, Req, Opts);\nmatch([{host, Value}|Tail], StreamID, Req=#{host := Value}, Opts) ->\n\tmatch(Tail, StreamID, Req, Opts);\nmatch([{path, Value}|Tail], StreamID, Req=#{path := Value}, Opts) ->\n\tmatch(Tail, StreamID, Req, Opts);\nmatch([{path_start, PathStart}|Tail], StreamID, Req=#{path := Path}, Opts) ->\n\tLen = byte_size(PathStart),\n\tcase Path of\n\t\t<<PathStart:Len/binary, _/bits>> -> match(Tail, StreamID, Req, Opts);\n\t\t_ -> false\n\tend;\nmatch([{header, Name}|Tail], StreamID, Req=#{headers := Headers}, Opts) ->\n\tcase Headers of\n\t\t#{Name := _} -> match(Tail, StreamID, Req, Opts);\n\t\t_ -> false\n\tend;\nmatch([{header, Name, Value}|Tail], StreamID, Req=#{headers := Headers}, Opts) ->\n\tcase Headers of\n\t\t#{Name := Value} -> match(Tail, StreamID, Req, Opts);\n\t\t_ -> false\n\tend;\nmatch([{peer_ip, IP}|Tail], StreamID, Req=#{peer := {IP, _}}, Opts) ->\n\tmatch(Tail, StreamID, Req, Opts);\nmatch(_, _, _, _) ->\n\tfalse.\n\n%% We only start the tracer if one wasn't started before.\nstart_tracer(StreamID, Req, Opts) ->\n\tcase erlang:trace_info(self(), tracer) of\n\t\t{tracer, []} ->\n\t\t\tTracerPid = proc_lib:spawn_link(?MODULE, tracer_process, [StreamID, Req, Opts]),\n\t\t\t%% The default flags are probably not suitable for production.\n\t\t\tFlags = maps:get(tracer_flags, Opts, [\n\t\t\t\tsend, 'receive', call, return_to,\n\t\t\t\tprocs, ports, monotonic_timestamp,\n\t\t\t\t%% The set_on_spawn flag is necessary to catch events\n\t\t\t\t%% from request processes.\n\t\t\t\tset_on_spawn\n\t\t\t]),\n\t\t\terlang:trace(self(), true, [{tracer, TracerPid}|Flags]),\n\t\t\tok;\n\t\t_ ->\n\t\t\tok\n\tend.\n\n%% Tracer process.\n\n-spec tracer_process(_, _, _) -> no_return().\ntracer_process(StreamID, Req=#{pid := Parent}, Opts=#{tracer_callback := Fun}) ->\n\t%% This is necessary because otherwise the tracer could stop\n\t%% before it has finished processing the events in its queue.\n\tprocess_flag(trap_exit, true),\n\tState = Fun(init, {StreamID, Req, Opts}),\n\ttracer_loop(Parent, Opts, State).\n\ntracer_loop(Parent, Opts=#{tracer_callback := Fun}, State0) ->\n\treceive\n\t\tMsg when element(1, Msg) =:= trace; element(1, Msg) =:= trace_ts ->\n\t\t\tState = Fun(Msg, State0),\n\t\t\ttracer_loop(Parent, Opts, State);\n\t\t{'EXIT', Parent, Reason} ->\n\t\t\ttracer_terminate(Reason, Opts, State0);\n\t\t{system, From, Request} ->\n\t\t\tsys:handle_system_msg(Request, From, Parent, ?MODULE, [], {Opts, State0});\n\t\tMsg ->\n\t\t\tcowboy:log(warning, \"~p: Tracer process received stray message ~9999p~n\",\n\t\t\t\t[?MODULE, Msg], Opts),\n\t\t\ttracer_loop(Parent, Opts, State0)\n\tend.\n\n-spec tracer_terminate(_, _, _) -> no_return().\ntracer_terminate(Reason, #{tracer_callback := Fun}, State) ->\n\t_ = Fun(terminate, State),\n\texit(Reason).\n\n%% System callbacks.\n\n-spec system_continue(pid(), _, {cowboy:opts(), any()}) -> no_return().\nsystem_continue(Parent, _, {Opts, State}) ->\n\ttracer_loop(Parent, Opts, State).\n\n-spec system_terminate(any(), _, _, _) -> no_return().\nsystem_terminate(Reason, _, _, {Opts, State}) ->\n\ttracer_terminate(Reason, Opts, State).\n\n-spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::any().\nsystem_code_change(Misc, _, _, _) ->\n\t{ok, Misc}.\n"
  },
  {
    "path": "src/cowboy_websocket.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% Cowboy supports versions 7 through 17 of the Websocket drafts.\n%% It also supports RFC6455, the proposed standard for Websocket.\n-module(cowboy_websocket).\n-behaviour(cowboy_sub_protocol).\n\n-export([is_upgrade_request/1]).\n-export([upgrade/4]).\n-export([upgrade/5]).\n-export([takeover/7]).\n-export([loop/3]).\n\n-export([system_continue/3]).\n-export([system_terminate/4]).\n-export([system_code_change/4]).\n\n-type commands() :: [cow_ws:frame()\n\t| {active, boolean()}\n\t| {deflate, boolean()}\n\t| {set_options, map()}\n\t| {shutdown_reason, any()}\n].\n-export_type([commands/0]).\n\n-type call_result(State) :: {commands(), State} | {commands(), State, hibernate}.\n\n-type deprecated_call_result(State) :: {ok, State}\n\t| {ok, State, hibernate}\n\t| {reply, cow_ws:frame() | [cow_ws:frame()], State}\n\t| {reply, cow_ws:frame() | [cow_ws:frame()], State, hibernate}\n\t| {stop, State}.\n\n-type terminate_reason() :: normal | stop | timeout\n\t| remote | {remote, cow_ws:close_code(), binary()}\n\t| {error, badencoding | badframe | closed | atom()}\n\t| {crash, error | exit | throw, any()}.\n\n-callback init(Req, any())\n\t-> {ok | module(), Req, any()}\n\t| {module(), Req, any(), any()}\n\twhen Req::cowboy_req:req().\n\n-callback websocket_init(State)\n\t-> call_result(State) | deprecated_call_result(State) when State::any().\n-optional_callbacks([websocket_init/1]).\n\n-callback websocket_handle(ping | pong | {text | binary | ping | pong, binary()}, State)\n\t-> call_result(State) | deprecated_call_result(State) when State::any().\n-callback websocket_info(any(), State)\n\t-> call_result(State) | deprecated_call_result(State) when State::any().\n\n-callback terminate(any(), cowboy_req:req(), any()) -> ok.\n-optional_callbacks([terminate/3]).\n\n-type opts() :: #{\n\tactive_n => pos_integer(),\n\tcompress => boolean(),\n\tdata_delivery => stream_handlers | relay,\n\tdata_delivery_flow => pos_integer(),\n\tdeflate_opts => cow_ws:deflate_opts(),\n\tdynamic_buffer => false | {pos_integer(), pos_integer()},\n\tdynamic_buffer_initial_average => non_neg_integer(),\n\tdynamic_buffer_initial_size => pos_integer(),\n\tidle_timeout => timeout(),\n\tmax_frame_size => non_neg_integer() | infinity,\n\treq_filter => fun((cowboy_req:req()) -> map()),\n\tvalidate_utf8 => boolean()\n}.\n-export_type([opts/0]).\n\n%% We don't want to reset the idle timeout too often,\n%% so we don't reset it on data. Instead we reset the\n%% number of ticks we have observed. We divide the\n%% timeout value by a value and that value becomes\n%% the number of ticks at which point we can drop\n%% the connection. This value is the number of ticks.\n-define(IDLE_TIMEOUT_TICKS, 10).\n\n-record(state, {\n\tparent :: undefined | pid(),\n\tref :: ranch:ref(),\n\tsocket = undefined :: inet:socket() | {pid(), cowboy_stream:streamid()} | undefined,\n\ttransport :: module() | {data_delivery, stream_handlers | relay},\n\topts = #{} :: opts(),\n\tactive = true :: boolean(),\n\thandler :: module(),\n\tkey = undefined :: undefined | binary(),\n\ttimeout_ref = undefined :: undefined | reference(),\n\ttimeout_num = 0 :: 0..?IDLE_TIMEOUT_TICKS,\n\tmessages = undefined :: undefined | {atom(), atom(), atom()}\n\t\t| {atom(), atom(), atom(), atom()},\n\n\t%% Dynamic buffer moving average and current buffer size.\n\tdynamic_buffer_size = false :: pos_integer() | false,\n\tdynamic_buffer_moving_average = 0.0 :: float(),\n\n\thibernate = false :: boolean(),\n\tfrag_state = undefined :: cow_ws:frag_state(),\n\tfrag_buffer = <<>> :: binary(),\n\tutf8_state :: cow_ws:utf8_state(),\n\tdeflate = true :: boolean(),\n\textensions = #{} :: map(),\n\treq = #{} :: map(),\n\tshutdown_reason = normal :: any()\n}).\n\n%% Because the HTTP/1.1 and HTTP/2 handshakes are so different,\n%% this function is necessary to figure out whether a request\n%% is trying to upgrade to the Websocket protocol.\n\n-spec is_upgrade_request(cowboy_req:req()) -> boolean().\nis_upgrade_request(#{version := Version, method := <<\"CONNECT\">>, protocol := Protocol})\n\t\twhen Version =:= 'HTTP/2'; Version =:= 'HTTP/3' ->\n\t<<\"websocket\">> =:= cowboy_bstr:to_lower(Protocol);\nis_upgrade_request(Req=#{version := 'HTTP/1.1', method := <<\"GET\">>}) ->\n\tConnTokens = cowboy_req:parse_header(<<\"connection\">>, Req, []),\n\tcase lists:member(<<\"upgrade\">>, ConnTokens) of\n\t\tfalse ->\n\t\t\tfalse;\n\t\ttrue ->\n\t\t\tUpgradeTokens = cowboy_req:parse_header(<<\"upgrade\">>, Req),\n\t\t\tlists:member(<<\"websocket\">>, UpgradeTokens)\n\tend;\nis_upgrade_request(_) ->\n\tfalse.\n\n%% Stream process.\n\n-spec upgrade(Req, Env, module(), any())\n\t-> {ok, Req, Env}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\nupgrade(Req, Env, Handler, HandlerState) ->\n\tupgrade(Req, Env, Handler, HandlerState, #{}).\n\n-spec upgrade(Req, Env, module(), any(), opts())\n\t-> {ok, Req, Env}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\n%% @todo Immediately crash if a response has already been sent.\nupgrade(Req0=#{version := Version}, Env, Handler, HandlerState, Opts) ->\n\tFilteredReq = case maps:get(req_filter, Opts, undefined) of\n\t\tundefined -> maps:with([method, version, scheme, host, port, path, qs, peer, streamid], Req0);\n\t\tFilterFun -> FilterFun(Req0)\n\tend,\n\tUtf8State = case maps:get(validate_utf8, Opts, true) of\n\t\ttrue -> 0;\n\t\tfalse -> undefined\n\tend,\n\tState0 = #state{opts=Opts, handler=Handler, utf8_state=Utf8State, req=FilteredReq},\n\ttry websocket_upgrade(State0, Req0) of\n\t\t{ok, State, Req} ->\n\t\t\twebsocket_handshake(State, Req, HandlerState, Env);\n\t\t%% The status code 426 is specific to HTTP/1.1 connections.\n\t\t{error, upgrade_required} when Version =:= 'HTTP/1.1' ->\n\t\t\t{ok, cowboy_req:reply(426, #{\n\t\t\t\t<<\"connection\">> => <<\"upgrade\">>,\n\t\t\t\t<<\"upgrade\">> => <<\"websocket\">>\n\t\t\t}, Req0), Env};\n\t\t%% Use 501 Not Implemented for HTTP/2 and HTTP/3 as recommended\n\t\t%% by RFC9220 3 (WebSockets Upgrade over HTTP/3).\n\t\t{error, upgrade_required} ->\n\t\t\t{ok, cowboy_req:reply(501, Req0), Env}\n\tcatch _:_ ->\n\t\t%% @todo Probably log something here?\n\t\t%% @todo Test that we can have 2 /ws 400 status code in a row on the same connection.\n\t\t{ok, cowboy_req:reply(400, Req0), Env}\n\tend.\n\nwebsocket_upgrade(State, Req=#{version := Version}) ->\n\tcase is_upgrade_request(Req) of\n\t\tfalse ->\n\t\t\t{error, upgrade_required};\n\t\ttrue when Version =:= 'HTTP/1.1' ->\n\t\t\tKey = cowboy_req:header(<<\"sec-websocket-key\">>, Req),\n\t\t\tfalse = Key =:= undefined,\n\t\t\twebsocket_version(State#state{key=Key}, Req);\n\t\ttrue ->\n\t\t\twebsocket_version(State, Req)\n\tend.\n\nwebsocket_version(State, Req) ->\n\tWsVersion = cowboy_req:parse_header(<<\"sec-websocket-version\">>, Req),\n\tcase WsVersion of\n\t\t7 -> ok;\n\t\t8 -> ok;\n\t\t13 -> ok\n\tend,\n\twebsocket_extensions(State, Req#{websocket_version => WsVersion}).\n\nwebsocket_extensions(State=#state{opts=Opts}, Req) ->\n\t%% @todo We want different options for this. For example\n\t%% * compress everything auto\n\t%% * compress only text auto\n\t%% * compress only binary auto\n\t%% * compress nothing auto (but still enabled it)\n\t%% * disable compression\n\tCompress = maps:get(compress, Opts, false),\n\tcase {Compress, cowboy_req:parse_header(<<\"sec-websocket-extensions\">>, Req)} of\n\t\t{true, Extensions} when Extensions =/= undefined ->\n\t\t\twebsocket_extensions(State, Req, Extensions, []);\n\t\t_ ->\n\t\t\t{ok, State, Req}\n\tend.\n\nwebsocket_extensions(State, Req, [], []) ->\n\t{ok, State, Req};\nwebsocket_extensions(State, Req, [], [<<\", \">>|RespHeader]) ->\n\t{ok, State, cowboy_req:set_resp_header(<<\"sec-websocket-extensions\">>, lists:reverse(RespHeader), Req)};\n%% For HTTP/2 we ARE on the controlling process and do NOT want to update the owner.\nwebsocket_extensions(State=#state{opts=Opts, extensions=Extensions},\n\t\tReq=#{pid := Pid, version := Version},\n\t\t[{<<\"permessage-deflate\">>, Params}|Tail], RespHeader) ->\n\tDeflateOpts0 = maps:get(deflate_opts, Opts, #{}),\n\tDeflateOpts = case Version of\n\t\t'HTTP/1.1' -> DeflateOpts0#{owner => Pid};\n\t\t_ -> DeflateOpts0\n\tend,\n\ttry cow_ws:negotiate_permessage_deflate(Params, Extensions, DeflateOpts) of\n\t\t{ok, RespExt, Extensions2} ->\n\t\t\twebsocket_extensions(State#state{extensions=Extensions2},\n\t\t\t\tReq, Tail, [<<\", \">>, RespExt|RespHeader]);\n\t\tignore ->\n\t\t\twebsocket_extensions(State, Req, Tail, RespHeader)\n\tcatch exit:{error, incompatible_zlib_version, _} ->\n\t\twebsocket_extensions(State, Req, Tail, RespHeader)\n\tend;\nwebsocket_extensions(State=#state{opts=Opts, extensions=Extensions},\n\t\tReq=#{pid := Pid, version := Version},\n\t\t[{<<\"x-webkit-deflate-frame\">>, Params}|Tail], RespHeader) ->\n\tDeflateOpts0 = maps:get(deflate_opts, Opts, #{}),\n\tDeflateOpts = case Version of\n\t\t'HTTP/1.1' -> DeflateOpts0#{owner => Pid};\n\t\t_ -> DeflateOpts0\n\tend,\n\ttry cow_ws:negotiate_x_webkit_deflate_frame(Params, Extensions, DeflateOpts) of\n\t\t{ok, RespExt, Extensions2} ->\n\t\t\twebsocket_extensions(State#state{extensions=Extensions2},\n\t\t\t\tReq, Tail, [<<\", \">>, RespExt|RespHeader]);\n\t\tignore ->\n\t\t\twebsocket_extensions(State, Req, Tail, RespHeader)\n\tcatch exit:{error, incompatible_zlib_version, _} ->\n\t\twebsocket_extensions(State, Req, Tail, RespHeader)\n\tend;\nwebsocket_extensions(State, Req, [_|Tail], RespHeader) ->\n\twebsocket_extensions(State, Req, Tail, RespHeader).\n\n-spec websocket_handshake(#state{}, Req, any(), Env)\n\t-> {ok, Req, Env}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\nwebsocket_handshake(State=#state{key=Key},\n\t\tReq=#{version := 'HTTP/1.1', pid := Pid, streamid := StreamID},\n\t\tHandlerState, Env) ->\n\tChallenge = base64:encode(crypto:hash(sha,\n\t\t<< Key/binary, \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\" >>)),\n\t%% @todo We don't want date and server headers.\n\tHeaders = cowboy_req:response_headers(#{\n\t\t<<\"connection\">> => <<\"Upgrade\">>,\n\t\t<<\"upgrade\">> => <<\"websocket\">>,\n\t\t<<\"sec-websocket-accept\">> => Challenge\n\t}, Req),\n\tPid ! {{Pid, StreamID}, {switch_protocol, Headers, ?MODULE, {State, HandlerState}}},\n\t{ok, Req, Env};\n%% For HTTP/2 we do not let the process die, we instead keep it\n%% for the Websocket stream. This is because in HTTP/2 we only\n%% have a stream, it doesn't take over the whole connection.\n%%\n%% There are two methods of delivering data to the Websocket session:\n%% - 'stream_handlers' is the default and makes the data go\n%%   through stream handlers just like when reading a request body;\n%% - 'relay' is a new method where data is sent as a message as\n%%   soon as it is received from the socket in a DATA frame.\nwebsocket_handshake(State=#state{opts=Opts},\n\t\tReq=#{ref := Ref, pid := Pid, streamid := StreamID},\n\t\tHandlerState, _Env) ->\n\t%% @todo We don't want date and server headers.\n\tHeaders = cowboy_req:response_headers(#{}, Req),\n\tDataDelivery = maps:get(data_delivery, Opts, stream_handlers),\n\tModState = #{\n\t\tdata_delivery => DataDelivery,\n\t\t%% For relay data_delivery. The flow is a hint and may\n\t\t%% not be used by the underlying protocol.\n\t\tdata_delivery_pid => self(),\n\t\tdata_delivery_flow => maps:get(data_delivery_flow, Opts, 131072)\n\t},\n\tPid ! {{Pid, StreamID}, {switch_protocol, Headers, ?MODULE, ModState}},\n\ttakeover(Pid, Ref, {Pid, StreamID}, {data_delivery, DataDelivery}, #{}, <<>>,\n\t\t{State, HandlerState}).\n\n%% Connection process.\n\n-record(ps_header, {\n\tbuffer = <<>> :: binary()\n}).\n\n-record(ps_payload, {\n\ttype :: cow_ws:frame_type(),\n\tlen :: non_neg_integer(),\n\tmask_key :: cow_ws:mask_key(),\n\trsv :: cow_ws:rsv(),\n\tclose_code = undefined :: undefined | cow_ws:close_code(),\n\tunmasked = <<>> :: binary(),\n\tunmasked_len = 0 :: non_neg_integer(),\n\tbuffer = <<>> :: binary()\n}).\n\n-type parse_state() :: #ps_header{} | #ps_payload{}.\n\n-spec takeover(pid(), ranch:ref(), inet:socket() | {pid(), cowboy_stream:streamid()},\n\tmodule() | {data_delivery, stream_handlers | relay}, any(), binary(),\n\t{#state{}, any()}) -> no_return().\ntakeover(Parent, Ref, Socket, Transport, Opts, Buffer,\n\t\t{State0=#state{opts=WsOpts, handler=Handler, req=Req}, HandlerState}) ->\n\tcase Req of\n\t\t#{version := 'HTTP/3'} -> ok;\n\t\t%% @todo We should have an option to disable this behavior.\n\t\t_ -> ranch:remove_connection(Ref)\n\tend,\n\tMessages = case Transport of\n\t\t{data_delivery, _} -> undefined;\n\t\t_ -> Transport:messages()\n\tend,\n\tState = set_idle_timeout(State0#state{parent=Parent,\n\t\tref=Ref, socket=Socket, transport=Transport,\n\t\topts=WsOpts#{dynamic_buffer => maps:get(dynamic_buffer, Opts, false)},\n\t\tkey=undefined, messages=Messages,\n\t\t%% Dynamic buffer only applies to HTTP/1.1 Websocket.\n\t\tdynamic_buffer_size=init_dynamic_buffer_size(Opts),\n\t\tdynamic_buffer_moving_average=maps:get(dynamic_buffer_initial_average, Opts, 0.0)}, 0),\n\t%% We call parse_header/3 immediately because there might be\n\t%% some data in the buffer that was sent along with the handshake.\n\t%% While it is not allowed by the protocol to send frames immediately,\n\t%% we still want to process that data if any.\n\tcase erlang:function_exported(Handler, websocket_init, 1) of\n\t\ttrue -> handler_call(State, HandlerState, #ps_header{buffer=Buffer},\n\t\t\twebsocket_init, undefined, fun after_init/3);\n\t\tfalse -> after_init(State, HandlerState, #ps_header{buffer=Buffer})\n\tend.\n\n-include(\"cowboy_dynamic_buffer.hrl\").\n\n%% @todo Implement early socket error detection.\nmaybe_socket_error(_, _) ->\n\tok.\n\nafter_init(State=#state{active=true}, HandlerState, ParseState) ->\n\t%% Enable active,N for HTTP/1.1, and auto read_body for HTTP/2.\n\t%% We must do this only after calling websocket_init/1 (if any)\n\t%% to give the handler a chance to disable active mode immediately.\n\tsetopts_active(State),\n\tmaybe_read_body(State),\n\tparse_header(State, HandlerState, ParseState);\nafter_init(State, HandlerState, ParseState) ->\n\tparse_header(State, HandlerState, ParseState).\n\n%% We have two ways of reading the body for Websocket. For HTTP/1.1\n%% we have full control of the socket and can therefore use active,N.\n%% For HTTP/2 we are just a stream, and are instead using read_body\n%% (automatic mode). Technically HTTP/2 will only go passive after\n%% receiving the next data message, while HTTP/1.1 goes passive\n%% immediately but there might still be data to be processed in\n%% the message queue.\n\nsetopts_active(#state{transport={data_delivery, _}}) ->\n\tok;\nsetopts_active(#state{socket=Socket, transport=Transport, opts=Opts}) ->\n\tN = maps:get(active_n, Opts, 1),\n\tTransport:setopts(Socket, [{active, N}]).\n\nmaybe_read_body(#state{transport={data_delivery, stream_handlers},\n\t\tsocket=Stream={Pid, _}, active=true}) ->\n\t%% @todo Keep Ref around.\n\tReadBodyRef = make_ref(),\n\tPid ! {Stream, {read_body, self(), ReadBodyRef, auto, infinity}},\n\tok;\nmaybe_read_body(_) ->\n\tok.\n\nactive(State=#state{transport={data_delivery, relay},\n\t\tsocket=Stream={Pid, _}}) ->\n\tPid ! {'$cowboy_relay_command', Stream, active},\n\tState#state{active=true};\nactive(State0) ->\n\tState = State0#state{active=true},\n\tsetopts_active(State),\n\tmaybe_read_body(State),\n\tState.\n\npassive(State=#state{transport={data_delivery, stream_handlers}}) ->\n\t%% Unfortunately we cannot currently cancel read_body.\n\t%% But that's OK, we will just stop reading the body\n\t%% after the next message.\n\tState#state{active=false};\npassive(State=#state{transport={data_delivery, relay},\n\t\tsocket=Stream={Pid, _}}) ->\n\tPid ! {'$cowboy_relay_command', Stream, passive},\n\tState#state{active=false};\npassive(State=#state{socket=Socket, transport=Transport, messages=Messages}) ->\n\tTransport:setopts(Socket, [{active, false}]),\n\tflush_passive(Socket, Messages),\n\tState#state{active=false}.\n\nflush_passive(Socket, Messages) ->\n\treceive\n\t\t{Passive, Socket} when Passive =:= element(4, Messages);\n\t\t\t\t%% Hardcoded for compatibility with Ranch 1.x.\n\t\t\t\tPassive =:= tcp_passive; Passive =:= ssl_passive ->\n\t\t\tflush_passive(Socket, Messages)\n\tafter 0 ->\n\t\tok\n\tend.\n\nbefore_loop(State=#state{hibernate=true}, HandlerState, ParseState) ->\n\tproc_lib:hibernate(?MODULE, loop,\n\t\t[State#state{hibernate=false}, HandlerState, ParseState]);\nbefore_loop(State, HandlerState, ParseState) ->\n\tloop(State, HandlerState, ParseState).\n\n-spec set_idle_timeout(#state{}, 0..?IDLE_TIMEOUT_TICKS) -> #state{}.\n\n%% @todo Do we really need this for HTTP/2?\nset_idle_timeout(State=#state{opts=Opts, timeout_ref=PrevRef}, TimeoutNum) ->\n\t%% Most of the time we don't need to cancel the timer since it\n\t%% will have triggered already. But this call is harmless so\n\t%% it is kept to simplify the code as we do need to cancel when\n\t%% options are changed dynamically.\n\t_ = case PrevRef of\n\t\tundefined -> ignore;\n\t\tPrevRef -> erlang:cancel_timer(PrevRef, [{async, true}, {info, false}])\n\tend,\n\tcase maps:get(idle_timeout, Opts, 60000) of\n\t\tinfinity ->\n\t\t\tState#state{timeout_ref=undefined, timeout_num=TimeoutNum};\n\t\tTimeout ->\n\t\t\tTRef = erlang:start_timer(Timeout div ?IDLE_TIMEOUT_TICKS, self(), ?MODULE),\n\t\t\tState#state{timeout_ref=TRef, timeout_num=TimeoutNum}\n\tend.\n\n-define(reset_idle_timeout(State), State#state{timeout_num=0}).\n\ntick_idle_timeout(State=#state{timeout_num=?IDLE_TIMEOUT_TICKS}, HandlerState, _) ->\n\twebsocket_close(State, HandlerState, timeout);\ntick_idle_timeout(State=#state{timeout_num=TimeoutNum}, HandlerState, ParseState) ->\n\tbefore_loop(set_idle_timeout(State, TimeoutNum + 1), HandlerState, ParseState).\n\n-spec loop(#state{}, any(), parse_state()) -> no_return().\nloop(State=#state{parent=Parent, socket=Socket, messages=Messages,\n\t\ttimeout_ref=TRef}, HandlerState, ParseState) ->\n\treceive\n\t\t%% Socket messages. (HTTP/1.1)\n\t\t{OK, Socket, Data} when OK =:= element(1, Messages) ->\n\t\t\tState1 = maybe_resize_buffer(State, Data),\n\t\t\tparse(?reset_idle_timeout(State1), HandlerState, ParseState, Data);\n\t\t{Closed, Socket} when Closed =:= element(2, Messages) ->\n\t\t\tterminate(State, HandlerState, {error, closed});\n\t\t{Error, Socket, Reason} when Error =:= element(3, Messages) ->\n\t\t\tterminate(State, HandlerState, {error, Reason});\n\t\t{Passive, Socket} when Passive =:= element(4, Messages);\n\t\t\t\t%% Hardcoded for compatibility with Ranch 1.x.\n\t\t\t\tPassive =:= tcp_passive; Passive =:= ssl_passive ->\n\t\t\tsetopts_active(State),\n\t\t\tloop(State, HandlerState, ParseState);\n\t\t%% Body reading messages. (HTTP/2)\n\t\t{request_body, _Ref, nofin, Data} ->\n\t\t\tmaybe_read_body(State),\n\t\t\tparse(?reset_idle_timeout(State), HandlerState, ParseState, Data);\n\t\t%% @todo We need to handle this case as if it was an {error, closed}\n\t\t%% but not before we finish processing frames. We probably should have\n\t\t%% a check in before_loop to let us stop looping if a flag is set.\n\t\t{request_body, _Ref, fin, _, Data} ->\n\t\t\tmaybe_read_body(State),\n\t\t\tparse(?reset_idle_timeout(State), HandlerState, ParseState, Data);\n\t\t%% @todo It would be better to check StreamID.\n\t\t%% @todo We must ensure that IsFin=fin is handled like a socket close?\n\t\t{'$cowboy_relay_data', {Pid, _StreamID}, _IsFin, Data} when Pid =:= Parent ->\n\t\t\tparse(?reset_idle_timeout(State), HandlerState, ParseState, Data);\n\t\t%% Timeouts.\n\t\t{timeout, TRef, ?MODULE} ->\n\t\t\ttick_idle_timeout(State, HandlerState, ParseState);\n\t\t{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->\n\t\t\tbefore_loop(State, HandlerState, ParseState);\n\t\t%% System messages.\n\t\t{'EXIT', Parent, Reason} ->\n\t\t\t%% The terminate reason will differ with HTTP/1.1\n\t\t\t%% since we don't have direct access to the socket.\n\t\t\t%% @todo Perhaps we can make cowboy_children:terminate\n\t\t\t%% receive the shutdown Reason and send {shutdown, Reason}\n\t\t\t%% instead of just 'shutdown' in this scenario.\n\t\t\tterminate(State, HandlerState, Reason);\n\t\t{system, From, Request} ->\n\t\t\tsys:handle_system_msg(Request, From, Parent, ?MODULE, [],\n\t\t\t\t{State, HandlerState, ParseState});\n\t\t%% Calls from supervisor module.\n\t\t{'$gen_call', From, Call} ->\n\t\t\tcowboy_children:handle_supervisor_call(Call, From, [], ?MODULE),\n\t\t\tbefore_loop(State, HandlerState, ParseState);\n\t\tMessage ->\n\t\t\thandler_call(State, HandlerState, ParseState,\n\t\t\t\twebsocket_info, Message, fun before_loop/3)\n\tend.\n\nparse(State, HandlerState, PS=#ps_header{buffer=Buffer}, Data) ->\n\tparse_header(State, HandlerState, PS#ps_header{\n\t\tbuffer= <<Buffer/binary, Data/binary>>});\nparse(State, HandlerState, PS=#ps_payload{buffer=Buffer}, Data) ->\n\tparse_payload(State, HandlerState, PS#ps_payload{buffer= <<>>},\n\t\t<<Buffer/binary, Data/binary>>).\n\nparse_header(State=#state{opts=Opts, frag_state=FragState, extensions=Extensions},\n\t\tHandlerState, ParseState=#ps_header{buffer=Data}) ->\n\tMaxFrameSize = maps:get(max_frame_size, Opts, infinity),\n\tcase cow_ws:parse_header(Data, Extensions, FragState) of\n\t\t%% All frames sent from the client to the server are masked.\n\t\t{_, _, _, _, undefined, _} ->\n\t\t\twebsocket_close(State, HandlerState, {error, badframe});\n\t\t{_, _, _, Len, _, _} when Len > MaxFrameSize ->\n\t\t\twebsocket_close(State, HandlerState, {error, badsize});\n\t\t{Type, FragState2, Rsv, Len, MaskKey, Rest} ->\n\t\t\tparse_payload(State#state{frag_state=FragState2}, HandlerState,\n\t\t\t\t#ps_payload{type=Type, len=Len, mask_key=MaskKey, rsv=Rsv}, Rest);\n\t\tmore ->\n\t\t\tbefore_loop(State, HandlerState, ParseState);\n\t\terror ->\n\t\t\twebsocket_close(State, HandlerState, {error, badframe})\n\tend.\n\nparse_payload(State=#state{opts=Opts, frag_state=FragState, utf8_state=Incomplete, extensions=Extensions},\n\t\tHandlerState, ParseState=#ps_payload{\n\t\t\ttype=Type, len=Len, mask_key=MaskKey, rsv=Rsv,\n\t\t\tunmasked=Unmasked, unmasked_len=UnmaskedLen}, Data) ->\n\tMaxFrameSize = case maps:get(max_frame_size, Opts, infinity) of\n\t\tinfinity -> infinity;\n\t\tMaxFrameSize0 -> MaxFrameSize0 - UnmaskedLen\n\tend,\n\tcase cow_ws:parse_payload(Data, MaskKey, Incomplete, UnmaskedLen,\n\t\t\tType, Len, FragState, Extensions#{max_inflate_size => MaxFrameSize}, Rsv) of\n\t\t{ok, CloseCode, Payload, Utf8State, Rest} ->\n\t\t\tdispatch_frame(State#state{utf8_state=Utf8State}, HandlerState,\n\t\t\t\tParseState#ps_payload{unmasked= <<Unmasked/binary, Payload/binary>>,\n\t\t\t\t\tclose_code=CloseCode}, Rest);\n\t\t{ok, Payload, Utf8State, Rest} ->\n\t\t\tdispatch_frame(State#state{utf8_state=Utf8State}, HandlerState,\n\t\t\t\tParseState#ps_payload{unmasked= <<Unmasked/binary, Payload/binary>>},\n\t\t\t\tRest);\n\t\t{more, CloseCode, Payload, Utf8State} ->\n\t\t\tbefore_loop(State#state{utf8_state=Utf8State}, HandlerState,\n\t\t\t\tParseState#ps_payload{len=Len - byte_size(Data), close_code=CloseCode,\n\t\t\t\t\tunmasked= <<Unmasked/binary, Payload/binary>>,\n\t\t\t\t\tunmasked_len=UnmaskedLen + byte_size(Data)});\n\t\t{more, Payload, Utf8State} ->\n\t\t\tbefore_loop(State#state{utf8_state=Utf8State}, HandlerState,\n\t\t\t\tParseState#ps_payload{len=Len - byte_size(Data),\n\t\t\t\t\tunmasked= <<Unmasked/binary, Payload/binary>>,\n\t\t\t\t\tunmasked_len=UnmaskedLen + byte_size(Data)});\n\t\tError = {error, _Reason} ->\n\t\t\twebsocket_close(State, HandlerState, Error)\n\tend.\n\ndispatch_frame(State=#state{opts=Opts, frag_state=FragState, frag_buffer=SoFar}, HandlerState,\n\t\t#ps_payload{type=Type0, unmasked=Payload0, close_code=CloseCode0}, RemainingData) ->\n\tMaxFrameSize = maps:get(max_frame_size, Opts, infinity),\n\tcase cow_ws:make_frame(Type0, Payload0, CloseCode0, FragState) of\n\t\t%% @todo Allow receiving fragments.\n\t\t{fragment, _, _, Payload} when byte_size(Payload) + byte_size(SoFar) > MaxFrameSize ->\n\t\t\twebsocket_close(State, HandlerState, {error, badsize});\n\t\t{fragment, nofin, _, Payload} ->\n\t\t\tparse_header(State#state{frag_buffer= << SoFar/binary, Payload/binary >>},\n\t\t\t\tHandlerState, #ps_header{buffer=RemainingData});\n\t\t{fragment, fin, Type, Payload} ->\n\t\t\thandler_call(State#state{frag_state=undefined, frag_buffer= <<>>}, HandlerState,\n\t\t\t\t#ps_header{buffer=RemainingData},\n\t\t\t\twebsocket_handle, {Type, << SoFar/binary, Payload/binary >>},\n\t\t\t\tfun parse_header/3);\n\t\tclose ->\n\t\t\twebsocket_close(State, HandlerState, remote);\n\t\t{close, CloseCode, Payload} ->\n\t\t\twebsocket_close(State, HandlerState, {remote, CloseCode, Payload});\n\t\tFrame = ping ->\n\t\t\ttransport_send(State, nofin, frame(pong, State)),\n\t\t\thandler_call(State, HandlerState,\n\t\t\t\t#ps_header{buffer=RemainingData},\n\t\t\t\twebsocket_handle, Frame, fun parse_header/3);\n\t\tFrame = {ping, Payload} ->\n\t\t\ttransport_send(State, nofin, frame({pong, Payload}, State)),\n\t\t\thandler_call(State, HandlerState,\n\t\t\t\t#ps_header{buffer=RemainingData},\n\t\t\t\twebsocket_handle, Frame, fun parse_header/3);\n\t\tFrame ->\n\t\t\thandler_call(State, HandlerState,\n\t\t\t\t#ps_header{buffer=RemainingData},\n\t\t\t\twebsocket_handle, Frame, fun parse_header/3)\n\tend.\n\nhandler_call(State=#state{handler=Handler}, HandlerState,\n\t\tParseState, Callback, Message, NextState) ->\n\ttry case Callback of\n\t\twebsocket_init -> Handler:websocket_init(HandlerState);\n\t\t_ -> Handler:Callback(Message, HandlerState)\n\tend of\n\t\t{Commands, HandlerState2} when is_list(Commands) ->\n\t\t\thandler_call_result(State,\n\t\t\t\tHandlerState2, ParseState, NextState, Commands);\n\t\t{Commands, HandlerState2, hibernate} when is_list(Commands) ->\n\t\t\thandler_call_result(State#state{hibernate=true},\n\t\t\t\tHandlerState2, ParseState, NextState, Commands);\n\t\t%% The following call results are deprecated.\n\t\t{ok, HandlerState2} ->\n\t\t\tNextState(State, HandlerState2, ParseState);\n\t\t{ok, HandlerState2, hibernate} ->\n\t\t\tNextState(State#state{hibernate=true}, HandlerState2, ParseState);\n\t\t{reply, Payload, HandlerState2} ->\n\t\t\tcase websocket_send(Payload, State) of\n\t\t\t\tok ->\n\t\t\t\t\tNextState(State, HandlerState2, ParseState);\n\t\t\t\tstop ->\n\t\t\t\t\tterminate(State, HandlerState2, stop);\n\t\t\t\tError = {error, _} ->\n\t\t\t\t\tterminate(State, HandlerState2, Error)\n\t\t\tend;\n\t\t{reply, Payload, HandlerState2, hibernate} ->\n\t\t\tcase websocket_send(Payload, State) of\n\t\t\t\tok ->\n\t\t\t\t\tNextState(State#state{hibernate=true},\n\t\t\t\t\t\tHandlerState2, ParseState);\n\t\t\t\tstop ->\n\t\t\t\t\tterminate(State, HandlerState2, stop);\n\t\t\t\tError = {error, _} ->\n\t\t\t\t\tterminate(State, HandlerState2, Error)\n\t\t\tend;\n\t\t{stop, HandlerState2} ->\n\t\t\twebsocket_close(State, HandlerState2, stop)\n\tcatch Class:Reason:Stacktrace ->\n\t\twebsocket_send_close(State, {crash, Class, Reason}),\n\t\thandler_terminate(State, HandlerState, {crash, Class, Reason}),\n\t\terlang:raise(Class, Reason, Stacktrace)\n\tend.\n\n-spec handler_call_result(#state{}, any(), parse_state(), fun(), commands()) -> no_return().\nhandler_call_result(State0, HandlerState, ParseState, NextState, Commands) ->\n\tcase commands(Commands, State0, []) of\n\t\t{ok, State} ->\n\t\t\tNextState(State, HandlerState, ParseState);\n\t\t{stop, State} ->\n\t\t\tterminate(State, HandlerState, stop);\n\t\t{Error = {error, _}, State} ->\n\t\t\tterminate(State, HandlerState, Error)\n\tend.\n\ncommands([], State, []) ->\n\t{ok, State};\ncommands([], State, Data) ->\n\tResult = transport_send(State, nofin, lists:reverse(Data)),\n\t{Result, State};\ncommands([{active, Active}|Tail], State0=#state{active=Active0}, Data) when is_boolean(Active) ->\n\tState = if\n\t\tActive, not Active0 ->\n\t\t\tactive(State0);\n\t\tActive0, not Active ->\n\t\t\tpassive(State0);\n\t\ttrue ->\n\t\t\tState0\n\tend,\n\tcommands(Tail, State#state{active=Active}, Data);\ncommands([{deflate, Deflate}|Tail], State, Data) when is_boolean(Deflate) ->\n\tcommands(Tail, State#state{deflate=Deflate}, Data);\ncommands([{set_options, SetOpts}|Tail], State0, Data) ->\n\tState = maps:fold(fun\n\t\t(idle_timeout, IdleTimeout, StateF=#state{opts=Opts}) ->\n\t\t\t%% We reset the number of ticks when changing the idle_timeout option.\n\t\t\tset_idle_timeout(StateF#state{opts=Opts#{idle_timeout => IdleTimeout}}, 0);\n\t\t(max_frame_size, MaxFrameSize, StateF=#state{opts=Opts}) ->\n\t\t\tStateF#state{opts=Opts#{max_frame_size => MaxFrameSize}};\n\t\t(_, _, StateF) ->\n\t\t\tStateF\n\tend, State0, SetOpts),\n\tcommands(Tail, State, Data);\ncommands([{shutdown_reason, ShutdownReason}|Tail], State, Data) ->\n\tcommands(Tail, State#state{shutdown_reason=ShutdownReason}, Data);\ncommands([Frame|Tail], State, Data0) ->\n\tData = [frame(Frame, State)|Data0],\n\tcase is_close_frame(Frame) of\n\t\ttrue ->\n\t\t\t_ = transport_send(State, fin, lists:reverse(Data)),\n\t\t\t{stop, State};\n\t\tfalse ->\n\t\t\tcommands(Tail, State, Data)\n\tend.\n\ntransport_send(#state{transport={data_delivery, stream_handlers},\n\t\tsocket=Stream={Pid, _}}, IsFin, Data) ->\n\tPid ! {Stream, {data, IsFin, Data}},\n\tok;\ntransport_send(#state{transport={data_delivery, relay},\n\t\tsocket=Stream={Pid, _}}, IsFin, Data) ->\n\tPid ! {'$cowboy_relay_command', Stream, {data, IsFin, Data}},\n\tok;\ntransport_send(#state{socket=Socket, transport=Transport}, _, Data) ->\n\tTransport:send(Socket, Data).\n\n-spec websocket_send(cow_ws:frame(), #state{}) -> ok | stop | {error, atom()}.\nwebsocket_send(Frames, State) when is_list(Frames) ->\n\twebsocket_send_many(Frames, State, []);\nwebsocket_send(Frame, State) ->\n\tData = frame(Frame, State),\n\tcase is_close_frame(Frame) of\n\t\ttrue ->\n\t\t\t_ = transport_send(State, fin, Data),\n\t\t\tstop;\n\t\tfalse ->\n\t\t\ttransport_send(State, nofin, Data)\n\tend.\n\nwebsocket_send_many([], State, Acc) ->\n\ttransport_send(State, nofin, lists:reverse(Acc));\nwebsocket_send_many([Frame|Tail], State, Acc0) ->\n\tAcc = [frame(Frame, State)|Acc0],\n\tcase is_close_frame(Frame) of\n\t\ttrue ->\n\t\t\t_ = transport_send(State, fin, lists:reverse(Acc)),\n\t\t\tstop;\n\t\tfalse ->\n\t\t\twebsocket_send_many(Tail, State, Acc)\n\tend.\n\nis_close_frame(close) -> true;\nis_close_frame({close, _}) -> true;\nis_close_frame({close, _, _}) -> true;\nis_close_frame(_) -> false.\n\n-spec websocket_close(#state{}, any(), terminate_reason()) -> no_return().\nwebsocket_close(State, HandlerState, Reason) ->\n\twebsocket_send_close(State, Reason),\n\tterminate(State, HandlerState, Reason).\n\nwebsocket_send_close(State, Reason) ->\n\t_ = case Reason of\n\t\tNormal when Normal =:= stop; Normal =:= timeout ->\n\t\t\ttransport_send(State, fin, frame({close, 1000, <<>>}, State));\n\t\t{error, badframe} ->\n\t\t\ttransport_send(State, fin, frame({close, 1002, <<>>}, State));\n\t\t{error, badencoding} ->\n\t\t\ttransport_send(State, fin, frame({close, 1007, <<>>}, State));\n\t\t{error, badsize} ->\n\t\t\ttransport_send(State, fin, frame({close, 1009, <<>>}, State));\n\t\t{crash, _, _} ->\n\t\t\ttransport_send(State, fin, frame({close, 1011, <<>>}, State));\n\t\tremote ->\n\t\t\ttransport_send(State, fin, frame(close, State));\n\t\t{remote, Code, _} ->\n\t\t\ttransport_send(State, fin, frame({close, Code, <<>>}, State))\n\tend,\n\tok.\n\n%% Don't compress frames while deflate is disabled.\nframe(Frame, #state{deflate=false, extensions=Extensions}) ->\n\tcow_ws:frame(Frame, Extensions#{deflate => false});\nframe(Frame, #state{extensions=Extensions}) ->\n\tcow_ws:frame(Frame, Extensions).\n\n-spec terminate(#state{}, any(), terminate_reason()) -> no_return().\nterminate(State=#state{shutdown_reason=Shutdown}, HandlerState, Reason) ->\n\thandler_terminate(State, HandlerState, Reason),\n\tcase Shutdown of\n\t\tnormal -> exit(normal);\n\t\t_ -> exit({shutdown, Shutdown})\n\tend.\n\nhandler_terminate(#state{handler=Handler, req=Req}, HandlerState, Reason) ->\n\tcowboy_handler:terminate(Reason, Req, HandlerState, Handler).\n\n%% System callbacks.\n\n-spec system_continue(_, _, {#state{}, any(), parse_state()}) -> no_return().\nsystem_continue(_, _, {State, HandlerState, ParseState}) ->\n\tloop(State, HandlerState, ParseState).\n\n-spec system_terminate(any(), _, _, {#state{}, any(), parse_state()}) -> no_return().\nsystem_terminate(Reason, _, _, {State, HandlerState, _}) ->\n\t%% @todo We should exit gracefully, if possible.\n\tterminate(State, HandlerState, Reason).\n\n-spec system_code_change(Misc, _, _, _)\n\t-> {ok, Misc} when Misc::{#state{}, any(), parse_state()}.\nsystem_code_change(Misc, _, _, _) ->\n\t{ok, Misc}.\n"
  },
  {
    "path": "src/cowboy_webtransport.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% @todo To enable WebTransport the following options need to be set:\n%%\n%% QUIC:\n%%  - max_datagram_frame_size > 0\n%%\n%% HTTP/3:\n%%  - SETTINGS_H3_DATAGRAM = 1\n%%  - SETTINGS_ENABLE_CONNECT_PROTOCOL = 1\n%%  - SETTINGS_WT_MAX_SESSIONS >= 1\n\n%% Cowboy supports versions 07 through 13 of the WebTransport drafts.\n%% Cowboy also has some compatibility with version 02.\n%%\n%% WebTransport CONNECT requests go through cowboy_stream as normal\n%% and then an upgrade/switch_protocol is issued (just like Websocket).\n%% After that point none of the events go through cowboy_stream except\n%% the final terminate event. The request process becomes the process\n%% handling all events in the WebTransport session.\n%%\n%% WebTransport sessions can be ended via a command, via a crash or\n%% exit, via the closing of the connection (client or server inititated),\n%% via the client ending the session (mirroring the command) or via\n%% the client terminating the CONNECT stream.\n-module(cowboy_webtransport).\n\n-export([upgrade/4]).\n-export([upgrade/5]).\n\n%% cowboy_stream.\n-export([info/3]).\n-export([terminate/3]).\n\n-type stream_type() :: unidi | bidi.\n-type open_stream_ref() :: any().\n\n-type event() ::\n\t{stream_open, cow_http3:stream_id(), stream_type()} |\n\t{opened_stream_id, open_stream_ref(), cow_http3:stream_id()} |\n\t{stream_data, cow_http3:stream_id(), cow_http:fin(), binary()} |\n\t{datagram, binary()} |\n\tclose_initiated.\n\n-type commands() :: [\n\t{open_stream, open_stream_ref(), stream_type(), iodata()} |\n\t{close_stream, cow_http3:stream_id(), cow_http3:wt_app_error_code()} |\n\t{send, cow_http3:stream_id() | datagram, iodata()} |\n\tinitiate_close |\n\tclose |\n\t{close, cow_http3:wt_app_error_code()} |\n\t{close, cow_http3:wt_app_error_code(), iodata()}\n].\n-export_type([commands/0]).\n\n-type call_result(State) :: {commands(), State} | {commands(), State, hibernate}.\n\n-callback init(Req, any())\n\t-> {ok | module(), Req, any()}\n\t| {module(), Req, any(), any()}\n\twhen Req::cowboy_req:req().\n\n-callback webtransport_init(State)\n\t-> call_result(State) when State::any().\n-optional_callbacks([webtransport_init/1]).\n\n-callback webtransport_handle(event(), State)\n\t-> call_result(State) when State::any().\n-optional_callbacks([webtransport_handle/2]).\n\n-callback webtransport_info(any(), State)\n\t-> call_result(State) when State::any().\n-optional_callbacks([webtransport_info/2]).\n\n-callback terminate(any(), cowboy_req:req(), any()) -> ok.\n-optional_callbacks([terminate/3]).\n\n-type opts() :: #{\n\treq_filter => fun((cowboy_req:req()) -> map())\n}.\n-export_type([opts/0]).\n\n-record(state, {\n\tid :: cow_http3:stream_id(),\n\tparent :: pid(),\n\topts = #{} :: opts(),\n\thandler :: module(),\n\thibernate = false :: boolean(),\n\treq = #{} :: map()\n}).\n\n%% This function mirrors a similar function for Websocket.\n\n-spec is_upgrade_request(cowboy_req:req()) -> boolean().\n\nis_upgrade_request(#{version := Version, method := <<\"CONNECT\">>, protocol := Protocol})\n\t\twhen Version =:= 'HTTP/3' ->\n\t%% @todo scheme MUST BE \"https\"\n\t<<\"webtransport\">> =:= cowboy_bstr:to_lower(Protocol);\n\nis_upgrade_request(_) ->\n\tfalse.\n\n%% Stream process.\n\n-spec upgrade(Req, Env, module(), any())\n\t-> {ok, Req, Env}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\n\nupgrade(Req, Env, Handler, HandlerState) ->\n\tupgrade(Req, Env, Handler, HandlerState, #{}).\n\n-spec upgrade(Req, Env, module(), any(), opts())\n\t-> {ok, Req, Env}\n\twhen Req::cowboy_req:req(), Env::cowboy_middleware:env().\n\n%% @todo Immediately crash if a response has already been sent.\nupgrade(Req=#{version := 'HTTP/3', pid := Pid, streamid := StreamID}, Env, Handler, HandlerState, Opts) ->\n\tFilteredReq = case maps:get(req_filter, Opts, undefined) of\n\t\tundefined -> maps:with([method, version, scheme, host, port, path, qs, peer], Req);\n\t\tFilterFun -> FilterFun(Req)\n\tend,\n\tState = #state{id=StreamID, parent=Pid, opts=Opts, handler=Handler, req=FilteredReq},\n\t%% @todo Must ensure the relevant settings are enabled (QUIC and H3).\n\t%% Either we check them BEFORE, or we check them when the handler\n\t%% is OK to initiate a webtransport session. Probably need to\n\t%% check them BEFORE as we need to become (takeover) the webtransport process\n\t%% after we are done with the upgrade. Maybe in cow_http3_machine but\n\t%% it doesn't have QUIC settings currently (max_datagram_size).\n\tcase is_upgrade_request(Req) of\n\t\ttrue ->\n\t\t\tHeaders = cowboy_req:response_headers(#{}, Req),\n\t\t\tPid ! {{Pid, StreamID}, {switch_protocol, Headers, ?MODULE,\n\t\t\t\t#{session_pid => self()}}},\n\t\t\twebtransport_init(State, HandlerState);\n\t\t%% Use 501 Not Implemented to mirror the recommendation in\n\t\t%% by RFC9220 3 (WebSockets Upgrade over HTTP/3).\n\t\tfalse ->\n\t\t\t%% @todo I don't think terminate will be called.\n\t\t\t{ok, cowboy_req:reply(501, Req), Env}\n\tend.\n\nwebtransport_init(State=#state{handler=Handler}, HandlerState) ->\n\tcase erlang:function_exported(Handler, webtransport_init, 1) of\n\t\ttrue -> handler_call(State, HandlerState, webtransport_init, undefined);\n\t\tfalse -> before_loop(State, HandlerState)\n\tend.\n\nbefore_loop(State=#state{hibernate=true}, HandlerState) ->\n\tproc_lib:hibernate(?MODULE, loop, [State#state{hibernate=false}, HandlerState]);\nbefore_loop(State, HandlerState) ->\n\tloop(State, HandlerState).\n\n-spec loop(#state{}, any()) -> no_return().\n\nloop(State=#state{id=SessionID, parent=Parent}, HandlerState) ->\n\treceive\n\t\t{'$webtransport_event', SessionID, Event={closed, _, _}} ->\n\t\t\tterminate_proc(State, HandlerState, Event);\n\t\t{'$webtransport_event', SessionID, Event=closed_abruptly} ->\n\t\t\tterminate_proc(State, HandlerState, Event);\n\t\t{'$webtransport_event', SessionID, Event} ->\n\t\t\thandler_call(State, HandlerState, webtransport_handle, Event);\n\t\t%% Timeouts.\n%% @todo idle_timeout\n%\t\t{timeout, TRef, ?MODULE} ->\n%\t\t\ttick_idle_timeout(State, HandlerState, ParseState);\n%\t\t{timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->\n%\t\t\tbefore_loop(State, HandlerState, ParseState);\n\t\t%% System messages.\n\t\t{'EXIT', Parent, Reason} ->\n\t\t\t%% @todo We should exit gracefully.\n\t\t\texit(Reason);\n\t\t{system, From, Request} ->\n\t\t\tsys:handle_system_msg(Request, From, Parent, ?MODULE, [],\n\t\t\t\t{State, HandlerState});\n\t\t%% Calls from supervisor module.\n\t\t{'$gen_call', From, Call} ->\n\t\t\tcowboy_children:handle_supervisor_call(Call, From, [], ?MODULE),\n\t\t\tbefore_loop(State, HandlerState);\n\t\tMessage ->\n\t\t\thandler_call(State, HandlerState, webtransport_info, Message)\n\tend.\n\nhandler_call(State=#state{handler=Handler}, HandlerState, Callback, Message) ->\n\ttry case Callback of\n\t\twebtransport_init -> Handler:webtransport_init(HandlerState);\n\t\t_ -> Handler:Callback(Message, HandlerState)\n\tend of\n\t\t{Commands, HandlerState2} when is_list(Commands) ->\n\t\t\thandler_call_result(State, HandlerState2, Commands);\n\t\t{Commands, HandlerState2, hibernate} when is_list(Commands) ->\n\t\t\thandler_call_result(State#state{hibernate=true}, HandlerState2, Commands)\n\tcatch Class:Reason:Stacktrace ->\n\t\t%% @todo Do we need to send a close? Let cowboy_http3 detect and handle it?\n\t\thandler_terminate(State, HandlerState, {crash, Class, Reason}),\n\t\terlang:raise(Class, Reason, Stacktrace)\n\tend.\n\nhandler_call_result(State0, HandlerState, Commands) ->\n\tcase commands(Commands, State0, ok, []) of\n\t\t{ok, State} ->\n\t\t\tbefore_loop(State, HandlerState);\n\t\t{stop, State} ->\n\t\t\tterminate_proc(State, HandlerState, stop)\n\tend.\n\n%% We accumulate the commands that must be sent to the connection process\n%% because we want to send everything into one message. Other commands are\n%% processed immediately.\n\ncommands([], State, Res, []) ->\n\t{Res, State};\ncommands([], State=#state{id=SessionID, parent=Pid}, Res, Commands) ->\n\tPid ! {'$webtransport_commands', SessionID, lists:reverse(Commands)},\n\t{Res, State};\n%% {open_stream, OpenStreamRef, StreamType, InitialData}.\ncommands([Command={open_stream, _, _, _}|Tail], State, Res, Acc) ->\n\tcommands(Tail, State, Res, [Command|Acc]);\n%% {close_stream, StreamID, Code}.\ncommands([Command={close_stream, _, _}|Tail], State, Res, Acc) ->\n\tcommands(Tail, State, Res, [Command|Acc]);\n%% @todo We must reject send to a remote unidi stream.\n%% {send, StreamID | datagram, Data}.\ncommands([Command={send, _, _}|Tail], State, Res, Acc) ->\n\tcommands(Tail, State, Res, [Command|Acc]);\n%% {send, StreamID, IsFin, Data}.\ncommands([Command={send, _, _, _}|Tail], State, Res, Acc) ->\n\tcommands(Tail, State, Res, [Command|Acc]);\n%% initiate_close - DRAIN_WT_SESSION\ncommands([Command=initiate_close|Tail], State, Res, Acc) ->\n\tcommands(Tail, State, Res, [Command|Acc]);\n%% close | {close, Code} | {close, Code, Msg} - CLOSE_WT_SESSION\n%% @todo At this point the handler must not issue stream or send commands.\ncommands([Command=close|Tail], State, _, Acc) ->\n\tcommands(Tail, State, stop, [Command|Acc]);\ncommands([Command={close, _}|Tail], State, _, Acc) ->\n\tcommands(Tail, State, stop, [Command|Acc]);\ncommands([Command={close, _, _}|Tail], State, _, Acc) ->\n\tcommands(Tail, State, stop, [Command|Acc]).\n%% @todo A set_options command could be useful to increase the number of allowed streams\n%%       or other forms of flow control. Alternatively a flow command. Or both.\n%% @todo A shutdown_reason command could be useful for the same reasons as Websocekt.\n\n-spec terminate_proc(_, _, _) -> no_return().\n\nterminate_proc(State, HandlerState, Reason) ->\n\thandler_terminate(State, HandlerState, Reason),\n\t%% @todo This is what should be done if shutdown_reason gets implemented.\n%\tcase Shutdown of\n%\t\tnormal -> exit(normal);\n%\t\t_ -> exit({shutdown, Shutdown})\n%\tend.\n\texit(normal).\n\nhandler_terminate(#state{handler=Handler, req=Req}, HandlerState, Reason) ->\n\tcowboy_handler:terminate(Reason, Req, HandlerState, Handler).\n\n%% cowboy_stream callbacks.\n%%\n%% We shortcut stream handlers but still need to process some events\n%% such as process exiting or termination. We implement the relevant\n%% callbacks here. Note that as far as WebTransport is concerned,\n%% receiving stream data here would be an error therefore the data\n%% callback is not implemented.\n%%\n%% @todo Better type than map() for the cowboy_stream state.\n%% @todo Is this really useful?\n\n-spec info(cowboy_stream:streamid(), any(), State)\n\t-> {cowboy_stream:commands(), State} when State::map().\n\ninfo(StreamID, Msg, WTState=#{stream_state := StreamState0}) ->\n\t{Commands, StreamState} = cowboy_stream:info(StreamID, Msg, StreamState0),\n\t{Commands, WTState#{stream_state => StreamState}}.\n\n-spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), map())\n\t-> any().\n\nterminate(StreamID, Reason, #{stream_state := StreamState}) ->\n\tcowboy_stream:terminate(StreamID, Reason, StreamState).\n"
  },
  {
    "path": "test/compress_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(compress_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\n%% ct.\n\nall() ->\n\tAll = [\n\t\t{group, http_compress},\n\t\t{group, https_compress},\n\t\t{group, h2_compress},\n\t\t{group, h2c_compress},\n\t\t{group, h3_compress}\n\t],\n\t%% Don't run HTTP/3 tests on Windows for now.\n\tcase os:type() of\n\t\t{win32, _} ->\n\t\t\tAll -- [{group, h3_compress}];\n\t\t_ ->\n\t\t\tAll\n\tend.\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\n%% Routes.\n\ninit_dispatch(_Config) ->\n\tcowboy_router:compile([{\"[...]\", [\n\t\t{\"/reply/:what\", compress_h, reply},\n\t\t{\"/stream_reply/:what\", compress_h, stream_reply}\n\t]}]).\n\n%% Internal.\n\ndo_get(Path, ReqHeaders, Config) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, Path, ReqHeaders),\n\t{response, IsFin, Status, RespHeaders} = gun:await(ConnPid, Ref),\n\t{ok, Body} = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, Ref);\n\t\tfin -> {ok, <<>>}\n\tend,\n\tgun:close(ConnPid),\n\t{Status, RespHeaders, Body}.\n\n%% Tests.\n\ngzip_accept_encoding_malformed(Config) ->\n\tdoc(\"Send malformed accept-encoding; get an uncompressed response.\"),\n\t{200, Headers, _} = do_get(\"/reply/large\",\n\t\t[{<<\"accept-encoding\">>, <<\";\">>}], Config),\n\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t{_, <<\"100000\">>} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\ngzip_accept_encoding_missing(Config) ->\n\tdoc(\"Don't send accept-encoding; get an uncompressed response.\"),\n\t{200, Headers, _} = do_get(\"/reply/large\",\n\t\t[], Config),\n\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t{_, <<\"100000\">>} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\ngzip_accept_encoding_no_gzip(Config) ->\n\tdoc(\"Send accept-encoding: compress (unsupported by Cowboy); get an uncompressed response.\"),\n\t{200, Headers, _} = do_get(\"/reply/large\",\n\t\t[{<<\"accept-encoding\">>, <<\"compress\">>}], Config),\n\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t{_, <<\"100000\">>} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\ngzip_accept_encoding_not_supported(Config) ->\n\tdoc(\"Send unsupported accept-encoding; get an uncompressed response.\"),\n\t{200, Headers, _} = do_get(\"/reply/large\",\n\t\t[{<<\"accept-encoding\">>, <<\"application/gzip\">>}], Config),\n\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t{_, <<\"100000\">>} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\ngzip_reply_content_encoding(Config) ->\n\tdoc(\"Reply with content-encoding header; get an uncompressed response.\"),\n\t{200, Headers, _} = do_get(\"/reply/content-encoding\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\t%% We set the content-encoding to compress; without actually compressing.\n\t{_, <<\"compress\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t%% The reply didn't include a vary header.\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t{_, <<\"100000\">>} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\ngzip_reply_etag(Config) ->\n\tdoc(\"Reply with etag header; get an uncompressed response.\"),\n\t{200, Headers, _} = do_get(\"/reply/etag\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\t%% We set a strong etag.\n\t{_, <<\"\\\"STRONK\\\"\">>} = lists:keyfind(<<\"etag\">>, 1, Headers),\n\t%% The reply didn't include a vary header.\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t{_, <<\"100000\">>} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\ngzip_reply_large_body(Config) ->\n\tdoc(\"Reply a large body; get a gzipped response.\"),\n\t{200, Headers, GzBody} = do_get(\"/reply/large\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t{_, Length} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tct:log(\"Original length: 100000; compressed: ~s.\", [Length]),\n\t_ = zlib:gunzip(GzBody),\n\tok.\n\ngzip_reply_sendfile(Config) ->\n\tdoc(\"Reply using sendfile; get an uncompressed response.\"),\n\t{200, Headers, Body} = do_get(\"/reply/sendfile\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\tct:log(\"Body received:~n~p~n\", [Body]),\n\tok.\n\ngzip_reply_small_body(Config) ->\n\tdoc(\"Reply a small body; get an uncompressed response.\"),\n\t{200, Headers, _} = do_get(\"/reply/small\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t{_, <<\"100\">>} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\ngzip_stream_reply(Config) ->\n\tdoc(\"Stream reply; get a gzipped response.\"),\n\t{200, Headers, GzBody} = do_get(\"/stream_reply/large\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t_ = zlib:gunzip(GzBody),\n\tok.\n\ngzip_stream_reply_sendfile(Config) ->\n\tdoc(\"Stream reply using sendfile for some chunks; get a gzipped response.\"),\n\t{200, Headers, GzBody} = do_get(\"/stream_reply/sendfile\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t_ = zlib:gunzip(GzBody),\n\tok.\n\ngzip_stream_reply_sendfile_fin(Config) ->\n\tdoc(\"Stream reply using sendfile for some chunks; get a gzipped response.\"),\n\t{200, Headers, GzBody} = do_get(\"/stream_reply/sendfile_fin\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t_ = zlib:gunzip(GzBody),\n\tok.\n\ngzip_stream_reply_content_encoding(Config) ->\n\tdoc(\"Stream reply with content-encoding header; get an uncompressed response.\"),\n\t{200, Headers, Body} = do_get(\"/stream_reply/content-encoding\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\t{_, <<\"compress\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t100000 = iolist_size(Body),\n\tok.\n\ngzip_stream_reply_etag(Config) ->\n\tdoc(\"Stream reply with etag header; get an uncompressed response.\"),\n\t{200, Headers, Body} = do_get(\"/stream_reply/etag\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\t{_, <<\"\\\"STRONK\\\"\">>} = lists:keyfind(<<\"etag\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t100000 = iolist_size(Body),\n\tok.\n\nopts_compress_buffering_false(Config0) ->\n\tdoc(\"Confirm that the compress_buffering option can be set to false, \"\n\t\t\"which is the default.\"),\n\tFun = case config(ref, Config0) of\n\t\thttps_compress -> init_https;\n\t\th2_compress -> init_http2;\n\t\t_ -> init_http\n\tend,\n\tConfig = cowboy_test:Fun(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => init_dispatch(Config0)},\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h],\n\t\tcompress_buffering => false\n\t}, Config0),\n\ttry\n\t\tConnPid = gun_open(Config),\n\t\tRef = gun:get(ConnPid, \"/stream_reply/delayed\",\n\t\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t\tZ = zlib:open(),\n\t\tzlib:inflateInit(Z, 31),\n\t\t{data, nofin, Data1} = gun:await(ConnPid, Ref, 500),\n\t\t<<\"data: Hello!\\r\\n\\r\\n\">> = iolist_to_binary(zlib:inflate(Z, Data1)),\n\t\ttimer:sleep(1000),\n\t\t{data, nofin, Data2} = gun:await(ConnPid, Ref, 500),\n\t\t<<\"data: World!\\r\\n\\r\\n\">> = iolist_to_binary(zlib:inflate(Z, Data2)),\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nopts_compress_buffering_true(Config0) ->\n\tdoc(\"Confirm that the compress_buffering option can be set to true, \"\n\t\t\"and that the data received is buffered.\"),\n\tFun = case config(ref, Config0) of\n\t\thttps_compress -> init_https;\n\t\th2_compress -> init_http2;\n\t\t_ -> init_http\n\tend,\n\tConfig = cowboy_test:Fun(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => init_dispatch(Config0)},\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h],\n\t\tcompress_buffering => true\n\t}, Config0),\n\ttry\n\t\tConnPid = gun_open(Config),\n\t\tRef = gun:get(ConnPid, \"/stream_reply/delayed\",\n\t\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t\tZ = zlib:open(),\n\t\tzlib:inflateInit(Z, 31),\n\t\t%% The data gets buffered because it is too small.\n\t\t%% In zlib versions before OTP 20.1 the gzip header was also buffered.\n\t\t<<>> = case gun:await(ConnPid, Ref, 500) of\n\t\t\t{data, nofin, Data1} ->\n\t\t\t\tiolist_to_binary(zlib:inflate(Z, Data1));\n\t\t\t{error, timeout} ->\n\t\t\t\t<<>>\n\t\tend,\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_options_compress_buffering_false(Config0) ->\n\tdoc(\"Confirm that the compress_buffering option can be dynamically \"\n\t\t\"set to false by a handler and that the data received is not buffered.\"),\n\tFun = case config(ref, Config0) of\n\t\thttps_compress -> init_https;\n\t\th2_compress -> init_http2;\n\t\t_ -> init_http\n\tend,\n\tConfig = cowboy_test:Fun(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => init_dispatch(Config0)},\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h],\n\t\tcompress_buffering => true\n\t}, Config0),\n\ttry\n\t\tConnPid = gun_open(Config),\n\t\tRef = gun:get(ConnPid, \"/stream_reply/set_options_buffering_false\",\n\t\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t\tZ = zlib:open(),\n\t\tzlib:inflateInit(Z, 31),\n\t\t{data, nofin, Data1} = gun:await(ConnPid, Ref, 500),\n\t\t<<\"data: Hello!\\r\\n\\r\\n\">> = iolist_to_binary(zlib:inflate(Z, Data1)),\n\t\ttimer:sleep(1000),\n\t\t{data, nofin, Data2} = gun:await(ConnPid, Ref, 500),\n\t\t<<\"data: World!\\r\\n\\r\\n\">> = iolist_to_binary(zlib:inflate(Z, Data2)),\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_options_compress_buffering_true(Config0) ->\n\tdoc(\"Confirm that the compress_buffering option can be dynamically \"\n\t\t\"set to true by a handler and that the data received is buffered.\"),\n\tFun = case config(ref, Config0) of\n\t\thttps_compress -> init_https;\n\t\th2_compress -> init_http2;\n\t\t_ -> init_http\n\tend,\n\tConfig = cowboy_test:Fun(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => init_dispatch(Config0)},\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h],\n\t\tcompress_buffering => false\n\t}, Config0),\n\ttry\n\t\tConnPid = gun_open(Config),\n\t\tRef = gun:get(ConnPid, \"/stream_reply/set_options_buffering_true\",\n\t\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t\tZ = zlib:open(),\n\t\tzlib:inflateInit(Z, 31),\n\t\t%% The data gets buffered because it is too small.\n\t\t%% In zlib versions before OTP 20.1 the gzip header was also buffered.\n\t\t<<>> = case gun:await(ConnPid, Ref, 500) of\n\t\t\t{data, nofin, Data1} ->\n\t\t\t\tiolist_to_binary(zlib:inflate(Z, Data1));\n\t\t\t{error, timeout} ->\n\t\t\t\t<<>>\n\t\tend,\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_options_compress_threshold_0(Config) ->\n\tdoc(\"Confirm that the compress_threshold option can be dynamically \"\n\t\t\"set to change how large response bodies must be to be compressed.\"),\n\t{200, Headers, GzBody} = do_get(\"/reply/set_options_threshold0\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t_ = zlib:gunzip(GzBody),\n\tok.\n\nvary_accept(Config) ->\n\tdoc(\"Add accept-encoding to vary when the response has a 'vary: accept' header.\"),\n\t{200, Headers, _} = do_get(\"/reply/vary\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-vary\">>, <<\"accept\">>}\n\t], Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept, accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\tok.\n\nvary_accept_accept_encoding(Config) ->\n\tdoc(\"Don't change the vary value when the response has a 'vary: accept, accept-encoding' header.\"),\n\t{200, Headers, _} = do_get(\"/reply/vary\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-vary\">>, <<\"accept, accept-encoding\">>}\n\t], Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept, accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\tok.\n\nvary_empty(Config) ->\n\tdoc(\"Add accept-encoding to vary when the response has an empty vary header.\"),\n\t{200, Headers, _} = do_get(\"/reply/vary\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-vary\">>, <<>>}\n\t], Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"accept-encoding\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\tok.\n\nvary_wildcard(Config) ->\n\tdoc(\"Don't change the vary value when the response has a 'vary: *' header.\"),\n\t{200, Headers, _} = do_get(\"/reply/vary\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-vary\">>, <<\"*\">>}\n\t], Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t{_, <<\"*\">>} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\tok.\n"
  },
  {
    "path": "test/cover.spec",
    "content": "{incl_app, cowboy, details}.\n"
  },
  {
    "path": "test/cowboy_ct_hook.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_ct_hook).\n\n-export([init/2]).\n\ninit(_, _) ->\n\tct_helper:start([cowboy, gun]),\n\tct_helper:make_certs_in_ets(),\n\terror_logger:add_report_handler(ct_helper_error_h),\n\t{ok, undefined}.\n"
  },
  {
    "path": "test/cowboy_test.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(cowboy_test).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n\n%% Listeners initialization.\n\ninit_http(Ref, ProtoOpts, Config) ->\n\t{ok, _} = cowboy:start_clear(Ref, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(Ref),\n\t[{ref, Ref}, {type, tcp}, {protocol, http}, {port, Port}, {opts, []}|Config].\n\ninit_https(Ref, ProtoOpts, Config) ->\n\tOpts = ct_helper:get_certs_from_ets(),\n\t{ok, _} = cowboy:start_tls(Ref, Opts ++ [{port, 0}, {verify, verify_none}], ProtoOpts),\n\tPort = ranch:get_port(Ref),\n\t[{ref, Ref}, {type, ssl}, {protocol, http}, {port, Port}, {opts, Opts}|Config].\n\ninit_http2(Ref, ProtoOpts, Config) ->\n\tOpts = ct_helper:get_certs_from_ets(),\n\t{ok, _} = cowboy:start_tls(Ref, Opts ++ [{port, 0}, {verify, verify_none}], ProtoOpts),\n\tPort = ranch:get_port(Ref),\n\t[{ref, Ref}, {type, ssl}, {protocol, http2}, {port, Port}, {opts, Opts}|Config].\n\n%% @todo This will probably require TransOpts as argument.\ninit_http3(Ref, ProtoOpts, Config) ->\n\t%% @todo Quicer does not currently support non-file cert/key,\n\t%%       so we use quicer test certificates for now.\n\t%% @todo Quicer also does not support cacerts which means\n\t%%       we currently have no authentication based security.\n\tDataDir = filename:dirname(filename:dirname(config(data_dir, Config)))\n\t\t++ \"/rfc9114_SUITE_data\",\n\tTransOpts = #{\n\t\tsocket_opts => [\n\t\t\t{certfile, DataDir ++ \"/server.pem\"},\n\t\t\t{keyfile, DataDir ++ \"/server.key\"}\n\t\t]\n\t},\n\t{ok, Listener} = cowboy:start_quic(Ref, TransOpts, ProtoOpts),\n\t{ok, {_, Port}} = quicer:sockname(Listener),\n\t%% @todo Keep listener information around in a better place.\n\tpersistent_term:put({cowboy_test_quic, Ref}, Listener),\n\t[{ref, Ref}, {type, quic}, {protocol, http3}, {port, Port}, {opts, TransOpts}|Config].\n\nstop_group(Ref) ->\n\tcase persistent_term:get({cowboy_test_quic, Ref}, undefined) of\n\t\tundefined ->\n\t\t\tcowboy:stop_listener(Ref);\n\t\tListener ->\n\t\t\tquicer:close_listener(Listener)\n\tend.\n\n%% Common group of listeners used by most suites.\n\ncommon_all() ->\n\tAll = [\n\t\t{group, http},\n\t\t{group, https},\n\t\t{group, h2},\n\t\t{group, h2c},\n\t\t{group, h3},\n\t\t{group, http_compress},\n\t\t{group, https_compress},\n\t\t{group, h2_compress},\n\t\t{group, h2c_compress},\n\t\t{group, h3_compress}\n\t],\n\t%% Don't run HTTP/3 tests on Windows for now.\n\tcase os:type() of\n\t\t{win32, _} ->\n\t\t\tAll -- [{group, h3}, {group, h3_compress}];\n\t\t_ ->\n\t\t\tAll\n\tend.\n\ncommon_groups(Tests) ->\n\tParallel = case os:getenv(\"NO_PARALLEL\") of\n\t\tfalse -> parallel;\n\t\t_ -> no_parallel\n\tend,\n\tcommon_groups(Tests, Parallel).\n\ncommon_groups(Tests, Parallel) ->\n\tOpts = case Parallel of\n\t\tparallel -> [parallel];\n\t\tno_parallel -> []\n\tend,\n\tGroups = [\n\t\t{http, Opts, Tests},\n\t\t{https, Opts, Tests},\n\t\t{h2, Opts, Tests},\n\t\t{h2c, Opts, Tests},\n\t\t{h3, Opts, Tests},\n\t\t{http_compress, Opts, Tests},\n\t\t{https_compress, Opts, Tests},\n\t\t{h2_compress, Opts, Tests},\n\t\t{h2c_compress, Opts, Tests},\n\t\t{h3_compress, Opts, Tests}\n\t],\n\t%% Don't run HTTP/3 tests on Windows for now.\n\tcase os:type() of\n\t\t{win32, _} ->\n\t\t\tGroups -- [{h3, Opts, Tests}, {h3_compress, Opts, Tests}];\n\t\t_ ->\n\t\t\tGroups\n\tend.\n\ninit_common_groups(Name, Config, Mod) ->\n\tinit_common_groups(Name, Config, Mod, #{}).\n\ninit_common_groups(Name = http, Config, Mod, ProtoOpts) ->\n\tinit_http(Name, ProtoOpts#{\n\t\tenv => #{dispatch => Mod:init_dispatch(Config)}\n\t}, [{flavor, vanilla}|Config]);\ninit_common_groups(Name = https, Config, Mod, ProtoOpts) ->\n\tinit_https(Name, ProtoOpts#{\n\t\tenv => #{dispatch => Mod:init_dispatch(Config)}\n\t}, [{flavor, vanilla}|Config]);\ninit_common_groups(Name = h2, Config, Mod, ProtoOpts) ->\n\tinit_http2(Name, ProtoOpts#{\n\t\tenv => #{dispatch => Mod:init_dispatch(Config)}\n\t}, [{flavor, vanilla}|Config]);\ninit_common_groups(Name = h2c, Config, Mod, ProtoOpts) ->\n\tConfig1 = init_http(Name, ProtoOpts#{\n\t\tenv => #{dispatch => Mod:init_dispatch(Config)}\n\t}, [{flavor, vanilla}|Config]),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_common_groups(Name = h3, Config, Mod, ProtoOpts) ->\n\tinit_http3(Name, ProtoOpts#{\n\t\tenv => #{dispatch => Mod:init_dispatch(Config)}\n\t}, [{flavor, vanilla}|Config]);\ninit_common_groups(Name = http_compress, Config, Mod, ProtoOpts) ->\n\tinit_http(Name, ProtoOpts#{\n\t\tenv => #{dispatch => Mod:init_dispatch(Config)},\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h]\n\t}, [{flavor, compress}|Config]);\ninit_common_groups(Name = https_compress, Config, Mod, ProtoOpts) ->\n\tinit_https(Name, ProtoOpts#{\n\t\tenv => #{dispatch => Mod:init_dispatch(Config)},\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h]\n\t}, [{flavor, compress}|Config]);\ninit_common_groups(Name = h2_compress, Config, Mod, ProtoOpts) ->\n\tinit_http2(Name, ProtoOpts#{\n\t\tenv => #{dispatch => Mod:init_dispatch(Config)},\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h]\n\t}, [{flavor, compress}|Config]);\ninit_common_groups(Name = h2c_compress, Config, Mod, ProtoOpts) ->\n\tConfig1 = init_http(Name, ProtoOpts#{\n\t\tenv => #{dispatch => Mod:init_dispatch(Config)},\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h]\n\t}, [{flavor, compress}|Config]),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_common_groups(Name = h3_compress, Config, Mod, ProtoOpts) ->\n\tinit_http3(Name, ProtoOpts#{\n\t\tenv => #{dispatch => Mod:init_dispatch(Config)},\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h]\n\t}, [{flavor, compress}|Config]).\n\n%% Support functions for testing using Gun.\n\ngun_open(Config) ->\n\tgun_open(Config, #{}).\n\ngun_open(Config, Opts) ->\n\tTlsOpts = case proplists:get_value(no_cert, Config, false) of\n\t\ttrue -> [{verify, verify_none}];\n\t\tfalse -> ct_helper:get_certs_from_ets() %% @todo Wrong in current quicer.\n\tend,\n\t{ok, ConnPid} = gun:open(\"localhost\", config(port, Config), Opts#{\n\t\tretry => 0,\n\t\ttransport => config(type, Config),\n\t\ttls_opts => TlsOpts,\n\t\tprotocols => [config(protocol, Config)]\n\t}),\n\tConnPid.\n\ngun_down(ConnPid) ->\n\treceive {gun_down, ConnPid, _, _, _} -> ok\n\tafter 500 -> error(timeout) end.\n\n%% Support functions for testing using a raw socket.\n\nraw_open(Config) ->\n\tTransport = case config(type, Config) of\n\t\ttcp -> gen_tcp;\n\t\tssl -> ssl\n\tend,\n\t{_, Opts} = lists:keyfind(opts, 1, Config),\n\t{ok, Socket} = Transport:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}, {packet, raw},\n\t\t\t{reuseaddr, true}, {nodelay, true}|Opts]),\n\t{raw_client, Socket, Transport}.\n\nraw_send({raw_client, Socket, Transport}, Data) ->\n\tTransport:send(Socket, Data).\n\nraw_recv_head({raw_client, Socket, Transport}) ->\n\t{ok, Data} = Transport:recv(Socket, 0, 10000),\n\traw_recv_head(Socket, Transport, Data).\n\nraw_recv_head(Socket, Transport, Buffer) ->\n\tcase binary:match(Buffer, <<\"\\r\\n\\r\\n\">>) of\n\t\tnomatch ->\n\t\t\t{ok, Data} = Transport:recv(Socket, 0, 10000),\n\t\t\traw_recv_head(Socket, Transport, << Buffer/binary, Data/binary >>);\n\t\t{_, _} ->\n\t\t\tBuffer\n\tend.\n\nraw_recv_rest({raw_client, _, _}, Length, Buffer) when Length =:= byte_size(Buffer) ->\n\tBuffer;\nraw_recv_rest({raw_client, Socket, Transport}, Length, Buffer) when Length > byte_size(Buffer) ->\n\t{ok, Data} = Transport:recv(Socket, Length - byte_size(Buffer), 10000),\n\t<< Buffer/binary, Data/binary >>.\n\nraw_recv({raw_client, Socket, Transport}, Length, Timeout) ->\n\tTransport:recv(Socket, Length, Timeout).\n\nraw_expect_recv({raw_client, _, _}, <<>>) ->\n\tok;\nraw_expect_recv({raw_client, Socket, Transport}, Expect) ->\n\t{ok, Expect} = Transport:recv(Socket, iolist_size(Expect), 10000),\n\tok.\n"
  },
  {
    "path": "test/decompress_SUITE.erl",
    "content": "%% Copyright (c) jdamanalo <joshuadavid.agustin@manalo.ph>\n%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(decompress_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\n%% ct.\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_group(Name = http, Config) ->\n\tcowboy_test:init_http(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = https, Config) ->\n\tcowboy_test:init_http(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = h2, Config) ->\n\tcowboy_test:init_http2(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = h2c, Config) ->\n\tConfig1 = cowboy_test:init_http(Name, init_plain_opts(Config), Config),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_per_group(Name = h3, Config) ->\n\tcowboy_test:init_http3(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = http_compress, Config) ->\n\tcowboy_test:init_http(Name, init_compress_opts(Config), Config);\ninit_per_group(Name = https_compress, Config) ->\n\tcowboy_test:init_http(Name, init_compress_opts(Config), Config);\ninit_per_group(Name = h2_compress, Config) ->\n\tcowboy_test:init_http2(Name, init_compress_opts(Config), Config);\ninit_per_group(Name = h2c_compress, Config) ->\n\tConfig1 = cowboy_test:init_http(Name, init_compress_opts(Config), Config),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_per_group(Name = h3_compress, Config) ->\n\tcowboy_test:init_http3(Name, init_compress_opts(Config), Config).\n\nend_per_group(Name, _) ->\n\tcowboy:stop_listener(Name).\n\ninit_plain_opts(Config) ->\n\t#{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))},\n\t\tstream_handlers => [cowboy_decompress_h, cowboy_stream_h]\n\t}.\n\ninit_compress_opts(Config) ->\n\t#{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))},\n\t\tstream_handlers => [cowboy_decompress_h, cowboy_compress_h, cowboy_stream_h]\n\t}.\n\ninit_routes(_) ->\n\t[{'_', [\n\t\t{\"/echo/:what\", decompress_h, echo},\n\t\t{\"/test/:what\", decompress_h, test}\n\t]}].\n\n%% Internal.\n\ndo_post(Path, ReqHeaders, Body, Config) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, Path, ReqHeaders, Body),\n\t{response, IsFin, Status, RespHeaders} = gun:await(ConnPid, Ref),\n\t{ok, ResponseBody} = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, Ref);\n\t\tfin -> {ok, <<>>}\n\tend,\n\tgun:close(ConnPid),\n\t{Status, RespHeaders, ResponseBody}.\n\ncreate_gzip_bomb() ->\n\tZ = zlib:open(),\n\tzlib:deflateInit(Z, 9, deflated, 31, 8, default),\n\t%% 1000 chunks of 100000 zeroes (100MB).\n\tBomb = do_create_gzip_bomb(Z, 1000),\n\tzlib:deflateEnd(Z),\n\tzlib:close(Z),\n\tiolist_to_binary(Bomb).\n\ndo_create_gzip_bomb(Z, 0) ->\n\tzlib:deflate(Z, << >>, finish);\ndo_create_gzip_bomb(Z, N) ->\n\tData = <<0:800000>>,\n\tDeflate = zlib:deflate(Z, Data),\n\t[Deflate | do_create_gzip_bomb(Z, N - 1)].\n\n%% Tests.\n\ncontent_encoding_none(Config) ->\n\tdoc(\"Requests without content-encoding are processed normally.\"),\n\tBody = <<\"test\">>,\n\t{200, _, Body} = do_post(\"/echo/normal\", [], Body, Config),\n\t%% The content-encoding header would be propagated,\n\t%% but there was no content-encoding header to propagate.\n\t{200, _, <<\"undefined\">>} = do_post(\"/test/content-encoding\", [], Body, Config),\n\t%% The content_decoded list is empty.\n\t{200, _, <<\"[]\">>} = do_post(\"/test/content-decoded\", [], Body, Config),\n\tok.\n\ncontent_encoding_malformed(Config) ->\n\tdoc(\"Requests with a malformed content-encoding are processed \"\n\t\t\"as if no content-encoding was sent.\"),\n\tBody = <<\"test\">>,\n\t{200, _, Body} = do_post(\"/echo/normal\",\n\t\t[{<<\"content-encoding\">>, <<\";\">>}], Body, Config),\n\t%% The content-encoding header is propagated.\n\t{200, _, <<\";\">>} = do_post(\"/test/content-encoding\",\n\t\t[{<<\"content-encoding\">>, <<\";\">>}], Body, Config),\n\t%% The content_decoded list is empty.\n\t{200, _, <<\"[]\">>} = do_post(\"/test/content-decoded\",\n\t\t[{<<\"content-encoding\">>, <<\";\">>}], Body, Config),\n\tok.\n\ncontent_encoding_not_supported(Config) ->\n\tdoc(\"Requests with an unsupported content-encoding are processed \"\n\t\t\"as if no content-encoding was sent.\"),\n\tBody = <<\"test\">>,\n\t{200, _, Body} = do_post(\"/echo/normal\",\n\t\t[{<<\"content-encoding\">>, <<\"compress\">>}], Body, Config),\n\t%% The content-encoding header is propagated.\n\t{200, _, <<\"compress\">>} = do_post(\"/test/content-encoding\",\n\t\t[{<<\"content-encoding\">>, <<\"compress\">>}], Body, Config),\n\t%% The content_decoded list is empty.\n\t{200, _, <<\"[]\">>} = do_post(\"/test/content-decoded\",\n\t\t[{<<\"content-encoding\">>, <<\"compress\">>}], Body, Config),\n\tok.\n\ncontent_encoding_multiple(Config) ->\n\tdoc(\"Requests with multiple content-encoding values are processed \"\n\t\t\"as if no content-encoding was sent.\"),\n\tBody = <<\"test\">>,\n\t{200, _, Body} = do_post(\"/echo/normal\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip, compress\">>}], Body, Config),\n\t%% The content-encoding header is propagated.\n\t{200, _, <<\"gzip, compress\">>} = do_post(\"/test/content-encoding\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip, compress\">>}], Body, Config),\n\t%% The content_decoded list is empty.\n\t{200, _, <<\"[]\">>} = do_post(\"/test/content-decoded\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip, compress\">>}], Body, Config),\n\tok.\n\ndecompress(Config) ->\n\tdoc(\"Requests with content-encoding set to gzip and gzipped data \"\n\t\t\"are transparently decompressed.\"),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\t{200, _, Data} = do_post(\"/echo/normal\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t%% The content-encoding header is NOT propagated.\n\t{200, _, <<\"undefined\">>} = do_post(\"/test/content-encoding\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t%% The content_decoded list contains <<\"gzip\">>.\n\t{200, _, <<\"[<<\\\"gzip\\\">>]\">>} = do_post(\"/test/content-decoded\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\tok.\n\ndecompress_error(Config) ->\n\tdoc(\"Requests with content-encoding set to gzip but the data \"\n\t\t\"cannot be decoded are rejected with a 400 Bad Request error.\"),\n\tBody = <<\"test\">>,\n\t{400, _, _} = do_post(\"/echo/normal\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\tok.\n\ndecompress_stream(Config) ->\n\tdoc(\"Requests with content-encoding set to gzip and gzipped data \"\n\t\t\"are transparently decompressed, even when the data is streamed.\"),\n\t%% Handler read length 1KB. Compressing 3KB should be enough to trigger more.\n\tData = crypto:strong_rand_bytes(3000),\n\tBody = zlib:gzip(Data),\n\tSize = byte_size(Body),\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, \"/echo/normal\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}]),\n\tgun:data(ConnPid, Ref, nofin, binary:part(Body, 0, Size div 2)),\n\ttimer:sleep(1000),\n\tgun:data(ConnPid, Ref, fin, binary:part(Body, Size div 2, Size div 2 + Size rem 2)),\n\t{response, IsFin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, Data} = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, Ref);\n\t\tfin -> {ok, <<>>}\n\tend,\n\tgun:close(ConnPid),\n\t%% The content-encoding header is NOT propagated.\n\tConnPid2 = gun_open(Config),\n\tRef2 = gun:post(ConnPid2, \"/test/content-encoding\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}]),\n\t{response, nofin, 200, _} = gun:await(ConnPid2, Ref2),\n\t{ok, <<\"undefined\">>} = gun:await_body(ConnPid2, Ref2),\n\tgun:close(ConnPid2),\n\t%% The content_decoded list contains <<\"gzip\">>.\n\tConnPid3 = gun_open(Config),\n\tRef3 = gun:post(ConnPid3, \"/test/content-decoded\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}]),\n\t{response, nofin, 200, _} = gun:await(ConnPid3, Ref3),\n\t{ok, <<\"[<<\\\"gzip\\\">>]\">>} = gun:await_body(ConnPid3, Ref3),\n\tgun:close(ConnPid3).\n\nopts_decompress_enabled_false(Config0) ->\n\tdoc(\"Confirm that the decompress_enabled option can be set.\"),\n\tFun = case config(ref, Config0) of\n\t\tHTTPS when HTTPS =:= https_compress; HTTPS =:= https -> init_https;\n\t\tH2 when H2 =:= h2_compress; H2 =:= h2 -> init_http2;\n\t\t_ -> init_http\n\tend,\n\tConfig = cowboy_test:Fun(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tstream_handlers => [cowboy_decompress_h, cowboy_stream_h],\n\t\tdecompress_enabled => false\n\t}, Config0),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\ttry\n\t\t{200, Headers, Body} = do_post(\"/echo/normal\",\n\t\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t\t%% We do not set accept-encoding when we are disabled.\n\t\tfalse = lists:keyfind(<<\"accept-encoding\">>, 1, Headers)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_options_decompress_enabled_false(Config) ->\n\tdoc(\"Confirm that the decompress_enabled option can be dynamically \"\n\t\t\"set to false and the data received is not decompressed.\"),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\t{200, Headers, Body} = do_post(\"/echo/decompress_disable\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t%% We do not set accept-encoding when we are disabled.\n\tfalse = lists:keyfind(<<\"accept-encoding\">>, 1, Headers),\n\tok.\n\nset_options_decompress_disable_in_the_middle(Config) ->\n\tdoc(\"Confirm that setting the decompress_enabled option dynamically \"\n\t\t\"to false after starting to read the body does not disable decompression \"\n\t\t\"and the data received is decompressed.\"),\n\tData = rand:bytes(1000000),\n\tBody = zlib:gzip(Data),\n\t%% Since we were not ignoring before starting to read,\n\t%% we receive the entire body decompressed.\n\t{200, Headers, Data} = do_post(\"/test/disable-in-the-middle\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t%% We do set accept-encoding when we are enabled,\n\t%% even if an attempt to disable in the middle is ignored.\n\t{_, _} = lists:keyfind(<<\"accept-encoding\">>, 1, Headers),\n\tok.\n\nset_options_decompress_enable_in_the_middle(Config0) ->\n\tdoc(\"Confirm that setting the decompress_enabled option dynamically \"\n\t\t\"to true after starting to read the body does not enable decompression \"\n\t\t\"and the data received is not decompressed.\"),\n\tFun = case config(ref, Config0) of\n\t\tHTTPS when HTTPS =:= https_compress; HTTPS =:= https -> init_https;\n\t\tH2 when H2 =:= h2_compress; H2 =:= h2 -> init_http2;\n\t\t_ -> init_http\n\tend,\n\tConfig = cowboy_test:Fun(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tstream_handlers => [cowboy_decompress_h, cowboy_stream_h],\n\t\tdecompress_enabled => false\n\t}, Config0),\n\tData = rand:bytes(1000000),\n\tBody = zlib:gzip(Data),\n\ttry\n\t\t%% Since we were ignoring before starting to read,\n\t\t%% we receive the entire body compressed.\n\t\t{200, Headers, Body} = do_post(\"/test/enable-in-the-middle\",\n\t\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t\t%% We do not set accept-encoding when we are disabled,\n\t\t%% even if an attempt to enable in the middle is ignored.\n\t\tfalse = lists:keyfind(<<\"accept-encoding\">>, 1, Headers)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nopts_decompress_ratio_limit(Config0) ->\n\tdoc(\"Confirm that the decompress_ratio_limit option can be set.\"),\n\tFun = case config(ref, Config0) of\n\t\tHTTPS when HTTPS =:= https_compress; HTTPS =:= https -> init_https;\n\t\tH2 when H2 =:= h2_compress; H2 =:= h2 -> init_http2;\n\t\t_ -> init_http\n\tend,\n\tConfig = cowboy_test:Fun(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tstream_handlers => [cowboy_decompress_h, cowboy_stream_h],\n\t\tdecompress_ratio_limit => 1\n\t}, Config0),\n\t%% Data must be big enough for compression to be effective,\n\t%% so that ratio_limit=1 will fail.\n\tData = <<0:800>>,\n\tBody = zlib:gzip(Data),\n\ttry\n\t\t{413, _, _} = do_post(\"/echo/normal\",\n\t\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_options_decompress_ratio_limit(Config) ->\n\tdoc(\"Confirm that the decompress_ratio_limit option can be dynamically set.\"),\n\t%% Data must be big enough for compression to be effective,\n\t%% so that ratio_limit=1 will fail.\n\tData = <<0:800>>,\n\tBody = zlib:gzip(Data),\n\t{413, _, _} = do_post(\"/echo/decompress_ratio_limit\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\tok.\n\ngzip_bomb(Config) ->\n\tdoc(\"Confirm that requests are rejected with a 413 Payload Too Large \"\n\t\t\"error when the ratio limit is exceeded.\"),\n\tBody = create_gzip_bomb(),\n\t{413, _, _} = do_post(\"/echo/normal\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\tok.\n\nset_accept_encoding_response(Config) ->\n\tdoc(\"Header accept-encoding must be set on valid response command. \"\n\t\t\"(RFC9110 12.5.3)\"),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\t{200, Headers, Data} = do_post(\"/echo/normal\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"accept-encoding\">>, 1, Headers),\n\tok.\n\nset_accept_encoding_header(Config) ->\n\tdoc(\"Header accept-encoding must be set on valid header command. \"\n\t\t\"(RFC9110 12.5.3)\"),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\t{200, Headers, Data} = do_post(\"/test/header-command\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"accept-encoding\">>, 1, Headers),\n\tok.\n\nadd_accept_encoding_header_valid(Config) ->\n\tdoc(\"Supported content codings must be added to the accept-encoding \"\n\t\t\"header if it already exists. (RFC9110 12.5.3)\"),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\t{200, Headers, Data} = do_post(\"/test/accept-identity\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t{_, <<\"identity, gzip\">>} = lists:keyfind(<<\"accept-encoding\">>, 1, Headers),\n\tok.\n\noverride_accept_encoding_header_invalid(Config) ->\n\tdoc(\"When the stream handler cannot parse the accept-encoding header \"\n\t\t\"found in the response, it overrides it.\"),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\t{200, Headers, Data} = do_post(\"/test/invalid-header\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"accept-encoding\">>, 1, Headers),\n\tok.\n\noverride_accept_encoding_excluded(Config) ->\n\tdoc(\"The stream handler must ensure that the content encodings \"\n\t\t\"it supports are not marked as unsupported in response headers. \"\n\t\t\"The stream handler enables gzip when explicitly excluded. \"\n\t\t\"(RFC9110 12.5.3)\"),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\t{200, Headers, Data} = do_post(\"/test/reject-explicit-header\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t{_, <<\"identity;q=1, gzip;q=1\">>} = lists:keyfind(<<\"accept-encoding\">>, 1, Headers),\n\tok.\n\n%% *;q=0 will reject codings that are not listed. Supported codings\n%% must always be enabled when the handler is used.\nadd_accept_encoding_excluded(Config) ->\n\tdoc(\"The stream handler must ensure that the content encodings \"\n\t\t\"it supports are not marked as unsupported in response headers. \"\n\t\t\"The stream handler enables gzip when implicitly excluded (*;q=0). \"\n\t\t\"(RFC9110 12.5.3)\"),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\t{200, Headers, Data} = do_post(\"/test/reject-implicit-header\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t{_, <<\"gzip;q=1, identity;q=1, *;q=0\">>} = lists:keyfind(<<\"accept-encoding\">>, 1, Headers),\n\tok.\n\nno_override_accept_coding_set_explicit(Config) ->\n\tdoc(\"Confirm that accept-encoding is not overridden when the \"\n\t\t\"content encodings it supports are explicitly set. \"\n\t\t\"(RFC9110 12.5.3)\"),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\t{200, Headers, Data} = do_post(\"/test/accept-explicit-header\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t{_, <<\"identity, gzip;q=0.5\">>} = lists:keyfind(<<\"accept-encoding\">>, 1, Headers),\n\tok.\n\nno_override_accept_coding_set_implicit(Config) ->\n\tdoc(\"Confirm that accept-encoding is not overridden when the \"\n\t\t\"content encodings it supports are implicitly set. \"\n\t\t\"(RFC9110 12.5.3)\"),\n\tData = <<\"test\">>,\n\tBody = zlib:gzip(Data),\n\t{200, Headers, Data} = do_post(\"/test/accept-implicit-header\",\n\t\t[{<<\"content-encoding\">>, <<\"gzip\">>}], Body, Config),\n\t{_, <<\"identity, *;q=0.5\">>} = lists:keyfind(<<\"accept-encoding\">>, 1, Headers),\n\tok.\n"
  },
  {
    "path": "test/draft_h3_webtransport_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(draft_h3_webtransport_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(rfc9114_SUITE, [do_wait_stream_aborted/1]).\n\n-ifdef(COWBOY_QUICER).\n\n-include_lib(\"quicer/include/quicer.hrl\").\n\nall() ->\n\t[{group, enabled}].\n\ngroups() ->\n\tTests = ct_helper:all(?MODULE),\n\t[{enabled, [], Tests}]. %% @todo Enable parallel when all is better.\n\ninit_per_group(Name = enabled, Config) ->\n\tcowboy_test:init_http3(Name, #{\n\t\tenable_connect_protocol => true,\n\t\th3_datagram => true,\n\t\tenable_webtransport => true, %% For compatibility with draft-02.\n\t\twt_max_sessions => 10,\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))}\n\t}, Config).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_routes(_) -> [\n\t{\"localhost\", [\n\t\t{\"/wt\", wt_echo_h, []}\n\t]}\n].\n\n%% Temporary.\n\n%% To start Chromium the command line is roughly:\n%% chromium --ignore-certificate-errors-spki-list=LeLykt63i2FRAm+XO91yBoSjKfrXnAFygqe5xt0zgDA= --ignore-certificate-errors --user-data-dir=/tmp/chromium-wt --allow-insecure-localhost --webtransport-developer-mode --enable-quic https://googlechrome.github.io/samples/webtransport/client.html\n%%\n%% To find the SPKI the command is roughly:\n%% openssl x509 -in ~/ninenines/cowboy/test/rfc9114_SUITE_data/server.pem -pubkey -noout | \\ \n%%  openssl pkey -pubin -outform der | \\\n%%  openssl dgst -sha256 -binary | \\\n%%  openssl enc -base64\n\n%run(Config) ->\n%\tct:pal(\"port ~p\", [config(port, Config)]),\n%\ttimer:sleep(infinity).\n\n%% 3. Session Establishment\n\n%% 3.1. Establishing a WebTransport-Capable HTTP/3 Connection\n\n%% In order to indicate support for WebTransport, the server MUST send a SETTINGS_WT_MAX_SESSIONS value greater than \"0\" in its SETTINGS frame. (3.1)\n%% @todo reject_session_disabled\n%% @todo accept_session_below\n%% @todo accept_session_equal\n%% @todo reject_session_above\n\n%% The client MUST NOT send a WebTransport request until it has received the setting indicating WebTransport support from the server. (3.1)\n\n%% For draft verisons of WebTransport only, the server MUST NOT process any incoming WebTransport requests until the client settings have been received, as the client may be using a version of the WebTransport extension that is different from the one used by the server. (3.1)\n\n%% Because WebTransport over HTTP/3 requires support for HTTP/3 datagrams and the Capsule Protocol, both the client and the server MUST indicate support for HTTP/3 datagrams by sending a SETTINGS_H3_DATAGRAM value set to 1 in their SETTINGS frame (see Section 2.1.1 of [HTTP-DATAGRAM]). (3.1)\n%% @todo settings_h3_datagram_enabled\n\n%% WebTransport over HTTP/3 also requires support for QUIC datagrams. To indicate support, both the client and the server MUST send a max_datagram_frame_size transport parameter with a value greater than 0 (see Section 3 of [QUIC-DATAGRAM]). (3.1)\n%% @todo quic_datagram_enabled (if size is too low the CONNECT stream can be used for capsules)\n\n%% Any WebTransport requests sent by the client without enabling QUIC and HTTP datagrams MUST be treated as malformed by the server, as described in Section 4.1.2 of [HTTP3]. (3.1)\n%% @todo reject_h3_datagram_disabled\n%% @todo reject_quic_datagram_disabled\n\n%% WebTransport over HTTP/3 relies on the RESET_STREAM_AT frame defined in [RESET-STREAM-AT]. To indicate support, both the client and the server MUST enable the extension as described in Section 3 of [RESET-STREAM-AT]. (3.1)\n%% @todo reset_stream_at_enabled\n\n%% 3.2. Extended CONNECT in HTTP/3\n\n%% [RFC8441] defines an extended CONNECT method in Section 4, enabled by the SETTINGS_ENABLE_CONNECT_PROTOCOL setting. That setting is defined for HTTP/3 by [RFC9220]. A server supporting WebTransport over HTTP/3 MUST send both the SETTINGS_WT_MAX_SESSIONS setting with a value greater than \"0\" and the SETTINGS_ENABLE_CONNECT_PROTOCOL setting with a value of \"1\". (3.2)\n%% @todo settings_enable_connect_protocol_enabled\n%% @todo reject_settings_enable_connect_protocol_disabled\n\n%% 3.3. Creating a New Session\n\n%% As WebTransport sessions are established over HTTP/3, they are identified using the https URI scheme ([HTTP], Section 4.2.2). (3.3)\n\n%% In order to create a new WebTransport session, a client can send an HTTP CONNECT request. The :protocol pseudo-header field ([RFC8441]) MUST be set to webtransport. The :scheme field MUST be https. Both the :authority and the :path value MUST be set; those fields indicate the desired WebTransport server. If the WebTransport session is coming from a browser client, an Origin header [RFC6454] MUST be provided within the request; otherwise, the header is OPTIONAL. (3.3)\n\n%% If it does not (have a WT server), it SHOULD reply with status code 404 (Section 15.5.5 of [HTTP]). (3.3)\n\n%% When the request contains the Origin header, the WebTransport server MUST verify the Origin header to ensure that the specified origin is allowed to access the server in question. If the verification fails, the WebTransport server SHOULD reply with status code 403 (Section 15.5.4 of [HTTP]). (3.3)\n\naccept_session_when_enabled(Config) ->\n\tdoc(\"Confirm that a WebTransport session can be established over HTTP/3. \"\n\t\t\"(draft_webtrans_http3 3.3, RFC9220)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send Hello, get Hello back.\n\t{ok, BidiStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(BidiStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6, \"Hello\">>),\n\t{nofin, <<\"Hello\">>} = do_receive_data(BidiStreamRef),\n\tok.\n\n%% If the server accepts 0-RTT, the server MUST NOT reduce the limit of maximum open WebTransport sessions from the one negotiated during the previous session; such change would be deemed incompatible, and MUST result in a H3_SETTINGS_ERROR connection error. (3.3)\n\n%% The capsule-protocol header field Section 3.4 of [HTTP-DATAGRAM] is not required by WebTransport and can safely be ignored by WebTransport endpoints. (3.3)\n\n%% 3.4. Application Protocol Negotiation\n\napplication_protocol_negotiation(Config) ->\n\tdoc(\"Applications can negotiate a protocol to use via WebTransport. \"\n\t\t\"(draft_webtrans_http3 3.4)\"),\n\t%% Connect to the WebTransport server.\n\tWTAvailableProtocols = cow_http_hd:wt_available_protocols([<<\"foo\">>, <<\"bar\">>]),\n\t#{\n\t\tresp_headers := RespHeaders\n\t} = do_webtransport_connect(Config, [{<<\"wt-available-protocols\">>, WTAvailableProtocols}]),\n\t{<<\"wt-protocol\">>, WTProtocol} = lists:keyfind(<<\"wt-protocol\">>, 1, RespHeaders),\n\t<<\"foo\">> = iolist_to_binary(cow_http_hd:parse_wt_protocol(WTProtocol)),\n\tok.\n\n%% Both WT-Available-Protocols and WT-Protocol are Structured Fields [RFC8941]. WT-Available-Protocols is a List of Tokens, and WT-Protocol is a Token. The token in the WT-Protocol response header field MUST be one of the tokens listed in WT-Available-Protocols of the request. (3.4)\n\n%% @todo 3.5 Prioritization\n\n%% 4. WebTransport Features\n\n%% The client MAY optimistically open unidirectional and bidirectional streams, as well as send datagrams, for a session that it has sent the CONNECT request for, even if it has not yet received the server's response to the request. (4)\n\n%% If at any point a session ID is received that cannot be a valid ID for a client-initiated bidirectional stream, the recipient MUST close the connection with an H3_ID_ERROR error code. (4)\n%% @todo Open bidi with Session ID 0, then do the CONNECT request.\n\n%% 4.1. Unidirectional streams\n\nunidirectional_streams(Config) ->\n\tdoc(\"Both endpoints can open and use unidirectional streams. \"\n\t\t\"(draft_webtrans_http3 4.1)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a unidi stream, send Hello with a Fin flag.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(LocalStreamRef,\n\t\t<<1:2, 16#54:14, 0:2, SessionID:6, \"Hello\">>,\n\t\t?QUIC_SEND_FLAG_FIN),\n\t%% Accept an identical unidi stream.\n\t{unidi, RemoteStreamRef} = do_receive_new_stream(),\n\t{nofin, <<1:2, 16#54:14, 0:2, SessionID:6>>} = do_receive_data(RemoteStreamRef),\n\t{fin, <<\"Hello\">>} = do_receive_data(RemoteStreamRef),\n\tok.\n\n%% 4.2. Bidirectional Streams\n\nbidirectional_streams_client(Config) ->\n\tdoc(\"The WT client can open and use bidirectional streams. \"\n\t\t\"(draft_webtrans_http3 4.2)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send Hello, get Hello back.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6, \"Hello\">>),\n\t{nofin, <<\"Hello\">>} = do_receive_data(LocalStreamRef),\n\tok.\n\nbidirectional_streams_server(Config) ->\n\tdoc(\"The WT server can open and use bidirectional streams. \"\n\t\t\"(draft_webtrans_http3 4.2)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send a special instruction\n\t%% to make the server create another bidi stream.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6, \"TEST:open_bidi\">>),\n\t%% Accept the bidi stream and receive the data.\n\t{bidi, RemoteStreamRef} = do_receive_new_stream(),\n\t{nofin, <<1:2, 16#41:14, 0:2, SessionID:6>>} = do_receive_data(RemoteStreamRef),\n\t{ok, _} = quicer:send(RemoteStreamRef, <<\"Hello\">>,\n\t\t?QUIC_SEND_FLAG_FIN),\n\t{fin, <<\"Hello\">>} = do_receive_data(RemoteStreamRef),\n\tok.\n\n%% Endpoints MUST NOT send WT_STREAM as a frame type on HTTP/3 streams other than the very first bytes of a request stream. Receiving this frame type in any other circumstances MUST be treated as a connection error of type H3_FRAME_ERROR. (4.2)\n\n%% 4.3. Resetting Data Streams\n\n%% A WebTransport endpoint may send a RESET_STREAM or a STOP_SENDING frame for a WebTransport data stream. Those signals are propagated by the WebTransport implementation to the application. (4.3)\n\n%% A WebTransport application SHALL provide an error code for those operations. (4.3)\n\n%% WebTransport implementations MUST use the RESET_STREAM_AT frame [RESET-STREAM-AT] with a Reliable Size set to at least the size of the WebTransport header when resetting a WebTransport data stream. This ensures that the ID field associating the data stream with a WebTransport session is always delivered. (4.3)\n\n%% WebTransport implementations SHALL forward the error code for a stream associated with a known session to the application that owns that session (4.3)\n\n%% 4.4. Datagrams\n\ndatagrams(Config) ->\n\tdoc(\"Both endpoints can send and receive datagrams. (draft_webtrans_http3 4.4)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\tQuarterID = SessionID div 4,\n\t%% Send a Hello datagram.\n\t{ok, _} = quicer:send_dgram(Conn, <<0:2, QuarterID:6, \"Hello\">>),\n\t%% Receive a Hello datagram back.\n\t{datagram, SessionID, <<\"Hello\">>} = do_receive_datagram(Conn),\n\tok.\n\n%% @todo datagrams_via_capsule?\n\n%% 4.5. Buffering Incoming Streams and Datagrams\n\n%% To handle this case (out of order stream_open/CONNECT), WebTransport endpoints SHOULD buffer streams and datagrams until those can be associated with an established session. (4.5)\n\n%% To avoid resource exhaustion, the endpoints MUST limit the number of buffered streams and datagrams. When the number of buffered streams is exceeded, a stream SHALL be closed by sending a RESET_STREAM and/or STOP_SENDING with the WT_BUFFERED_STREAM_REJECTED error code. When the number of buffered datagrams is exceeded, a datagram SHALL be dropped. It is up to an implementation to choose what stream or datagram to discard. (4.5)\n\n%% 4.6. Interaction with HTTP/3 GOAWAY frame\n\n%% A client receiving GOAWAY cannot initiate CONNECT requests for new WebTransport sessions on that HTTP/3 connection; it must open a new HTTP/3 connection to initiate new WebTransport sessions with the same peer. (4.6)\n\n%% An HTTP/3 GOAWAY frame is also a signal to applications to initiate shutdown for all WebTransport sessions. (4.6)\n\n%% @todo Currently receipt of a GOAWAY frame immediately ends the connection.\n%%       We want to allow WT sessions to gracefully shut down before that.\n%goaway_client(Config) ->\n%\tdoc(\"The HTTP/3 client can initiate the close of all WT sessions \"\n%\t\t\"by sending a GOAWAY frame. (draft_webtrans_http3 4.6)\"),\n%\t%% Connect to the WebTransport server.\n%\t#{\n%\t\tconn := Conn,\n%\t\tconnect_stream_ref := ConnectStreamRef,\n%\t\tsession_id := SessionID\n%\t} = do_webtransport_connect(Config),\n%\t%% Open a control stream and send a GOAWAY frame.\n%\t{ok, ControlRef} = quicer:start_stream(Conn,\n%\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n%\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n%\t{ok, _} = quicer:send(ControlRef, [\n%\t\t<<0>>, %% CONTROL stream.\n%\t\tSettingsBin,\n%\t\t<<7>>, %% GOAWAY frame.\n%\t\tcow_http3:encode_int(1),\n%\t\tcow_http3:encode_int(0)\n%\t]),\n%\t%% Receive a datagram indicating processing by the WT handler.\n%\t{datagram, SessionID, <<\"TEST:close_initiated\">>} = do_receive_datagram(Conn),\n%\tok.\n\nwt_drain_session_client(Config) ->\n\tdoc(\"The WT client can initiate the close of a single session. \"\n\t\t\"(draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Send the WT_DRAIN_SESSION capsule on the CONNECT stream.\n\t{ok, _} = quicer:send(ConnectStreamRef, cow_capsule:wt_drain_session()),\n\t%% Receive a datagram indicating processing by the WT handler.\n\t{datagram, SessionID, <<\"TEST:close_initiated\">>} = do_receive_datagram(Conn),\n\tok.\n\nwt_drain_session_server(Config) ->\n\tdoc(\"The WT server can initiate the close of a single session. \"\n\t\t\"(draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send a special instruction to make it initiate the close.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6, \"TEST:initiate_close\">>),\n\t%% Receive the WT_DRAIN_SESSION capsule on the CONNECT stream.\n\tDrainWTSessionCapsule = cow_capsule:wt_drain_session(),\n\t{nofin, DrainWTSessionCapsule} = do_receive_data(ConnectStreamRef),\n\tok.\n\nwt_drain_session_continue_client(Config) ->\n\tdoc(\"After the WT client has initiated the close of the session, \"\n\t\t\"both client and server can continue using the session and \"\n\t\t\"open new streams. (draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Send the WT_DRAIN_SESSION capsule on the CONNECT stream.\n\t{ok, _} = quicer:send(ConnectStreamRef, cow_capsule:wt_drain_session()),\n\t%% Receive a datagram indicating processing by the WT handler.\n\t{datagram, SessionID, <<\"TEST:close_initiated\">>} = do_receive_datagram(Conn),\n\t%% Create a new bidi stream, send Hello, get Hello back.\n\t{ok, ContinueStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(ContinueStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6, \"Hello\">>),\n\t{nofin, <<\"Hello\">>} = do_receive_data(ContinueStreamRef),\n\tok.\n\nwt_drain_session_continue_server(Config) ->\n\tdoc(\"After the WT server has initiated the close of the session, \"\n\t\t\"both client and server can continue using the session and \"\n\t\t\"open new streams. (draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send a special instruction to make it initiate the close.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6, \"TEST:initiate_close\">>),\n\t%% Receive the WT_DRAIN_SESSION capsule on the CONNECT stream.\n\tDrainWTSessionCapsule = cow_capsule:wt_drain_session(),\n\t{nofin, DrainWTSessionCapsule} = do_receive_data(ConnectStreamRef),\n\t%% Create a new bidi stream, send Hello, get Hello back.\n\t{ok, ContinueStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(ContinueStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6, \"Hello\">>),\n\t{nofin, <<\"Hello\">>} = do_receive_data(ContinueStreamRef),\n\tok.\n\n%% @todo 4.7. Use of Keying Material Exporters\n\n%% 5. Flow Control\n\n%% 5.1. Limiting the Number of Simultaneous Sessions\n\n%% This document defines a SETTINGS_WT_MAX_SESSIONS parameter that allows the server to limit the maximum number of concurrent WebTransport sessions on a single HTTP/3 connection. The client MUST NOT open more simultaneous sessions than indicated in the server SETTINGS parameter. The server MUST NOT close the connection if the client opens sessions exceeding this limit, as the client and the server do not have a consistent view of how many sessions are open due to the asynchronous nature of the protocol; instead, it MUST reset all of the CONNECT streams it is not willing to process with the H3_REQUEST_REJECTED status defined in [HTTP3]. (5.1)\n\n%% 5.2. Limiting the Number of Streams Within a Session\n\n%% The WT_MAX_STREAMS capsule (Section 5.6.1) establishes a limit on the number of streams within a WebTransport session. (5.2)\n\n%% Note that the CONNECT stream for the session is not included in either the bidirectional or the unidirectional stream limits (5.2)\n\n%% The session-level stream limit applies in addition to the QUIC MAX_STREAMS frame, which provides a connection-level stream limit. New streams can only be created within the session if both the stream- and the connection-level limit permit (5.2)\n\n%% The WT_STREAMS_BLOCKED capsule (Section 5.7) can be sent to indicate that an endpoint was unable to create a stream due to the session-level stream limit. (5.2)\n\n%% Note that enforcing this limit requires reliable resets for stream headers so that both endpoints can agree on the number of streams that are open. (5.2)\n\n%% 5.3. Data Limits\n\n%% The WT_MAX_DATA capsule (Section 5.8) establishes a limit on the amount of data that can be sent within a WebTransport session. This limit counts all data that is sent on streams of the corresponding type, excluding the stream header (see Section 4.1 and Section 4.2). (5.3)\n\n%% Implementing WT_MAX_DATA requires that the QUIC stack provide the WebTransport implementation with information about the final size of streams; see { {Section 4.5 of !RFC9000}}. This allows both endpoints to agree on how much data was consumed by that stream, although the stream header exclusion above applies. (5.3)\n\n%% The WT_DATA_BLOCKED capsule (Section 5.9) can be sent to indicate that an endpoint was unable to send data due to a limit set by the WT_MAX_DATA capsule. (5.3)\n\n%% The WT_MAX_STREAM_DATA and WT_STREAM_DATA_BLOCKED capsules (Part XX of [I-D.ietf-webtrans-http2]) are not used and so are prohibited. Endpoints MUST treat receipt of a WT_MAX_STREAM_DATA or a WT_STREAM_DATA_BLOCKED capsule as a session error. (5.3)\n\n%% 5.4. Flow Control and Intermediaries\n\n%% In practice, an intermediary that translates flow control signals between similar WebTransport protocols, such as between two HTTP/3 connections, can often simply reexpress the same limits received on one connection directly on the other connection. (5.4)\n\n%% 5.5. Flow Control SETTINGS\n\n%% WT_MAX_STREAMS via SETTINGS_WT_INITIAL_MAX_STREAMS_UNI and SETTINGS_WT_INITIAL_MAX_STREAMS_BIDI (5.5)\n\n%% WT_MAX_DATA via SETTINGS_WT_INITIAL_MAX_DATA (5.5)\n\n%% 5.6. Flow Control Capsules\n\n%% 5.6.1. WT_MAX_STREAMS Capsule\n\n%% An HTTP capsule [HTTP-DATAGRAM] called WT_MAX_STREAMS is introduced to inform the peer of the cumulative number of streams of a given type it is permitted to open. A WT_MAX_STREAMS capsule with a type of 0x190B4D3F applies to bidirectional streams, and a WT_MAX_STREAMS capsule with a type of 0x190B4D40 applies to unidirectional streams. (5.6.1)\n\n%% Note that, because Maximum Streams is a cumulative value representing the total allowed number of streams, including previously closed streams, endpoints repeatedly send new WT_MAX_STREAMS capsules with increasing Maximum Streams values as streams are opened. (5.6.1)\n\n%% Maximum Streams: A count of the cumulative number of streams of the corresponding type that can be opened over the lifetime of the session. This value cannot exceed 260, as it is not possible to encode stream IDs larger than 262-1. (5.6.1)\n\n%% An endpoint MUST NOT open more streams than permitted by the current stream limit set by its peer. (5.6.1)\n\n%% Note that this limit includes streams that have been closed as well as those that are open. (5.6.1)\n\n%% Initial values for these limits MAY be communicated by sending non-zero values for SETTINGS_WT_INITIAL_MAX_STREAMS_UNI and SETTINGS_WT_INITIAL_MAX_STREAMS_BIDI. (5.6.1)\n\n%% 5.7. WT_STREAMS_BLOCKED Capsule\n\n%% A sender SHOULD send a WT_STREAMS_BLOCKED capsule (type=0x190B4D43 for bidi or 0x190B4D44 for unidi) when it wishes to open a stream but is unable to do so due to the maximum stream limit set by its peer. (5.7)\n\n%% 5.8. WT_MAX_DATA Capsule\n\n%% An HTTP capsule [HTTP-DATAGRAM] called WT_MAX_DATA (type=0x190B4D3D) is introduced to inform the peer of the maximum amount of data that can be sent on the WebTransport session as a whole. (5.8)\n\n%% This limit counts all data that is sent on streams of the corresponding type, excluding the stream header (see Section 4.1 and Section 4.2). Implementing WT_MAX_DATA requires that the QUIC stack provide the WebTransport implementation with information about the final size of streams; see Section 4.5 of [RFC9000]. (5.8)\n\n%% All data sent in WT_STREAM capsules counts toward this limit. The sum of the lengths of Stream Data fields in WT_STREAM capsules MUST NOT exceed the value advertised by a receiver. (5.8)\n\n%% The initial value for this limit MAY be communicated by sending a non-zero value for SETTINGS_WT_INITIAL_MAX_DATA. (5.8)\n\n%% 5.9. WT_DATA_BLOCKED Capsule\n\n%% A sender SHOULD send a WT_DATA_BLOCKED capsule (type=0x190B4D41) when it wishes to send data but is unable to do so due to WebTransport session-level flow control. (5.9)\n\n%% WT_DATA_BLOCKED capsules can be used as input to tuning of flow control algorithms. (5.9)\n\n%% 6. Session Termination\n\n%% A WebTransport session over HTTP/3 is considered terminated when either of the following conditions is met:\n%% * the CONNECT stream is closed, either cleanly or abruptly, on either side; or\n%% * a WT_CLOSE_SESSION capsule is either sent or received.\n%% (6)\n\nwt_close_session_client(Config) ->\n\tdoc(\"The WT client can close a single session. (draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconnect_stream_ref := ConnectStreamRef\n\t} = do_webtransport_connect(Config),\n\t%% Send the WT_CLOSE_SESSION capsule on the CONNECT stream.\n\t{ok, _} = quicer:send(ConnectStreamRef,\n\t\tcow_capsule:wt_close_session(0, <<>>),\n\t\t?QUIC_SEND_FLAG_FIN),\n\t%% Normally we should also stop reading but in order to detect\n\t%% that the server stops the stream we must not otherwise the\n\t%% stream will be de facto closed on our end.\n\t%%\n\t%% The recipient must close or reset the stream in response.\n\treceive\n\t\t{quic, stream_closed, ConnectStreamRef, _} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror({timeout, waiting_for_stream_closed})\n\tend.\n\nwt_close_session_server(Config) ->\n\tdoc(\"The WT server can close a single session. (draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send a special instruction to make it initiate the close.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6, \"TEST:close\">>),\n\t%% Receive the WT_CLOSE_SESSION capsule on the CONNECT stream.\n\tCloseWTSessionCapsule = cow_capsule:wt_close_session(0, <<>>),\n\t{fin, CloseWTSessionCapsule} = do_receive_data(ConnectStreamRef),\n\tok.\n\nwt_session_gone_client(Config) ->\n\tdoc(\"Upon learning that the session has been terminated, \"\n\t\t\"the WT server must reset associated streams with the \"\n\t\t\"WEBTRANSPORT_SESSION_GONE error code. (draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a unidi stream.\n\t{ok, LocalUnidiStreamRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(LocalUnidiStreamRef,\n\t\t<<1:2, 16#54:14, 0:2, SessionID:6, \"Hello\">>),\n\t%% Accept an identical unidi stream.\n\t{unidi, RemoteUnidiStreamRef} = do_receive_new_stream(),\n\t{nofin, <<1:2, 16#54:14, 0:2, SessionID:6>>} = do_receive_data(RemoteUnidiStreamRef),\n\t{nofin, <<\"Hello\">>} = do_receive_data(RemoteUnidiStreamRef),\n\t%% Create a bidi stream, send a special instruction\n\t%% to make the server create another bidi stream.\n\t{ok, LocalBidiStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(LocalBidiStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6, \"TEST:open_bidi\">>),\n\t%% Accept the bidi stream and receive the data.\n\t{bidi, RemoteBidiStreamRef} = do_receive_new_stream(),\n\t{nofin, <<1:2, 16#41:14, 0:2, SessionID:6>>} = do_receive_data(RemoteBidiStreamRef),\n\t{ok, _} = quicer:send(RemoteBidiStreamRef, <<\"Hello\">>),\n\t{nofin, <<\"Hello\">>} = do_receive_data(RemoteBidiStreamRef),\n\t%% Send the WT_CLOSE_SESSION capsule on the CONNECT stream.\n\t{ok, _} = quicer:send(ConnectStreamRef,\n\t\tcow_capsule:wt_close_session(0, <<>>),\n\t\t?QUIC_SEND_FLAG_FIN),\n\t%% All streams from that WT session have been aborted.\n\t#{reason := wt_session_gone} = do_wait_stream_aborted(LocalUnidiStreamRef),\n\t#{reason := wt_session_gone} = do_wait_stream_aborted(RemoteUnidiStreamRef),\n\t#{reason := wt_session_gone} = do_wait_stream_aborted(LocalBidiStreamRef),\n\t#{reason := wt_session_gone} = do_wait_stream_aborted(RemoteBidiStreamRef),\n\tok.\n\nwt_session_gone_server(Config) ->\n\tdoc(\"After the session has been terminated by the WT server, \"\n\t\t\"the WT server must reset associated streams with the \"\n\t\t\"WT_SESSION_GONE error code. (draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a unidi stream.\n\t{ok, LocalUnidiStreamRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(LocalUnidiStreamRef,\n\t\t<<1:2, 16#54:14, 0:2, SessionID:6, \"Hello\">>),\n\t%% Accept an identical unidi stream.\n\t{unidi, RemoteUnidiStreamRef} = do_receive_new_stream(),\n\t{nofin, <<1:2, 16#54:14, 0:2, SessionID:6>>} = do_receive_data(RemoteUnidiStreamRef),\n\t{nofin, <<\"Hello\">>} = do_receive_data(RemoteUnidiStreamRef),\n\t%% Create a bidi stream, send a special instruction\n\t%% to make the server create another bidi stream.\n\t{ok, LocalBidiStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(LocalBidiStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6, \"TEST:open_bidi\">>),\n\t%% Accept the bidi stream and receive the data.\n\t{bidi, RemoteBidiStreamRef} = do_receive_new_stream(),\n\t{nofin, <<1:2, 16#41:14, 0:2, SessionID:6>>} = do_receive_data(RemoteBidiStreamRef),\n\t{ok, _} = quicer:send(RemoteBidiStreamRef, <<\"Hello\">>),\n\t{nofin, <<\"Hello\">>} = do_receive_data(RemoteBidiStreamRef),\n\n\t%% Send a special instruction to make the server initiate the close.\n\t{ok, _} = quicer:send(LocalBidiStreamRef, <<\"TEST:close\">>),\n\t%% Receive the WT_CLOSE_SESSION capsule on the CONNECT stream.\n\tCloseWTSessionCapsule = cow_capsule:wt_close_session(0, <<>>),\n\t{fin, CloseWTSessionCapsule} = do_receive_data(ConnectStreamRef),\n\t%% All streams from that WT session have been aborted.\n\t#{reason := wt_session_gone} = do_wait_stream_aborted(LocalUnidiStreamRef),\n\t#{reason := wt_session_gone} = do_wait_stream_aborted(RemoteUnidiStreamRef),\n\t#{reason := wt_session_gone} = do_wait_stream_aborted(LocalBidiStreamRef),\n\t#{reason := wt_session_gone} = do_wait_stream_aborted(RemoteBidiStreamRef),\n\tok.\n\n%% Application Error Message: A UTF-8 encoded error message string provided by the application closing the session. The message takes up the remainder of the capsule, and its length MUST NOT exceed 1024 bytes. (6)\n%% @todo What if it's larger?\n\nwt_close_session_app_code_msg_client(Config) ->\n\tdoc(\"The WT client can close a single session with an application error code \"\n\t\t\"and an application error message. (draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send a special instruction to make it propagate events.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\tEventPidBin = term_to_binary(self()),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6,\n\t\t\"TEST:event_pid:\", EventPidBin/binary>>),\n\t%% Send the WT_CLOSE_SESSION capsule on the CONNECT stream.\n\t{ok, _} = quicer:send(ConnectStreamRef,\n\t\tcow_capsule:wt_close_session(17, <<\"seventeen\">>),\n\t\t?QUIC_SEND_FLAG_FIN),\n\t%% @todo Stop reading from the CONNECt stream too. (STOP_SENDING)\n\t%% Receive the terminate event from the WT handler.\n\treceive\n\t\t{'$wt_echo_h', terminate, {closed, 17, <<\"seventeen\">>}, _, _} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror({timeout, waiting_for_terminate_event})\n\tend.\n\nwt_close_session_app_code_server(Config) ->\n\tdoc(\"The WT server can close a single session with an application error code. \"\n\t\t\"(draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send a special instruction to make it initiate the close.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6,\n\t\t\"TEST:close_app_code\">>),\n\t%% Receive the WT_CLOSE_SESSION capsule on the CONNECT stream.\n\tCloseWTSessionCapsule = cow_capsule:wt_close_session(1234567890, <<>>),\n\t{fin, CloseWTSessionCapsule} = do_receive_data(ConnectStreamRef),\n\tok.\n\nwt_close_session_app_code_msg_server(Config) ->\n\tdoc(\"The WT server can close a single session with an application error code \"\n\t\t\"and an application error message. (draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send a special instruction to make it initiate the close.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6,\n\t\t\"TEST:close_app_code_msg\">>),\n\t%% Receive the WT_CLOSE_SESSION capsule on the CONNECT stream.\n\tCloseWTSessionCapsule = iolist_to_binary(cow_capsule:wt_close_session(1234567890,\n\t\t<<\"onetwothreefourfivesixseveneightnineten\">>)),\n\t{fin, CloseWTSessionCapsule} = do_receive_data(ConnectStreamRef),\n\tok.\n\n%% An endpoint that sends a WT_CLOSE_SESSION capsule MUST immediately send a FIN. The endpoint MAY send a STOP_SENDING to indicate it is no longer reading from the CONNECT stream. The recipient MUST either close or reset the stream in response. (6)\n%% @todo wt_close_session_server_fin\n%% @todo The part about close/reset should be tested in wt_close_session_client.\n\n%% If any additional stream data is received on the CONNECT stream after receiving a WT_CLOSE_SESSION capsule, the stream MUST be reset with code H3_MESSAGE_ERROR. (6)\n%% @todo wt_close_session_followed_by_data\n\nconnect_stream_closed_cleanly_fin(Config) ->\n\tdoc(\"The WT client closing the CONNECT stream cleanly \"\n\t\t\"is equivalent to a capsule with an application error code of 0 \"\n\t\t\"and an empty error string. (draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send a special instruction to make it propagate events.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\tEventPidBin = term_to_binary(self()),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6,\n\t\t\"TEST:event_pid:\", EventPidBin/binary>>),\n\t{nofin, <<\"event_pid_received\">>} = do_receive_data(LocalStreamRef),\n\t%% Cleanly terminate the CONNECT stream.\n\t{ok, _} = quicer:send(ConnectStreamRef, <<>>, ?QUIC_SEND_FLAG_FIN),\n\t%% Receive the terminate event from the WT handler.\n\treceive\n\t\t{'$wt_echo_h', terminate, {closed, 0, <<>>}, _, _} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror({timeout, waiting_for_terminate_event})\n\tend.\n\nconnect_stream_closed_cleanly_shutdown(Config) ->\n\tdoc(\"The WT client closing the CONNECT stream cleanly \"\n\t\t\"is equivalent to a capsule with an application error code of 0 \"\n\t\t\"and an empty error string. (draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send a special instruction to make it propagate events.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\tEventPidBin = term_to_binary(self()),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6,\n\t\t\"TEST:event_pid:\", EventPidBin/binary>>),\n\t{nofin, <<\"event_pid_received\">>} = do_receive_data(LocalStreamRef),\n\t%% Cleanly terminate the CONNECT stream.\n\t_ = quicer:shutdown_stream(ConnectStreamRef),\n\t%% Receive the terminate event from the WT handler.\n\treceive\n\t\t{'$wt_echo_h', terminate, {closed, 0, <<>>}, _, _} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror({timeout, waiting_for_terminate_event})\n\tend.\n\nconnect_stream_closed_abruptly(Config) ->\n\tdoc(\"The WT client may close the CONNECT stream abruptly. \"\n\t\t\"(draft_webtrans_http3 4.6)\"),\n\t%% Connect to the WebTransport server.\n\t#{\n\t\tconn := Conn,\n\t\tconnect_stream_ref := ConnectStreamRef,\n\t\tsession_id := SessionID\n\t} = do_webtransport_connect(Config),\n\t%% Create a bidi stream, send a special instruction to make it propagate events.\n\t{ok, LocalStreamRef} = quicer:start_stream(Conn, #{}),\n\tEventPidBin = term_to_binary(self()),\n\t{ok, _} = quicer:send(LocalStreamRef, <<1:2, 16#41:14, 0:2, SessionID:6,\n\t\t\"TEST:event_pid:\", EventPidBin/binary>>),\n\t{nofin, <<\"event_pid_received\">>} = do_receive_data(LocalStreamRef),\n\t%% Abruptly terminate the CONNECT stream.\n\t_ = quicer:shutdown_stream(ConnectStreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT,\n\t\t0, infinity),\n\t%% Receive the terminate event from the WT handler.\n\treceive\n\t\t%% @todo It would be good to forward a stream error as well\n\t\t%%       so that a WT error can be sent, but I have been unsuccessful.\n\t\t{'$wt_echo_h', terminate, closed_abruptly, _, _} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror({timeout, waiting_for_terminate_event})\n\tend.\n\n%% @todo This one is about gracefully closing HTTP/3 connection with WT sessions.\n%% the endpoint SHOULD wait until all CONNECT streams have been closed by the peer before sending the CONNECTION_CLOSE (6)\n\n%% Helpers.\n\ndo_webtransport_connect(Config) ->\n\tdo_webtransport_connect(Config, []).\n\ndo_webtransport_connect(Config, ExtraHeaders) ->\n\t%% Connect to server.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config, #{\n\t\tpeer_unidi_stream_count => 100,\n\t\tdatagram_send_enabled => 1,\n\t\tdatagram_receive_enabled => 1\n\t}),\n\t%% Confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Confirm that SETTINGS_WT_MAX_SESSIONS >= 1.\n\t#{wt_max_sessions := WTMaxSessions} = Settings,\n\ttrue = WTMaxSessions >= 1,\n\t%% Confirm that SETTINGS_H3_DATAGRAM = 1.\n\t#{h3_datagram := true} = Settings,\n\t%% Confirm that QUIC's max_datagram_size > 0.\n\treceive {quic, dgram_state_changed, Conn, DatagramState} ->\n\t\t#{\n\t\t\tdgram_max_len := DatagramMaxLen,\n\t\t\tdgram_send_enabled := DatagramSendEnabled\n\t\t} = DatagramState,\n\t\ttrue = DatagramMaxLen > 0,\n\t\ttrue = DatagramSendEnabled,\n\t\tok\n\tafter 5000 ->\n\t\terror({timeout, waiting_for_datagram_state_change})\n\tend,\n\t%% Send a CONNECT :protocol request to upgrade the stream to Websocket.\n\t{ok, ConnectStreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"webtransport\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/wt\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"origin\">>, <<\"https://localhost\">>}\n\t|ExtraHeaders], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(ConnectStreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% Receive a 200 response.\n\t{nofin, Data} = do_receive_data(ConnectStreamRef),\n\t{HLenEnc, HLenBits} = rfc9114_SUITE:do_guess_int_encoding(Data),\n\t<<\n\t\t1, %% HEADERS frame.\n\t\tHLenEnc:2, HLen:HLenBits,\n\t\tEncodedResponse:HLen/bytes\n\t>> = Data,\n\t{ok, DecodedResponse, _DecData, _DecSt}\n\t\t= cow_qpack:decode_field_section(EncodedResponse, 0, cow_qpack:init(decoder)),\n\t#{<<\":status\">> := <<\"200\">>} = maps:from_list(DecodedResponse),\n\t%% Retrieve the Session ID.\n\t{ok, SessionID} = quicer:get_stream_id(ConnectStreamRef),\n\t%% Accept QPACK streams to avoid conflicts with unidi streams from tests.\n\tUnidi1 = rfc9114_SUITE:do_accept_qpack_stream(Conn),\n\tUnidi2 = rfc9114_SUITE:do_accept_qpack_stream(Conn),\n\t%% Done.\n\t#{\n\t\tconn => Conn,\n\t\tconnect_stream_ref => ConnectStreamRef,\n\t\tsession_id => SessionID,\n\t\tresp_headers => DecodedResponse,\n\t\tenc_or_dec1 => Unidi1,\n\t\tenc_or_dec2 => Unidi2\n\t}.\n\ndo_receive_new_stream() ->\n\treceive\n\t\t{quic, new_stream, StreamRef, #{flags := Flags}} ->\n\t\t\tok = quicer:setopt(StreamRef, active, true),\n\t\t\tcase quicer:is_unidirectional(Flags) of\n\t\t\t\ttrue -> {unidi, StreamRef};\n\t\t\t\tfalse -> {bidi, StreamRef}\n\t\t\tend\n\tafter 5000 ->\n\t\terror({timeout, waiting_for_stream})\n\tend.\n\ndo_receive_data(StreamRef) ->\n\treceive {quic, Data, StreamRef, #{flags := Flags}} ->\n\t\tIsFin = case Flags band ?QUIC_RECEIVE_FLAG_FIN of\n\t\t\t?QUIC_RECEIVE_FLAG_FIN -> fin;\n\t\t\t_ -> nofin\n\t\tend,\n\t\t{IsFin, Data}\n\tafter 5000 ->\n\t\terror({timeout, waiting_for_data})\n\tend.\n\ndo_receive_datagram(Conn) ->\n\treceive {quic, <<0:2, QuarterID:6, Data/bits>>, Conn, Flags} when is_integer(Flags) ->\n\t\t{datagram, QuarterID * 4, Data}\n\tafter 5000 ->\n\t\tct:pal(\"~p\", [process_info(self(), messages)]),\n\t\terror({timeout, waiting_for_datagram})\n\tend.\n\n-endif.\n"
  },
  {
    "path": "test/examples_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(examples_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\n%% ct.\n\nall() ->\n\tct_helper:all(?MODULE).\n\ninit_per_suite(Config) ->\n\t%% Remove environment variables inherited from Erlang.mk.\n\tos:unsetenv(\"ERLANG_MK_TMP\"),\n\tos:unsetenv(\"APPS_DIR\"),\n\tos:unsetenv(\"DEPS_DIR\"),\n\tos:unsetenv(\"ERL_LIBS\"),\n\t%% Clone and build Cowboy, Cowlib and Ranch only once and\n\t%% reuse the same build across all tests.\n\tMake = do_find_make_cmd(),\n\tCommonDir = config(priv_dir, Config),\n\tct:log(\"~ts~n\", [os:cmd(\"git clone --depth 1 https://github.com/ninenines/cowboy \"\n\t\t++ CommonDir ++ \"cowboy\")]),\n\tct:log(\"~ts~n\", [os:cmd(Make ++ \" -C \" ++ CommonDir ++ \"cowboy distclean\")]),\n\tct:log(\"~ts~n\", [os:cmd(Make ++ \" -C \" ++ CommonDir ++ \"cowboy DEPS_DIR=\" ++ CommonDir)]),\n\tConfig.\n\nend_per_suite(_) ->\n\tok.\n\n%% Find GNU Make.\n\ndo_find_make_cmd() ->\n\tcase os:getenv(\"MAKE\") of\n\t\tfalse ->\n\t\t\tcase os:find_executable(\"gmake\") of\n\t\t\t\tfalse -> \"make\";\n\t\t\t\tCmd   -> Cmd\n\t\t\tend;\n\t\tCmd ->\n\t\t\tCmd\n\tend.\n\n%% Compile, start and stop releases.\n\ndo_get_paths(Example0) ->\n\tExample = atom_to_list(Example0),\n\t{ok, CWD} = file:get_cwd(),\n\tDir = CWD ++ \"/../../examples/\" ++ Example,\n\tRel = Dir ++ \"/_rel/\" ++ Example ++ \"_example/bin/\" ++ Example ++ \"_example\",\n\tLog = Dir ++ \"/_rel/\" ++ Example ++ \"_example/log/erlang.log.1\",\n\t{Dir, Rel, Log}.\n\ndo_compile_and_start(Example, Config) ->\n\tMake = do_find_make_cmd(),\n\t{Dir, Rel, _} = do_get_paths(Example),\n\tct:log(\"~ts~n\", [os:cmd(Make ++ \" -C \" ++ Dir ++ \" distclean\")]),\n\t%% We use a common build for Cowboy, Cowlib and Ranch to speed things up.\n\tCommonDir = config(priv_dir, Config),\n\tct:log(\"~ts~n\", [os:cmd(\"mkdir \" ++ Dir ++ \"/deps\")]),\n\tct:log(\"~ts~n\", [os:cmd(\"ln -s \" ++ CommonDir ++ \"cowboy \" ++ Dir ++ \"/deps/cowboy\")]),\n\tct:log(\"~ts~n\", [os:cmd(\"ln -s \" ++ CommonDir ++ \"cowlib \" ++ Dir ++ \"/deps/cowlib\")]),\n\tct:log(\"~ts~n\", [os:cmd(\"ln -s \" ++ CommonDir ++ \"ranch \" ++ Dir ++ \"/deps/ranch\")]),\n\t%% TERM=dumb disables relx coloring.\n\tct:log(\"~ts~n\", [os:cmd(Make ++ \" -C \" ++ Dir ++ \" TERM=dumb\")]),\n\tct:log(\"~ts~n\", [os:cmd(Rel ++ \" stop\")]),\n\tct:log(\"~ts~n\", [os:cmd(Rel ++ \" daemon\")]),\n\ttimer:sleep(2000),\n\tok.\n\ndo_stop(Example) ->\n\t{_, Rel, Log} = do_get_paths(Example),\n\tct:log(\"~ts~n\", [os:cmd(Rel ++ \" stop\")]),\n\tct:log(\"~ts~n\", [element(2, file:read_file(Log))]),\n\tok.\n\n%% Fetch a response.\n\ndo_get(Transport, Protocol, Path, Config) ->\n\tdo_get(Transport, Protocol, Path, [], Config).\n\ndo_get(Transport, Protocol, Path, ReqHeaders, Config) ->\n\tPort = case Transport of\n\t\ttcp -> 8080;\n\t\tssl -> 8443\n\tend,\n\tConnPid = gun_open([{port, Port}, {type, Transport}, {protocol, Protocol}|Config]),\n\tRef = gun:get(ConnPid, Path, ReqHeaders),\n\tcase gun:await(ConnPid, Ref) of\n\t\t{response, nofin, Status, RespHeaders} ->\n\t\t\t{ok, Body} = gun:await_body(ConnPid, Ref),\n\t\t\t{Status, RespHeaders, Body};\n\t\t{response, fin, Status, RespHeaders} ->\n\t\t\t{Status, RespHeaders, <<>>}\n\tend.\n\n%% TCP and SSL Hello World.\n\nhello_world(Config) ->\n\tdoc(\"Hello World example.\"),\n\ttry\n\t\tdo_compile_and_start(hello_world, Config),\n\t\tdo_hello_world(tcp, http, Config),\n\t\tdo_hello_world(tcp, http2, Config)\n\tafter\n\t\tdo_stop(hello_world)\n\tend.\n\nssl_hello_world(Config) ->\n\tdoc(\"SSL Hello World example.\"),\n\ttry\n\t\tdo_compile_and_start(ssl_hello_world, Config),\n\t\tdo_hello_world(ssl, http, Config),\n\t\tdo_hello_world(ssl, http2, Config)\n\tafter\n\t\tdo_stop(ssl_hello_world)\n\tend.\n\ndo_hello_world(Transport, Protocol, Config) ->\n\t{200, _, <<\"Hello world!\">>} = do_get(Transport, Protocol, \"/\", Config),\n\tok.\n\n%% Chunked Hello World.\n\nchunked_hello_world(Config) ->\n\tdoc(\"Chunked Hello World example.\"),\n\ttry\n\t\tdo_compile_and_start(chunked_hello_world, Config),\n\t\tdo_chunked_hello_world(tcp, http, Config),\n\t\tdo_chunked_hello_world(tcp, http2, Config)\n\tafter\n\t\tdo_stop(chunked_hello_world)\n\tend.\n\ndo_chunked_hello_world(Transport, Protocol, Config) ->\n\tConnPid = gun_open([{port, 8080}, {type, Transport}, {protocol, Protocol}|Config]),\n\tRef = gun:get(ConnPid, \"/\"),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t%% We expect to receive a chunk every second, three total.\n\t{data, nofin, <<\"Hello\\r\\n\">>} = gun:await(ConnPid, Ref, 2000),\n\t{data, nofin, <<\"World\\r\\n\">>} = gun:await(ConnPid, Ref, 2000),\n\t{data, IsFin, <<\"Chunked!\\r\\n\">>} = gun:await(ConnPid, Ref, 2000),\n\t%% We may get an extra empty chunk (last chunk for HTTP/1.1,\n\t%% empty DATA frame with the FIN bit set for HTTP/2).\n\tcase IsFin of\n\t\tfin -> ok;\n\t\tnofin ->\n\t\t\t{data, fin, <<>>} = gun:await(ConnPid, Ref, 500),\n\t\t\tok\n\tend.\n\n%% Compressed responses.\n\ncompress_response(Config) ->\n\tdoc(\"Compressed response example.\"),\n\ttry\n\t\tdo_compile_and_start(compress_response, Config),\n\t\tdo_compress_response(tcp, http, Config),\n\t\tdo_compress_response(tcp, http2, Config)\n\tafter\n\t\tdo_stop(compress_response)\n\tend.\n\ndo_compress_response(Transport, Protocol, Config) ->\n\t{200, Headers, Body} = do_get(Transport, Protocol, \"/\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}], Config),\n\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\t_ = zlib:gunzip(Body),\n\tok.\n\n%% Cookie.\n\ncookie(Config) ->\n\tdoc(\"Cookie example.\"),\n\ttry\n\t\tdo_compile_and_start(cookie, Config),\n\t\tdo_cookie(tcp, http, Config),\n\t\tdo_cookie(tcp, http2, Config)\n\tafter\n\t\tdo_stop(cookie)\n\tend.\n\ndo_cookie(Transport, Protocol, Config) ->\n\t{200, _, One} = do_get(Transport, Protocol, \"/\", Config),\n\t{200, _, Two} = do_get(Transport, Protocol, \"/\", [{<<\"cookie\">>, <<\"server=abcdef\">>}], Config),\n\ttrue = One =/= Two,\n\tok.\n\n%% Echo GET.\n\necho_get(Config) ->\n\tdoc(\"GET parameter echo example.\"),\n\ttry\n\t\tdo_compile_and_start(echo_get, Config),\n\t\tdo_echo_get(tcp, http, Config),\n\t\tdo_echo_get(tcp, http2, Config)\n\tafter\n\t\tdo_stop(echo_get)\n\tend.\n\ndo_echo_get(Transport, Protocol, Config) ->\n\t{200, _, <<\"this is fun\">>} = do_get(Transport, Protocol, \"/?echo=this+is+fun\", Config),\n\t{400, _, _} = do_get(Transport, Protocol, \"/\", Config),\n\tok.\n\n%% Echo POST.\n\necho_post(Config) ->\n\tdoc(\"POST parameter echo example.\"),\n\ttry\n\t\tdo_compile_and_start(echo_post, Config),\n\t\tdo_echo_post(tcp, http, Config),\n\t\tdo_echo_post(tcp, http2, Config)\n\tafter\n\t\tdo_stop(echo_post)\n\tend.\n\ndo_echo_post(Transport, Protocol, Config) ->\n\tConnPid = gun_open([{port, 8080}, {type, Transport}, {protocol, Protocol}|Config]),\n\tRef = gun:post(ConnPid, \"/\", [\n\t\t{<<\"content-type\">>, <<\"application/octet-stream\">>}\n\t], <<\"echo=this+is+fun\">>),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"this is fun\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\n%% Eventsource.\n\neventsource(Config) ->\n\tdoc(\"Eventsource example.\"),\n\ttry\n\t\tdo_compile_and_start(eventsource, Config),\n\t\tdo_eventsource(tcp, http, Config),\n\t\tdo_eventsource(tcp, http2, Config)\n\tafter\n\t\tdo_stop(eventsource)\n\tend.\n\ndo_eventsource(Transport, Protocol, Config) ->\n\tConnPid = gun_open([{port, 8080}, {type, Transport}, {protocol, Protocol}|Config]),\n\tRef = gun:get(ConnPid, \"/eventsource\"),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"text/event-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t%% Receive a few events.\n\t{data, nofin, << \"id: \", _/bits >>} = gun:await(ConnPid, Ref, 2000),\n\t{data, nofin, << \"id: \", _/bits >>} = gun:await(ConnPid, Ref, 2000),\n\t{data, nofin, << \"id: \", _/bits >>} = gun:await(ConnPid, Ref, 2000),\n\tgun:close(ConnPid).\n\n%% REST Hello World.\n\nrest_hello_world(Config) ->\n\tdoc(\"REST Hello World example.\"),\n\ttry\n\t\tdo_compile_and_start(rest_hello_world, Config),\n\t\tdo_rest_hello_world(tcp, http, Config),\n\t\tdo_rest_hello_world(tcp, http2, Config)\n\tafter\n\t\tdo_stop(rest_hello_world)\n\tend.\n\ndo_rest_hello_world(Transport, Protocol, Config) ->\n\t<< \"<html>\", _/bits >> = do_rest_get(Transport, Protocol,\n\t\t\"/\", undefined, undefined, Config),\n\t<< \"REST Hello World as text!\" >> = do_rest_get(Transport, Protocol,\n\t\t\"/\", <<\"text/plain\">>, undefined, Config),\n\t<< \"{\\\"rest\\\": \\\"Hello World!\\\"}\" >> = do_rest_get(Transport, Protocol,\n\t\t\"/\", <<\"application/json\">>, undefined, Config),\n\tnot_acceptable = do_rest_get(Transport, Protocol,\n\t\t\"/\", <<\"text/css\">>, undefined, Config),\n\tok.\n\ndo_rest_get(Transport, Protocol, Path, Accept, Auth, Config) ->\n\tReqHeaders0 = case Accept of\n\t\tundefined -> [];\n\t\t_ -> [{<<\"accept\">>, Accept}]\n\tend,\n\tReqHeaders = case Auth of\n\t\tundefined -> ReqHeaders0;\n\t\t_ -> [{<<\"authorization\">>, [<<\"Basic \">>, base64:encode(Auth)]}|ReqHeaders0]\n\tend,\n\tcase do_get(Transport, Protocol, Path, ReqHeaders, Config) of\n\t\t{200, RespHeaders, Body} ->\n\t\t\tAccept = case Accept of\n\t\t\t\tundefined -> undefined;\n\t\t\t\t_ ->\n\t\t\t\t\t{_, ContentType} = lists:keyfind(<<\"content-type\">>, 1, RespHeaders),\n\t\t\t\t\tContentType\n\t\t\tend,\n\t\t\tBody;\n\t\t{401, _, _} ->\n\t\t\tunauthorized;\n\t\t{406, _, _} ->\n\t\t\tnot_acceptable\n\tend.\n\n%% REST basic auth.\n\nrest_basic_auth(Config) ->\n\tdoc(\"REST basic authorization example.\"),\n\ttry\n\t\tdo_compile_and_start(rest_basic_auth, Config),\n\t\tdo_rest_basic_auth(tcp, http, Config),\n\t\tdo_rest_basic_auth(tcp, http2, Config)\n\tafter\n\t\tdo_stop(rest_basic_auth)\n\tend.\n\ndo_rest_basic_auth(Transport, Protocol, Config) ->\n\tunauthorized = do_rest_get(Transport, Protocol, \"/\", undefined, undefined, Config),\n\t<<\"Hello, Alladin!\\n\">> = do_rest_get(Transport, Protocol, \"/\", undefined, \"Alladin:open sesame\", Config),\n\tok.\n\n%% REST pastebin.\n\nrest_pastebin(Config) ->\n\tdoc(\"REST pastebin example.\"),\n\ttry\n\t\tdo_compile_and_start(rest_pastebin, Config),\n\t\tdo_rest_pastebin(tcp, http, Config),\n\t\tdo_rest_pastebin(tcp, http2, Config)\n\tafter\n\t\tdo_stop(rest_pastebin)\n\tend.\n\ndo_rest_pastebin(Transport, Protocol, Config) ->\n\t%% Existing files.\n\t_ = do_rest_get(Transport, Protocol, \"/\", <<\"text/html\">>, undefined, Config),\n\t_ = do_rest_get(Transport, Protocol, \"/\", <<\"text/plain\">>, undefined, Config),\n\t%% Use POST to upload a new file and download it back.\n\tConnPid = gun_open([{port, 8080}, {type, Transport}, {protocol, Protocol}|Config]),\n\tRef = gun:post(ConnPid, \"/\", [\n\t\t{<<\"content-type\">>, <<\"application/x-www-form-urlencoded\">>}\n\t], <<\"paste=this+is+fun\">>),\n\t%% @todo Not too happy about 303 here,\n\t%% will need to revisit this example.\n\t{response, _, 303, Headers} = gun:await(ConnPid, Ref),\n\t{_, Location} = lists:keyfind(<<\"location\">>, 1, Headers),\n\t<<\"this is fun\">> = do_rest_get(Transport, Protocol, Location, <<\"text/plain\">>, undefined, Config),\n\t<< \"<!DOCTYPE html><html>\", _/bits >>\n\t\t= do_rest_get(Transport, Protocol, Location, <<\"text/html\">>, undefined, Config),\n\tok.\n\n%% File server.\n\nfile_server(Config) ->\n\tdoc(\"File server example with directory listing.\"),\n\ttry\n\t\tdo_compile_and_start(file_server, Config),\n\t\tdo_file_server(tcp, http, Config),\n\t\tdo_file_server(tcp, http2, Config)\n\tafter\n\t\tdo_stop(file_server)\n\tend.\n\ndo_file_server(Transport, Protocol, Config) ->\n\t%% Directory.\n\t{200, DirHeaders, <<\"<!DOCTYPE html><html>\", _/bits >>} = do_get(Transport, Protocol, \"/\", Config),\n\t{_, <<\"text/html; charset=utf-8\">>} = lists:keyfind(<<\"content-type\">>, 1, DirHeaders),\n\t_ = do_rest_get(Transport, Protocol, \"/\", <<\"application/json\">>, undefined, Config),\n\t%% Files.\n\t{200, _, _} = do_get(Transport, Protocol, \"/small.mp4\", Config),\n\t{200, _, _} = do_get(Transport, Protocol, \"/small.ogv\", Config),\n\t{200, _, _} = do_get(Transport, Protocol, \"/test.txt\", Config),\n\t{200, _, _} = do_get(Transport, Protocol, \"/video.html\", Config),\n\t{200, _, _} = do_get(Transport, Protocol,\n\t\t[\"/\", cow_uri:urlencode(<<\"中文\"/utf8>>), \"/\", cow_uri:urlencode(<<\"中文.html\"/utf8>>)],\n\t\tConfig),\n\tok.\n\n%% Markdown middleware.\n\nmarkdown_middleware(Config) ->\n\tdoc(\"Markdown middleware example.\"),\n\ttry\n\t\tdo_compile_and_start(markdown_middleware, Config),\n\t\tdo_markdown_middleware(tcp, http, Config),\n\t\tdo_markdown_middleware(tcp, http2, Config)\n\tafter\n\t\tdo_stop(markdown_middleware)\n\tend.\n\ndo_markdown_middleware(Transport, Protocol, Config) ->\n\t{200, Headers, <<\"<h1>\", _/bits >>} = do_get(Transport, Protocol, \"/video.html\", Config),\n\t{_, <<\"text/html\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\n%% Upload.\n\nupload(Config) ->\n\tdoc(\"Upload example.\"),\n\ttry\n\t\tdo_compile_and_start(upload, Config),\n\t\tdo_upload(tcp, http, Config),\n\t\tdo_upload(tcp, http2, Config)\n\tafter\n\t\tdo_stop(upload)\n\tend.\n\ndo_upload(Transport, Protocol, Config) ->\n\t{200, _, << \"<html>\", _/bits >>} = do_get(Transport, Protocol, \"/\", Config),\n\t%% Use POST to upload a file using multipart.\n\tConnPid = gun_open([{port, 8080}, {type, Transport}, {protocol, Protocol}|Config]),\n\tRef = gun:post(ConnPid, \"/upload\", [\n\t\t{<<\"content-type\">>, <<\"multipart/form-data;boundary=deadbeef\">>}\n\t], <<\n\t\t\"--deadbeef\\r\\n\"\n\t\t\"Content-Disposition: form-data; name=\\\"inputfile\\\"; filename=\\\"test.txt\\\"\\r\\n\"\n\t\t\"Content-Type: text/plain\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Cowboy upload example!\\r\\n\"\n\t\t\"--deadbeef--\">>),\n\t{response, fin, 204, _} = gun:await(ConnPid, Ref),\n\tok.\n\n%% Websocket.\n\nwebsocket(Config) ->\n\tdoc(\"Websocket example.\"),\n\ttry\n\t\tdo_compile_and_start(websocket, Config),\n\t\t%% We can only initiate a Websocket connection from HTTP/1.1.\n\t\t{ok, Pid} = gun:open(\"127.0.0.1\", 8080, #{protocols => [http], retry => 0}),\n\t\t{ok, http} = gun:await_up(Pid),\n\t\t_ = monitor(process, Pid),\n\t\tStreamRef = gun:ws_upgrade(Pid, \"/websocket\", [], #{compress => true}),\n\t\treceive\n\t\t\t{gun_upgrade, Pid, StreamRef, _, _} ->\n\t\t\t\tok;\n\t\t\tMsg1 ->\n\t\t\t\texit({connection_failed, Msg1})\n\t\tend,\n\t\t%% Check that we receive the message sent on timer on init.\n\t\treceive\n\t\t\t{gun_ws, Pid, StreamRef, {text, <<\"Hello!\">>}} ->\n\t\t\t\tok\n\t\tafter 2000 ->\n\t\t\texit(timeout)\n\t\tend,\n\t\t%% Check that we receive subsequent messages sent on timer.\n\t\treceive\n\t\t\t{gun_ws, Pid, StreamRef, {text, <<\"How' you doin'?\">>}} ->\n\t\t\t\tok\n\t\tafter 2000 ->\n\t\t\texit(timeout)\n\t\tend,\n\t\t%% Check that we receive the echoed message.\n\t\tgun:ws_send(Pid, StreamRef, {text, <<\"hello\">>}),\n\t\treceive\n\t\t\t{gun_ws, Pid, StreamRef, {text, <<\"That's what she said! hello\">>}} ->\n\t\t\t\tok\n\t\tafter 500 ->\n\t\t\texit(timeout)\n\t\tend,\n\t\tgun:ws_send(Pid, StreamRef, close)\n\tafter\n\t\tdo_stop(websocket)\n\tend.\n"
  },
  {
    "path": "test/h2spec_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(h2spec_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n\n%% ct.\n\nall() ->\n\t[h2spec].\n\ninit_per_suite(Config) ->\n\tcase os:getenv(\"H2SPEC\") of\n\t\tfalse ->\n\t\t\t{skip, \"H2SPEC environment variable undefined.\"};\n\t\tH2spec ->\n\t\t\tcase filelib:is_file(H2spec) of\n\t\t\t\tfalse ->\n\t\t\t\t\t{skip, \"H2SPEC executable not found.\"};\n\t\t\t\ttrue ->\n\t\t\t\t\tcowboy_test:init_http(h2spec, #{\n\t\t\t\t\t\tenv => #{dispatch => init_dispatch()},\n\t\t\t\t\t\tmax_concurrent_streams => 100,\n\t\t\t\t\t\t%% This test suite expects an HTTP/2-only connection.\n\t\t\t\t\t\tprotocols => [http2],\n\t\t\t\t\t\t%% Disable the DATA threshold for this test suite.\n\t\t\t\t\t\tstream_window_data_threshold => 0\n\t\t\t\t\t}, Config)\n\t\t\tend\n\tend.\n\nend_per_suite(_Config) ->\n\tcowboy:stop_listener(h2spec).\n\n%% Dispatch configuration.\n\ninit_dispatch() ->\n\tcowboy_router:compile([\n\t\t{'_', [\n\t\t\t{\"/\", delay_hello_h, 50}\n\t\t]}\n\t]).\n\n%% Tests.\n\nh2spec(Config) ->\n\tdoc(\"h2spec test suite for the HTTP/2 protocol.\"),\n\tSelf = self(),\n\tspawn_link(fun() -> start_port(Config, Self) end),\n\treceive\n\t\t{h2spec_exit, 0, Log} ->\n\t\t\tct:log(\"~ts\", [Log]),\n\t\t\tok;\n\t\t{h2spec_exit, Status, Log} ->\n\t\t\tct:log(\"~ts\", [Log]),\n\t\t\terror({exit_status, Status})\n\tend.\n\nstart_port(Config, Pid) ->\n\tH2spec = os:getenv(\"H2SPEC\"),\n\tListenPort = config(port, Config),\n\tPort = open_port(\n\t\t{spawn, H2spec ++ \" -S -p \"\n\t\t\t++ integer_to_list(ListenPort)},\n\t\t[{line, 10000}, {cd, config(priv_dir, Config)}, binary, exit_status]),\n\treceive_infinity(Port, Pid, []).\n\nreceive_infinity(Port, Pid, Acc) ->\n\treceive\n\t\t{Port, {data, {eol, Line}}} ->\n\t\t\tio:format(user, \"~s~n\", [Line]),\n\t\t\treceive_infinity(Port, Pid, [Line|Acc]);\n\t\t{Port, {exit_status, Status}} ->\n\t\t\tPid ! {h2spec_exit, Status, [[L, $\\n] || L <- lists:reverse(Acc)]}\n\tend.\n"
  },
  {
    "path": "test/handlers/accept_callback_h.erl",
    "content": "%% This module returns something different in\n%% AcceptCallback depending on the query string.\n\n-module(accept_callback_h).\n\n-export([init/2]).\n-export([allowed_methods/2]).\n-export([content_types_accepted/2]).\n-export([put_text_plain/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\nallowed_methods(Req, State) ->\n\t{[<<\"PUT\">>, <<\"POST\">>, <<\"PATCH\">>], Req, State}.\n\ncontent_types_accepted(Req, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, []}, put_text_plain}], Req, State}.\n\nput_text_plain(Req=#{qs := <<\"false\">>}, State) ->\n\t{false, Req, State};\nput_text_plain(Req=#{qs := <<\"true\">>}, State) ->\n\t{true, Req, State}.\n"
  },
  {
    "path": "test/handlers/accept_callback_missing_h.erl",
    "content": "-module(accept_callback_missing_h).\n\n-export([init/2]).\n-export([allowed_methods/2]).\n-export([content_types_accepted/2]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\nallowed_methods(Req, State) ->\n\t{[<<\"PUT\">>], Req, State}.\n\ncontent_types_accepted(Req, State) ->\n\tct_helper_error_h:ignore(cowboy_rest, process_content_type, 3),\n\t{[{<<\"text/plain\">>, accept}], Req, State}.\n"
  },
  {
    "path": "test/handlers/asterisk_h.erl",
    "content": "%% This module echoes back the value the test is interested in.\n\n-module(asterisk_h).\n\n-export([init/2]).\n\ninit(Req, Opts) ->\n\techo(cowboy_req:header(<<\"x-echo\">>, Req), Req, Opts).\n\necho(undefined, Req, Opts) ->\n\t{ok, cowboy_req:reply(200, Req), Opts};\necho(What, Req, Opts) ->\n\tF = binary_to_atom(What, latin1),\n\tValue = case cowboy_req:F(Req) of\n\t\tV when is_integer(V) -> integer_to_binary(V);\n\t\tV -> V\n\tend,\n\t{ok, cowboy_req:reply(200, #{}, Value, Req), Opts}.\n"
  },
  {
    "path": "test/handlers/charset_in_content_types_provided_h.erl",
    "content": "%% This module has a media type provided with an explicit charset.\n\n-module(charset_in_content_types_provided_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([charsets_provided/2]).\n-export([get_text_plain/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{{<<\"text\">>, <<\"plain\">>, [{<<\"charset\">>, <<\"utf-8\">>}]}, get_text_plain}\n\t], Req, State}.\n\ncharsets_provided(Req, State) ->\n\t{[<<\"utf-16\">>, <<\"iso-8861-1\">>], Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n"
  },
  {
    "path": "test/handlers/charset_in_content_types_provided_implicit_h.erl",
    "content": "%% This module has a media type provided with a wildcard\n%% and a list of charsets that is limited.\n\n-module(charset_in_content_types_provided_implicit_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([charsets_provided/2]).\n-export([get_text_plain/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{{<<\"text\">>, <<\"plain\">>, '*'}, get_text_plain}\n\t], Req, State}.\n\ncharsets_provided(Req, State) ->\n\t{[<<\"utf-8\">>, <<\"utf-16\">>], Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n\n"
  },
  {
    "path": "test/handlers/charset_in_content_types_provided_implicit_no_callback_h.erl",
    "content": "%% This module has a media type provided with a wildcard\n%% and lacks a charsets_provided callback.\n\n-module(charset_in_content_types_provided_implicit_no_callback_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([get_text_plain/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{{<<\"text\">>, <<\"plain\">>, '*'}, get_text_plain}\n\t], Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n\n\n"
  },
  {
    "path": "test/handlers/charsets_provided_empty_h.erl",
    "content": "%% This module has a text and non-text media type,\n%% but provides no charset. All requests will result\n%% in a 406 not acceptable.\n\n-module(charsets_provided_empty_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([charsets_provided/2]).\n-export([get_text_plain/2]).\n-export([get_application_json/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain},\n\t\t{{<<\"application\">>, <<\"json\">>, []}, get_application_json}\n\t], Req, State}.\n\ncharsets_provided(Req, State) ->\n\t{[], Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n\nget_application_json(Req, State) ->\n\t{<<\"{\\\"hello\\\": \\\"rest\\\"}\">>, Req, State}.\n\n"
  },
  {
    "path": "test/handlers/charsets_provided_h.erl",
    "content": "%% This module has a text and non-text media type,\n%% and provides two charsets.\n\n-module(charsets_provided_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([charsets_provided/2]).\n-export([get_text_plain/2]).\n-export([get_application_json/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain},\n\t\t{{<<\"application\">>, <<\"json\">>, []}, get_application_json}\n\t], Req, State}.\n\ncharsets_provided(Req, State) ->\n\t{[<<\"utf-8\">>, <<\"utf-16\">>], Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n\nget_application_json(Req, State) ->\n\t{<<\"{\\\"hello\\\": \\\"rest\\\"}\">>, Req, State}.\n"
  },
  {
    "path": "test/handlers/compress_h.erl",
    "content": "%% This module sends a response body of varying sizes to test\n%% the cowboy_compress_h stream handler.\n\n-module(compress_h).\n\n-export([init/2]).\n\ninit(Req0, State=reply) ->\n\tReq = case cowboy_req:binding(what, Req0) of\n\t\t<<\"small\">> ->\n\t\t\tcowboy_req:reply(200, #{}, lists:duplicate(100, $a), Req0);\n\t\t<<\"large\">> ->\n\t\t\tcowboy_req:reply(200, #{}, lists:duplicate(100000, $a), Req0);\n\t\t<<\"vary\">> ->\n\t\t\tVary = cowboy_req:header(<<\"x-test-vary\">>, Req0),\n\t\t\tcowboy_req:reply(200, #{<<\"vary\">> => Vary}, lists:duplicate(100000, $a), Req0);\n\t\t<<\"over-threshold\">> ->\n\t\t\tcowboy_req:reply(200, #{}, lists:duplicate(200, $a), Req0);\n\t\t<<\"content-encoding\">> ->\n\t\t\tcowboy_req:reply(200, #{<<\"content-encoding\">> => <<\"compress\">>},\n\t\t\t\tlists:duplicate(100000, $a), Req0);\n\t\t<<\"etag\">> ->\n\t\t\tcowboy_req:reply(200, #{<<\"etag\">> => <<\"\\\"STRONK\\\"\">>},\n\t\t\t\tlists:duplicate(100000, $a), Req0);\n\t\t<<\"sendfile\">> ->\n\t\t\tAppFile = code:where_is_file(\"cowboy.app\"),\n\t\t\tSize = filelib:file_size(AppFile),\n\t\t\tcowboy_req:reply(200, #{}, {sendfile, 0, Size, AppFile}, Req0);\n\t\t<<\"set_options_threshold0\">> ->\n\t\t\tcowboy_req:cast({set_options, #{compress_threshold => 0}}, Req0),\n\t\t\tcowboy_req:reply(200, #{}, lists:duplicate(100, $a), Req0)\n\tend,\n\t{ok, Req, State};\ninit(Req0, State=stream_reply) ->\n\tReq = case cowboy_req:binding(what, Req0) of\n\t\t<<\"large\">> ->\n\t\t\tstream_reply(#{}, Req0);\n\t\t<<\"content-encoding\">> ->\n\t\t\tstream_reply(#{<<\"content-encoding\">> => <<\"compress\">>}, Req0);\n\t\t<<\"etag\">> ->\n\t\t\tstream_reply(#{<<\"etag\">> => <<\"\\\"STRONK\\\"\">>}, Req0);\n\t\t<<\"sendfile\">> ->\n\t\t\tData = lists:duplicate(10000, $a),\n\t\t\tAppFile = code:where_is_file(\"cowboy.app\"),\n\t\t\tSize = filelib:file_size(AppFile),\n\t\t\tReq1 = cowboy_req:stream_reply(200, Req0),\n\t\t\t%% We send a few files interspersed into other data.\n\t\t\tcowboy_req:stream_body(Data, nofin, Req1),\n\t\t\tcowboy_req:stream_body({sendfile, 0, Size, AppFile}, nofin, Req1),\n\t\t\tcowboy_req:stream_body(Data, nofin, Req1),\n\t\t\tcowboy_req:stream_body({sendfile, 0, Size, AppFile}, nofin, Req1),\n\t\t\tcowboy_req:stream_body(Data, fin, Req1),\n\t\t\tReq1;\n\t\t<<\"sendfile_fin\">> ->\n\t\t\tData = lists:duplicate(10000, $a),\n\t\t\tAppFile = code:where_is_file(\"cowboy.app\"),\n\t\t\tSize = filelib:file_size(AppFile),\n\t\t\tReq1 = cowboy_req:stream_reply(200, Req0),\n\t\t\t%% We send a few files interspersed into other data.\n\t\t\tcowboy_req:stream_body(Data, nofin, Req1),\n\t\t\tcowboy_req:stream_body({sendfile, 0, Size, AppFile}, nofin, Req1),\n\t\t\tcowboy_req:stream_body(Data, nofin, Req1),\n\t\t\tcowboy_req:stream_body({sendfile, 0, Size, AppFile}, fin, Req1),\n\t\t\tReq1;\n\t\t<<\"delayed\">> ->\n\t\t\tstream_delayed(Req0);\n\t\t<<\"set_options_buffering_false\">> ->\n\t\t\tcowboy_req:cast({set_options, #{compress_buffering => false}}, Req0),\n\t\t\tstream_delayed(Req0);\n\t\t<<\"set_options_buffering_true\">> ->\n\t\t\tcowboy_req:cast({set_options, #{compress_buffering => true}}, Req0),\n\t\t\tstream_delayed(Req0)\n\tend,\n\t{ok, Req, State}.\n\nstream_reply(Headers, Req0) ->\n\tData = lists:duplicate(10000, $a),\n\tReq = cowboy_req:stream_reply(200, Headers, Req0),\n\t_ = [cowboy_req:stream_body(Data, nofin, Req) || _ <- lists:seq(1,9)],\n\tcowboy_req:stream_body(Data, fin, Req),\n\tReq.\n\nstream_delayed(Req0) ->\n\tReq = cowboy_req:stream_reply(200, Req0),\n\tcowboy_req:stream_body(<<\"data: Hello!\\r\\n\\r\\n\">>, nofin, Req),\n\ttimer:sleep(1000),\n\tcowboy_req:stream_body(<<\"data: World!\\r\\n\\r\\n\">>, nofin, Req),\n\ttimer:sleep(1000),\n\tcowboy_req:stream_body(<<\"data: Closing!\\r\\n\\r\\n\">>, fin, Req),\n\tReq.\n"
  },
  {
    "path": "test/handlers/content_types_accepted_h.erl",
    "content": "%% This module returns something different in\n%% content_types_accepted depending on the query string.\n\n-module(content_types_accepted_h).\n\n-export([init/2]).\n-export([allowed_methods/2]).\n-export([content_types_accepted/2]).\n-export([put_multipart_mixed/2]).\n-export([put_text_plain/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\nallowed_methods(Req, State) ->\n\t{[<<\"PUT\">>], Req, State}.\n\ncontent_types_accepted(Req=#{qs := <<\"multipart\">>}, State) ->\n\t{[\n\t\t{{<<\"multipart\">>, <<\"mixed\">>, [{<<\"v\">>, <<\"1\">>}]}, put_multipart_mixed}\n\t], Req, State};\ncontent_types_accepted(Req=#{qs := <<\"param\">>}, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, [{<<\"charset\">>, <<\"utf-8\">>}]}, put_text_plain}], Req, State};\ncontent_types_accepted(Req=#{qs := <<\"wildcard\">>}, State) ->\n\t{[{'*', put_text_plain}], Req, State};\ncontent_types_accepted(Req=#{qs := <<\"wildcard-param\">>}, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, '*'}, put_text_plain}], Req, State}.\n\nput_multipart_mixed(Req, State) ->\n\t{true, Req, State}.\n\nput_text_plain(Req0, State) ->\n\t{ok, _, Req} = cowboy_req:read_body(Req0),\n\t{true, Req, State}.\n"
  },
  {
    "path": "test/handlers/content_types_provided_h.erl",
    "content": "%% This module has different content_types_provided values\n%% and/or sends a different response body depending on the\n%% query string.\n\n-module(content_types_provided_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([get_text_plain/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req=#{qs := <<\"invalid-type\">>}, State) ->\n\tct_helper:ignore(cowboy_rest, normalize_content_types, 2),\n\t{[{{'*', '*', '*'}, get_text_plain}], Req, State};\ncontent_types_provided(Req=#{qs := <<\"wildcard-param\">>}, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, '*'}, get_text_plain}], Req, State}.\n\nget_text_plain(Req=#{qs := <<\"invalid-type\">>}, State) ->\n\t{<<\"invalid-type\">>, Req, State};\nget_text_plain(Req=#{qs := <<\"wildcard-param\">>}, State) ->\n\t{_, _, Param} = maps:get(media_type, Req),\n\tBody = if\n\t\tParam =:= [] -> <<\"[]\">>;\n\t\tParam =/= [] ->\n\t\t\tiolist_to_binary([[Key, $=, Value] || {Key, Value} <- Param])\n\tend,\n\t{Body, Req, State}.\n"
  },
  {
    "path": "test/handlers/crash_h.erl",
    "content": "%% This module crashes immediately.\n\n-module(crash_h).\n\n-behaviour(cowboy_handler).\n\n-export([init/2]).\n\n-spec init(_, _) -> no_return().\ninit(_, external_exit) ->\n\tct_helper:ignore(?MODULE, init, 2),\n\texit(self(), ct_helper_ignore);\ninit(_, no_reply) ->\n\tct_helper:ignore(?MODULE, init, 2),\n\terror(crash);\ninit(Req, reply) ->\n\t_ = cowboy_req:reply(200, Req),\n\tct_helper:ignore(?MODULE, init, 2),\n\terror(crash).\n"
  },
  {
    "path": "test/handlers/create_resource_h.erl",
    "content": "-module(create_resource_h).\n\n-export([init/2]).\n-export([allowed_methods/2]).\n-export([resource_exists/2]).\n-export([content_types_accepted/2]).\n-export([from_text/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\nallowed_methods(Req, State) ->\n\t{[<<\"POST\">>], Req, State}.\n\nresource_exists(Req, State) ->\n\t{true, Req, State}.\n\ncontent_types_accepted(Req, State) ->\n\t{[{{<<\"application\">>, <<\"text\">>, []}, from_text}], Req, State}.\n\nfrom_text(Req=#{qs := Qs}, State) ->\n\tNewURI = [cowboy_req:uri(Req), \"/foo\"],\n\tcase Qs of\n\t\t<<\"created\">> ->\n\t\t\t{{created, NewURI}, Req, State};\n\t\t<<\"see_other\">> ->\n\t\t\t{{see_other, NewURI}, Req, State}\n\tend.\n"
  },
  {
    "path": "test/handlers/custom_req_fields_h.erl",
    "content": "%% This module adds custom fields to the Req object.\n%% It is only meant to be checked by Dialyzer.\n\n-module(custom_req_fields_h).\n\n-export([init/2]).\n\n-spec init(Req, Opts) -> {ok, Req, Opts} when Req::cowboy_req:req().\ninit(Req, Opts) ->\n\t{ok, Req#{'_myapp_auth_method' => pubkey}, Opts}.\n"
  },
  {
    "path": "test/handlers/decompress_h.erl",
    "content": "%% This module echoes a request body of to test\n%% the cowboy_decompress_h stream handler.\n\n-module(decompress_h).\n\n-export([init/2]).\n\ninit(Req0, State=echo) ->\n\tcase cowboy_req:binding(what, Req0) of\n\t\t<<\"decompress_disable\">> ->\n\t\t\tcowboy_req:cast({set_options, #{decompress_enabled => false}}, Req0);\n\t\t<<\"decompress_ratio_limit\">> ->\n\t\t\tcowboy_req:cast({set_options, #{decompress_ratio_limit => 0.5}}, Req0);\n\t\t<<\"normal\">> -> ok\n\tend,\n\t{ok, Body, Req1} = read_body(Req0),\n\tReq = cowboy_req:reply(200, #{}, Body, Req1),\n\t{ok, Req, State};\ninit(Req0, State=test) ->\n\tReq = test(Req0, cowboy_req:binding(what, Req0)),\n\t{ok, Req, State}.\n\ntest(Req, <<\"content-encoding\">>) ->\n\tcowboy_req:reply(200, #{},\n\t\tcowboy_req:header(<<\"content-encoding\">>, Req, <<\"undefined\">>),\n\t\tReq);\ntest(Req, <<\"content-decoded\">>) ->\n\tcowboy_req:reply(200, #{},\n\t\tio_lib:format(\"~0p\", [maps:get(content_decoded, Req, undefined)]),\n\t\tReq);\ntest(Req0, <<\"disable-in-the-middle\">>) ->\n\t{Status, Data, Req1} = cowboy_req:read_body(Req0, #{length => 1000}),\n\tcowboy_req:cast({set_options, #{decompress_enabled => false}}, Req1),\n\t{ok, Body, Req} = do_read_body(Status, Req1, Data),\n\tcowboy_req:reply(200, #{}, Body, Req);\ntest(Req0, <<\"enable-in-the-middle\">>) ->\n\t{Status, Data, Req1} = cowboy_req:read_body(Req0, #{length => 1000}),\n\tcowboy_req:cast({set_options, #{decompress_enabled => true}}, Req1),\n\t{ok, Body, Req} = do_read_body(Status, Req1, Data),\n\tcowboy_req:reply(200, #{}, Body, Req);\ntest(Req0, <<\"header-command\">>) ->\n\t{ok, Body, Req1} = read_body(Req0),\n\tReq = cowboy_req:stream_reply(200, #{}, Req1),\n\tcowboy_req:stream_body(Body, fin, Req);\ntest(Req0, <<\"accept-identity\">>) ->\n\t{ok, Body, Req} = read_body(Req0),\n\tcowboy_req:reply(200,\n\t\t#{<<\"accept-encoding\">> => <<\"identity\">>},\n\t\tBody, Req);\ntest(Req0, <<\"invalid-header\">>) ->\n\t{ok, Body, Req} = read_body(Req0),\n\tcowboy_req:reply(200,\n\t\t#{<<\"accept-encoding\">> => <<\";\">>},\n\t\tBody, Req);\ntest(Req0, <<\"reject-explicit-header\">>) ->\n\t{ok, Body, Req} = read_body(Req0),\n\tcowboy_req:reply(200,\n\t\t#{<<\"accept-encoding\">> => <<\"identity, gzip;q=0\">>},\n\t\tBody, Req);\ntest(Req0, <<\"reject-implicit-header\">>) ->\n\t{ok, Body, Req} = read_body(Req0),\n\tcowboy_req:reply(200,\n\t\t#{<<\"accept-encoding\">> => <<\"identity, *;q=0\">>},\n\t\tBody, Req);\ntest(Req0, <<\"accept-explicit-header\">>) ->\n\t{ok, Body, Req} = read_body(Req0),\n\tcowboy_req:reply(200,\n\t\t#{<<\"accept-encoding\">> => <<\"identity, gzip;q=0.5\">>},\n\t\tBody, Req);\ntest(Req0, <<\"accept-implicit-header\">>) ->\n\t{ok, Body, Req} = read_body(Req0),\n\tcowboy_req:reply(200,\n\t\t#{<<\"accept-encoding\">> => <<\"identity, *;q=0.5\">>},\n\t\tBody, Req).\n\nread_body(Req0) ->\n\t{Status, Data, Req} = cowboy_req:read_body(Req0, #{length => 1000}),\n\tdo_read_body(Status, Req, Data).\n\ndo_read_body(more, Req0, Acc) ->\n\t{Status, Data, Req} = cowboy_req:read_body(Req0),\n\tdo_read_body(Status, Req, << Acc/binary, Data/binary >>);\ndo_read_body(ok, Req, Acc) ->\n\t{ok, Acc, Req}.\n"
  },
  {
    "path": "test/handlers/default_h.erl",
    "content": "%% This module does not do anything.\n\n-module(default_h).\n\n-export([init/2]).\n\ninit(Req, Opts) ->\n\t{ok, Req, Opts}.\n"
  },
  {
    "path": "test/handlers/delay_hello_h.erl",
    "content": "%% This module sends a hello world response after a delay.\n\n-module(delay_hello_h).\n\n-export([init/2]).\n\ninit(Req, Delay) when is_integer(Delay) ->\n\tinit(Req, #{delay => Delay});\ninit(Req, Opts=#{delay := Delay}) ->\n\t_ = case Opts of\n\t\t#{notify_received := Pid} ->\n\t\t\tPid ! {request_received, maps:get(path, Req)};\n\t\t_ ->\n\t\t\tok\n\tend,\n\ttimer:sleep(Delay),\n\t{ok, cowboy_req:reply(200, #{}, <<\"Hello world!\">>, Req), Delay}.\n"
  },
  {
    "path": "test/handlers/delete_resource_h.erl",
    "content": "%% This module accepts a multipart media type with parameters\n%% that do not include boundary.\n\n-module(delete_resource_h).\n\n-export([init/2]).\n-export([allowed_methods/2]).\n-export([delete_resource/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\nallowed_methods(Req, State) ->\n\t{[<<\"DELETE\">>], Req, State}.\n\ndelete_resource(#{qs := <<\"missing\">>}, _) ->\n\tno_call.\n"
  },
  {
    "path": "test/handlers/echo_h.erl",
    "content": "%% This module echoes back the value the test is interested in.\n\n-module(echo_h).\n\n-export([init/2]).\n\ninit(Req, Opts) ->\n\tcase cowboy_req:binding(arg, Req) of\n\t\tundefined ->\n\t\t\techo(cowboy_req:binding(key, Req), Req, Opts);\n\t\tArg ->\n\t\t\techo_arg(Arg, Req, Opts)\n\tend.\n\necho(<<\"read_body\">>, Req0, Opts) ->\n\tcase Opts of\n\t\t#{crash := true} -> ct_helper:ignore(cowboy_req, read_body, 2);\n\t\t_ -> ok\n\tend,\n\t{_, Body, Req} = case cowboy_req:path(Req0) of\n\t\t<<\"/100-continue\", _/bits>> ->\n\t\t\tcowboy_req:inform(100, Req0),\n\t\t\tcowboy_req:read_body(Req0);\n\t\t<<\"/delay\", _/bits>> ->\n\t\t\ttimer:sleep(500),\n\t\t\tcowboy_req:read_body(Req0);\n\t\t<<\"/full\", _/bits>> -> read_body(Req0, <<>>);\n\t\t<<\"/auto-sync\", _/bits>> -> read_body_auto_sync(Req0, <<>>);\n\t\t<<\"/auto-async\", _/bits>> -> read_body_auto_async(Req0, <<>>);\n\t\t<<\"/length\", _/bits>> ->\n\t\t\t{_, _, Req1} = read_body(Req0, <<>>),\n\t\t\tLength = cowboy_req:body_length(Req1),\n\t\t\t{ok, integer_to_binary(Length), Req1};\n\t\t<<\"/opts\", _/bits>> -> cowboy_req:read_body(Req0, Opts);\n\t\t<<\"/spawn\", _/bits>> ->\n\t\t\tParent = self(),\n\t\t\tPid = spawn_link(fun() ->\n\t\t\t\tParent ! {self(), cowboy_req:read_body(Req0)}\n\t\t\tend),\n\t\t\treceive\n\t\t\t\t{Pid, Msg} -> Msg\n\t\t\tafter 5000 ->\n\t\t\t\terror(timeout)\n\t\t\tend;\n\t\t_ -> cowboy_req:read_body(Req0)\n\tend,\n\t{ok, cowboy_req:reply(200, #{}, Body, Req), Opts};\necho(<<\"read_urlencoded_body\">>, Req0, Opts) ->\n\tPath = cowboy_req:path(Req0),\n\tcase {Path, Opts} of\n\t\t{<<\"/opts\", _/bits>>, #{crash := true}} -> ct_helper:ignore(cowboy_req, read_body, 2);\n\t\t{_, #{crash := true}} -> ct_helper:ignore(cowboy_req, read_urlencoded_body, 2);\n\t\t_ -> ok\n\tend,\n\t{ok, Body, Req} = case Path of\n\t\t<<\"/opts\", _/bits>> -> cowboy_req:read_urlencoded_body(Req0, Opts);\n\t\t<<\"/crash\", _/bits>> -> cowboy_req:read_urlencoded_body(Req0, Opts);\n\t\t_ -> cowboy_req:read_urlencoded_body(Req0)\n\tend,\n\t{ok, cowboy_req:reply(200, #{}, value_to_iodata(Body), Req), Opts};\necho(<<\"read_and_match_urlencoded_body\">>, Req0, Opts) ->\n\tPath = cowboy_req:path(Req0),\n\tcase {Path, Opts} of\n\t\t{<<\"/opts\", _/bits>>, #{crash := true}} -> ct_helper:ignore(cowboy_req, read_body, 2);\n\t\t{_, #{crash := true}} -> ct_helper:ignore(cowboy_req, read_urlencoded_body, 2);\n\t\t_ -> ok\n\tend,\n\t{ok, Body, Req} = case Path of\n\t\t<<\"/opts\", _/bits>> -> cowboy_req:read_and_match_urlencoded_body([], Req0, Opts);\n\t\t<<\"/crash\", _/bits>> -> cowboy_req:read_and_match_urlencoded_body([], Req0, Opts);\n\t\t_ -> cowboy_req:read_and_match_urlencoded_body([], Req0)\n\tend,\n\t{ok, cowboy_req:reply(200, #{}, value_to_iodata(Body), Req), Opts};\necho(<<\"uri\">>, Req, Opts) ->\n\tValue = case cowboy_req:path_info(Req) of\n\t\t[<<\"origin\">>] -> cowboy_req:uri(Req, #{host => undefined});\n\t\t[<<\"protocol-relative\">>] -> cowboy_req:uri(Req, #{scheme => undefined});\n\t\t[<<\"no-qs\">>] -> cowboy_req:uri(Req, #{qs => undefined});\n\t\t[<<\"no-path\">>] -> cowboy_req:uri(Req, #{path => undefined, qs => undefined});\n\t\t[<<\"set-port\">>] -> cowboy_req:uri(Req, #{port => 123});\n\t\t_ -> cowboy_req:uri(Req)\n\tend,\n\t{ok, cowboy_req:reply(200, #{}, Value, Req), Opts};\necho(<<\"match\">>, Req, Opts) ->\n\t[Type|Fields0] = cowboy_req:path_info(Req),\n\tFields = [binary_to_atom(F, latin1) || F <- Fields0],\n\tValue = case Type of\n\t\t<<\"qs\">> -> cowboy_req:match_qs(Fields, Req);\n\t\t<<\"qs_with_constraints\">> -> cowboy_req:match_qs([{id, integer}], Req);\n\t\t<<\"cookies\">> -> cowboy_req:match_cookies(Fields, Req);\n\t\t<<\"body_qs\">> ->\n\t\t\t%% Note that the Req should not be discarded but for the\n\t\t\t%% purpose of this test this has no ill impacts.\n\t\t\t{ok, Match, _} = cowboy_req:read_and_match_urlencoded_body(Fields, Req),\n\t\t\tMatch\n\tend,\n\t{ok, cowboy_req:reply(200, #{}, value_to_iodata(Value), Req), Opts};\necho(<<\"filter_then_parse_cookies\">>, Req0, Opts) ->\n\tReq = cowboy_req:filter_cookies([cake, color], Req0),\n\tValue = cowboy_req:parse_cookies(Req),\n\t{ok, cowboy_req:reply(200, #{}, value_to_iodata(Value), Req), Opts};\necho(What, Req, Opts) ->\n\tKey = binary_to_atom(What, latin1),\n\tValue = case cowboy_req:path(Req) of\n\t\t<<\"/direct/\",_/bits>> -> maps:get(Key, Req);\n\t\t_ -> cowboy_req:Key(Req)\n\tend,\n\t{ok, cowboy_req:reply(200, #{}, value_to_iodata(Value), Req), Opts}.\n\necho_arg(Arg0, Req, Opts) ->\n\tF = binary_to_atom(cowboy_req:binding(key, Req), latin1),\n\tArg = case F of\n\t\tbinding -> binary_to_atom(Arg0, latin1);\n\t\t_ -> Arg0\n\tend,\n\tValue = case cowboy_req:binding(default, Req) of\n\t\tundefined -> cowboy_req:F(Arg, Req);\n\t\tDefault -> cowboy_req:F(Arg, Req, Default)\n\tend,\n\t{ok, cowboy_req:reply(200, #{}, value_to_iodata(Value), Req), Opts}.\n\nread_body(Req0, Acc) ->\n\tcase cowboy_req:read_body(Req0) of\n\t\t{ok, Data, Req} -> {ok, << Acc/binary, Data/binary >>, Req};\n\t\t{more, Data, Req} -> read_body(Req, << Acc/binary, Data/binary >>)\n\tend.\n\nread_body_auto_sync(Req0, Acc) ->\n\tOpts = #{length => auto, period => infinity},\n\tcase cowboy_req:read_body(Req0, Opts) of\n\t\t{ok, Data, Req} -> {ok, << Acc/binary, Data/binary >>, Req};\n\t\t{more, Data, Req} -> read_body_auto_sync(Req, << Acc/binary, Data/binary >>)\n\tend.\n\nread_body_auto_async(Req, Acc) ->\n\tread_body_auto_async(Req, make_ref(), Acc).\n\nread_body_auto_async(Req, ReadBodyRef, Acc) ->\n\tcowboy_req:cast({read_body, self(), ReadBodyRef, auto, infinity}, Req),\n\treceive\n\t\t{request_body, ReadBodyRef, nofin, Data} ->\n\t\t\tread_body_auto_async(Req, ReadBodyRef, <<Acc/binary, Data/binary>>);\n\t\t{request_body, ReadBodyRef, fin, _, Data} ->\n\t\t\t{ok, <<Acc/binary, Data/binary>>, Req}\n\tend.\n\nvalue_to_iodata(V) when is_integer(V) -> integer_to_binary(V);\nvalue_to_iodata(V) when is_atom(V) -> atom_to_binary(V, latin1);\nvalue_to_iodata(V) when is_list(V); is_tuple(V); is_map(V) -> io_lib:format(\"~999999p\", [V]);\nvalue_to_iodata(V) -> V.\n"
  },
  {
    "path": "test/handlers/expires_h.erl",
    "content": "%% This module sends a different expires value\n%% depending on the query string.\n\n-module(expires_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([get_text_plain/2]).\n-export([expires/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain}], Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n\nexpires(Req=#{qs := <<\"tuple\">>}, State) ->\n\t{{{2012, 9, 21}, {22, 36, 14}}, Req, State};\nexpires(Req=#{qs := <<\"binary\">>}, State) ->\n\t{<<\"0\">>, Req, State};\nexpires(Req=#{qs := <<\"undefined\">>}, State) ->\n\t{undefined, Req, State};\n%% Simulate the callback being missing in other cases.\nexpires(#{qs := <<\"missing\">>}, _) ->\n\tno_call.\n"
  },
  {
    "path": "test/handlers/generate_etag_h.erl",
    "content": "%% This module sends a different etag value\n%% depending on the query string.\n\n-module(generate_etag_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([get_text_plain/2]).\n-export([generate_etag/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain}], Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n\n%% Correct return values from generate_etag/2.\ngenerate_etag(Req=#{qs := <<\"tuple-weak\">>}, State) ->\n\t{{weak, <<\"etag-header-value\">>}, Req, State};\ngenerate_etag(Req=#{qs := <<\"tuple-strong\">>}, State) ->\n\t{{strong, <<\"etag-header-value\">>}, Req, State};\n%% Backwards compatible return values from generate_etag/2.\ngenerate_etag(Req=#{qs := <<\"binary-weak-quoted\">>}, State) ->\n\t{<<\"W/\\\"etag-header-value\\\"\">>, Req, State};\ngenerate_etag(Req=#{qs := <<\"binary-strong-quoted\">>}, State) ->\n\t{<<\"\\\"etag-header-value\\\"\">>, Req, State};\n%% Invalid return values from generate_etag/2.\ngenerate_etag(Req=#{qs := <<\"binary-weak-unquoted\">>}, State) ->\n\tct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),\n\t{<<\"W/etag-header-value\">>, Req, State};\ngenerate_etag(Req=#{qs := <<\"binary-strong-unquoted\">>}, State) ->\n\tct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),\n\t{<<\"etag-header-value\">>, Req, State};\n%% Returning 'undefined' to indicate no etag.\ngenerate_etag(Req=#{qs := <<\"undefined\">>}, State) ->\n\t{undefined, Req, State};\n%% Simulate the callback being missing in other cases.\ngenerate_etag(#{qs := <<\"missing\">>}, _) ->\n\tno_call.\n"
  },
  {
    "path": "test/handlers/hello_h.erl",
    "content": "%% This module sends a hello world response.\n\n-module(hello_h).\n\n-export([init/2]).\n\ninit(Req, Opts) ->\n\t{ok, cowboy_req:reply(200, #{}, <<\"Hello world!\">>, Req), Opts}.\n"
  },
  {
    "path": "test/handlers/if_range_h.erl",
    "content": "%% This module defines the ranges_provided callback\n%% and a generate_etag callback that returns something\n%% different depending on query string. It also defines\n%% a last_modified callback that must be ignored when a\n%% date is provided in if_range.\n\n-module(if_range_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([ranges_provided/2]).\n-export([generate_etag/2]).\n-export([last_modified/2]).\n-export([get_text_plain/2]).\n-export([get_text_plain_bytes/2]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\ncontent_types_provided(Req, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain}], Req, State}.\n\n%% Simulate the callback being missing.\nranges_provided(#{qs := <<\"missing-ranges_provided\">>}, _) ->\n\tno_call;\nranges_provided(Req=#{qs := <<\"empty-ranges_provided\">>}, State) ->\n\t{[], Req, State};\nranges_provided(Req, State) ->\n\t{[{<<\"bytes\">>, get_text_plain_bytes}], Req, State}.\n\ngenerate_etag(Req=#{qs := <<\"weak-etag\">>}, State) ->\n\t{{weak, <<\"weak-no-match\">>}, Req, State};\ngenerate_etag(Req, State) ->\n\t{{strong, <<\"strong-and-match\">>}, Req, State}.\n\nlast_modified(Req, State) ->\n\t{{{2222, 2, 22}, {11, 11, 11}}, Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n\nget_text_plain_bytes(Req, State) ->\n\t%% We send everything in one part, since we are not testing\n\t%% this callback specifically.\n\tBody = <<\"This is ranged REST!\">>,\n\t{[{{0, byte_size(Body) - 1, byte_size(Body)}, Body}], Req, State}.\n"
  },
  {
    "path": "test/handlers/last_modified_h.erl",
    "content": "%% This module sends a different last-modified value\n%% depending on the query string.\n\n-module(last_modified_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([get_text_plain/2]).\n-export([last_modified/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain}], Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n\nlast_modified(Req=#{qs := <<\"tuple\">>}, State) ->\n\t{{{2012, 9, 21}, {22, 36, 14}}, Req, State};\nlast_modified(Req=#{qs := <<\"undefined\">>}, State) ->\n\t{undefined, Req, State};\n%% Simulate the callback being missing in other cases.\nlast_modified(#{qs := <<\"missing\">>}, _) ->\n\tno_call.\n"
  },
  {
    "path": "test/handlers/long_polling_h.erl",
    "content": "%% This module implements a loop handler for long-polling.\n%% It starts by sending itself a message after 200ms,\n%% then sends another after that for a total of 3 messages.\n%% When it receives the last message, it sends a 102 reply back.\n\n-module(long_polling_h).\n\n-export([init/2]).\n-export([info/3]).\n-export([terminate/3]).\n\ninit(Req, _) ->\n\terlang:send_after(200, self(), timeout),\n\t{cowboy_loop, Req, 2, hibernate}.\n\ninfo(timeout, Req, 0) ->\n\t%% Send an unused status code to make sure there's no\n\t%% conflict with whatever Cowboy may send itself.\n\t{stop, cowboy_req:reply(<<\"299 OK!\">>, Req), 0};\ninfo(timeout, Req, Count) ->\n\terlang:send_after(200, self(), timeout),\n\t{ok, Req, Count - 1, hibernate}.\n\nterminate(stop, _, 0) ->\n\tok;\nterminate({error, overflow}, _, _) ->\n\tok.\n"
  },
  {
    "path": "test/handlers/long_polling_sys_h.erl",
    "content": "%% This module implements a loop handler that does nothing\n%% and expects a crash to happen.\n\n-module(long_polling_sys_h).\n\n-export([init/2]).\n-export([info/3]).\n-export([terminate/3]).\n\ninit(Req, _) ->\n\tprocess_flag(trap_exit, true),\n\terlang:send_after(500, self(), timeout),\n\t{cowboy_loop, Req, undefined}.\n\ninfo(timeout, Req, State) ->\n\t%% Send an unused status code to make sure there's no\n\t%% conflict with whatever Cowboy may send itself.\n\t{ok, cowboy_req:reply(<<\"299 OK!\">>, Req), State};\ninfo(_, Req, State) ->\n\t{ok, Req, State}.\n\nterminate(_, _, _) ->\n\tok.\n"
  },
  {
    "path": "test/handlers/loop_handler_abort_h.erl",
    "content": "%% This module implements a loop handler that reads\n%% 1000 bytes of the request body after sending itself\n%% a message, then terminates the stream.\n\n-module(loop_handler_abort_h).\n\n-export([init/2]).\n-export([info/3]).\n-export([terminate/3]).\n\ninit(Req, _) ->\n\tself() ! timeout,\n\t{cowboy_loop, Req, undefined, hibernate}.\n\ninfo(timeout, Req0, State) ->\n\t{_Status, Body, Req} = cowboy_req:read_body(Req0, #{length => 1000}),\n\t1000 = byte_size(Body),\n\t{stop, cowboy_req:reply(200, Req), State}.\n\nterminate(stop, _, _) ->\n\tok.\n"
  },
  {
    "path": "test/handlers/loop_handler_body_h.erl",
    "content": "%% This module implements a loop handler that reads\n%% the request body after sending itself a message,\n%% checks that its size is exactly 100000 bytes,\n%% then sends a 200 reply back.\n\n-module(loop_handler_body_h).\n\n-export([init/2]).\n-export([info/3]).\n-export([terminate/3]).\n\ninit(Req, _) ->\n\tself() ! timeout,\n\t{cowboy_loop, Req, undefined, hibernate}.\n\ninfo(timeout, Req0, State) ->\n\t{ok, Body, Req} = cowboy_req:read_body(Req0),\n\t100000 = byte_size(Body),\n\t{stop, cowboy_req:reply(200, Req), State}.\n\nterminate(stop, _, _) ->\n\tok.\n"
  },
  {
    "path": "test/handlers/loop_handler_endless_h.erl",
    "content": "%% This module implements a loop handler that streams endless data.\n\n-module(loop_handler_endless_h).\n\n-export([init/2]).\n-export([info/3]).\n\ninit(Req0, #{delay := Delay} = Opts) ->\n\tcase cowboy_req:header(<<\"x-test-pid\">>, Req0) of\n\t\tBinPid when is_binary(BinPid) ->\n\t\t\tPid = list_to_pid(binary_to_list(BinPid)),\n\t\t\tPid ! {Pid, self(), init},\n\t\t\tok;\n\t\t_ ->\n\t\t\tok\n\tend,\n\terlang:send_after(Delay, self(), timeout),\n\tReq = cowboy_req:stream_reply(200, Req0),\n\t{cowboy_loop, Req, Opts}.\n\ninfo(timeout, Req, State) ->\n\tcowboy_req:stream_body(<<0:10000/unit:8>>, nofin, Req),\n\t%% Equivalent to a 0 timeout.\n\tself() ! timeout,\n\t{ok, Req, State}.\n"
  },
  {
    "path": "test/handlers/loop_handler_timeout_h.erl",
    "content": "%% This module implements a loop handler that sends\n%% itself a timeout that will intentionally arrive\n%% after the HTTP/1.1 request_timeout. The protocol\n%% is not supposed to close the connection when a\n%% request is ongoing, and therefore this handler\n%% will eventually send a 200 reply.\n\n-module(loop_handler_timeout_h).\n\n-export([init/2]).\n-export([info/3]).\n-export([terminate/3]).\n\ninit(Req, _) ->\n\terlang:send_after(6000, self(), timeout),\n\t{cowboy_loop, Req, #{hibernate => true}}.\n\ninfo(timeout, Req, State) ->\n\t{stop, cowboy_req:reply(200, #{}, <<\"Good!\">>, Req), State}.\n\nterminate(stop, _, _) ->\n\tok.\n"
  },
  {
    "path": "test/handlers/loop_handler_timeout_hibernate_h.erl",
    "content": "%% This module implements a loop handler that first\n%% sets a timeout, then hibernates, then ensures\n%% that the timeout initially set no longer triggers.\n%% If everything goes fine a 200 is returned. If the\n%% timeout triggers again a 299 is.\n\n-module(loop_handler_timeout_hibernate_h).\n\n-export([init/2]).\n-export([info/3]).\n-export([terminate/3]).\n\ninit(Req, _) ->\n\tself() ! message1,\n\t{cowboy_loop, Req, undefined, 100}.\n\ninfo(message1, Req, State) ->\n\terlang:send_after(200, self(), message2),\n\t{ok, Req, State, hibernate};\ninfo(message2, Req, State) ->\n\terlang:send_after(200, self(), message3),\n\t%% Don't set a timeout now.\n\t{ok, Req, State};\ninfo(message3, Req, State) ->\n\t{stop, cowboy_req:reply(200, Req), State};\ninfo(timeout, Req, State) ->\n\t{stop, cowboy_req:reply(<<\"299 OK!\">>, Req), State}.\n\nterminate(stop, _, _) ->\n\tok.\n"
  },
  {
    "path": "test/handlers/loop_handler_timeout_info_h.erl",
    "content": "%% This module implements a loop handler that changes\n%% the timeout value to 500ms after the first message\n%% then sends itself another message after 1000ms.\n%% It is expected to timeout, that is, reply a 299.\n\n-module(loop_handler_timeout_info_h).\n\n-export([init/2]).\n-export([info/3]).\n-export([terminate/3]).\n\ninit(Req, _) ->\n\tself() ! message,\n\t{cowboy_loop, Req, undefined}.\n\ninfo(message, Req, State) ->\n\terlang:send_after(500, self(), message),\n\t{ok, Req, State, 100};\ninfo(timeout, Req, State) ->\n\t{stop, cowboy_req:reply(<<\"299 OK!\">>, Req), State}.\n\nterminate(stop, _, _) ->\n\tok.\n"
  },
  {
    "path": "test/handlers/loop_handler_timeout_init_h.erl",
    "content": "%% This module implements a loop handler that reads\n%% the request query for a timeout value, then sends\n%% itself a message after 1000ms. It replies a 200 when\n%% the message does not timeout and a 299 otherwise.\n\n-module(loop_handler_timeout_init_h).\n\n-export([init/2]).\n-export([info/3]).\n-export([terminate/3]).\n\ninit(Req, _) ->\n\t#{timeout := Timeout} = cowboy_req:match_qs([{timeout, int}], Req),\n\terlang:send_after(500, self(), message),\n\t{cowboy_loop, Req, undefined, Timeout}.\n\ninfo(message, Req, State) ->\n\t{stop, cowboy_req:reply(200, Req), State};\ninfo(timeout, Req, State) ->\n\t{stop, cowboy_req:reply(<<\"299 OK!\">>, Req), State}.\n\nterminate(stop, _, _) ->\n\tok.\n"
  },
  {
    "path": "test/handlers/multipart_h.erl",
    "content": "%% This module reads a multipart body and echoes it back as an Erlang term.\n\n-module(multipart_h).\n\n-export([init/2]).\n\ninit(Req0, State) ->\n\t{Result, Req} = case cowboy_req:binding(key, Req0) of\n\t\tundefined -> acc_multipart(Req0, []);\n\t\t<<\"skip_body\">> -> skip_body_multipart(Req0, []);\n\t\t<<\"read_part2\">> -> read_part2_multipart(Req0, []);\n\t\t<<\"read_part_body2\">> -> read_part_body2_multipart(Req0, [])\n\tend,\n\t{ok, cowboy_req:reply(200, #{}, term_to_binary(Result), Req), State}.\n\nacc_multipart(Req0, Acc) ->\n\tcase cowboy_req:read_part(Req0) of\n\t\t{ok, Headers, Req1} ->\n\t\t\t{ok, Body, Req} = stream_body(Req1, <<>>),\n\t\t\tacc_multipart(Req, [{Headers, Body}|Acc]);\n\t\t{done, Req} ->\n\t\t\t{lists:reverse(Acc), Req}\n\tend.\n\nstream_body(Req0, Acc) ->\n\tcase cowboy_req:read_part_body(Req0) of\n\t\t{more, Data, Req} ->\n\t\t\tstream_body(Req, << Acc/binary, Data/binary >>);\n\t\t{ok, Data, Req} ->\n\t\t\t{ok, << Acc/binary, Data/binary >>, Req}\n\tend.\n\nskip_body_multipart(Req0, Acc) ->\n\tcase cowboy_req:read_part(Req0) of\n\t\t{ok, Headers, Req} ->\n\t\t\tskip_body_multipart(Req, [Headers|Acc]);\n\t\t{done, Req} ->\n\t\t\t{lists:reverse(Acc), Req}\n\tend.\n\nread_part2_multipart(Req0, Acc) ->\n\tcase cowboy_req:read_part(Req0, #{length => 1, period => 1}) of\n\t\t{ok, Headers, Req1} ->\n\t\t\t{ok, Body, Req} = stream_body(Req1, <<>>),\n\t\t\tacc_multipart(Req, [{Headers, Body}|Acc]);\n\t\t{done, Req} ->\n\t\t\t{lists:reverse(Acc), Req}\n\tend.\n\nread_part_body2_multipart(Req0, Acc) ->\n\tcase cowboy_req:read_part(Req0) of\n\t\t{ok, Headers, Req1} ->\n\t\t\t{ok, Body, Req} = stream_body2(Req1, <<>>),\n\t\t\tacc_multipart(Req, [{Headers, Body}|Acc]);\n\t\t{done, Req} ->\n\t\t\t{lists:reverse(Acc), Req}\n\tend.\n\nstream_body2(Req0, Acc) ->\n\tcase cowboy_req:read_part_body(Req0, #{length => 1, period => 1}) of\n\t\t{more, Data, Req} ->\n\t\t\tstream_body(Req, << Acc/binary, Data/binary >>);\n\t\t{ok, Data, Req} ->\n\t\t\t{ok, << Acc/binary, Data/binary >>, Req}\n\tend.\n"
  },
  {
    "path": "test/handlers/provide_callback_missing_h.erl",
    "content": "-module(provide_callback_missing_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\ncontent_types_provided(Req, State) ->\n\tct_helper_error_h:ignore(cowboy_rest, set_resp_body, 2),\n\t{[{<<\"text/plain\">>, provide}], Req, State}.\n"
  },
  {
    "path": "test/handlers/provide_range_callback_h.erl",
    "content": "%% This module defines many callbacks relevant to range requests\n%% and return something different depending on query string.\n\n-module(provide_range_callback_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([ranges_provided/2]).\n-export([expires/2]).\n-export([generate_etag/2]).\n-export([last_modified/2]).\n-export([get_text_plain/2]).\n-export([get_text_plain_bytes/2]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\ncontent_types_provided(Req, State) ->\n\t{[\n\t\t{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain},\n\t\t%% This one only exists so we generate a vary header.\n\t\t{{<<\"text\">>, <<\"html\">>, []}, get_text_html}\n\t], Req, State}.\n\nranges_provided(Req, State) ->\n\t{[{<<\"bytes\">>, get_text_plain_bytes}], Req, State}.\n\ngenerate_etag(Req=#{qs := <<\"weak-etag\">>}, State) ->\n\t{{weak, <<\"weak-no-match\">>}, Req, State};\ngenerate_etag(Req, State) ->\n\t{{strong, <<\"strong-and-match\">>}, Req, State}.\n\nlast_modified(Req, State) ->\n\t{{{2222, 2, 22}, {11, 11, 11}}, Req, State}.\n\nexpires(Req, State) ->\n\t{{{3333, 3, 3}, {11, 11, 11}}, Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n\n%% Simulate the callback being missing, otherwise expect true/false.\nget_text_plain_bytes(#{qs := <<\"missing\">>}, _) ->\n\tct_helper_error_h:ignore(cowboy_rest, set_ranged_body_callback, 3),\n\tno_call;\nget_text_plain_bytes(Req=#{qs := <<\"sendfile\">>, range := {_, [{From=0, infinity}]}}, State) ->\n\tPath = code:lib_dir(cowboy) ++ \"/ebin/cowboy.app\",\n\tSize = filelib:file_size(Path),\n\t{[{{From, Size - 1, Size}, {sendfile, From, Size, Path}}], Req, State};\nget_text_plain_bytes(Req=#{range := {_, [{From=0, infinity}]}}, State) ->\n\t%% We send everything in one part.\n\tBody = <<\"This is ranged REST!\">>,\n\tTotal = byte_size(Body),\n\t{[{{From, Total - 1, Total}, Body}], Req, State};\nget_text_plain_bytes(Req=#{qs := <<\"sendfile\">>, range := {_, Range}}, State) ->\n\t%% We check the range header we get and send everything hardcoded.\n\t[\n\t\t{50, 99},\n\t\t{150, 199},\n\t\t{250, 299},\n\t\t-99\n\t] = Range,\n\tPath = code:lib_dir(cowboy) ++ \"/ebin/cowboy.app\",\n\tSize = filelib:file_size(Path),\n\t{[\n\t\t{{50, 99, Size}, {sendfile, 50, 50, Path}},\n\t\t{{150, 199, Size}, {sendfile, 150, 50, Path}},\n\t\t{{250, 299, Size}, {sendfile, 250, 50, Path}},\n\t\t{{Size - 99, Size - 1, Size}, {sendfile, Size - 99, 99, Path}}\n\t], Req, State};\nget_text_plain_bytes(Req=#{range := {_, Range}}, State) ->\n\t%% We check the range header we get and send everything hardcoded.\n\t[\n\t\t{0, 3},\n\t\t{5, 6},\n\t\t{8, 13},\n\t\t{15, infinity}\n\t] = Range,\n\tBody = <<\"This is ranged REST!\">>,\n\tTotal = byte_size(Body),\n\t{[\n\t\t{{0, 3, Total}, <<\"This\">>},\n\t\t{{5, 6, Total}, <<\"is\">>},\n\t\t{{8, 13, Total}, <<\"ranged\">>},\n\t\t{{15, 19, Total}, <<\"REST!\">>}\n\t], Req, State}.\n"
  },
  {
    "path": "test/handlers/range_satisfiable_h.erl",
    "content": "%% This module defines the range_satisfiable callback\n%% and return something different depending on query string.\n\n-module(range_satisfiable_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([ranges_provided/2]).\n-export([range_satisfiable/2]).\n-export([get_text_plain/2]).\n-export([get_text_plain_bytes/2]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\ncontent_types_provided(Req, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain}], Req, State}.\n\nranges_provided(Req, State) ->\n\t{[{<<\"bytes\">>, get_text_plain_bytes}], Req, State}.\n\n%% Simulate the callback being missing, otherwise expect true/false.\nrange_satisfiable(#{qs := <<\"missing\">>}, _) ->\n\tno_call;\nrange_satisfiable(Req=#{qs := <<\"false-int\">>}, State) ->\n\t{{false, 123}, Req, State};\nrange_satisfiable(Req=#{qs := <<\"false-bin\">>}, State) ->\n\t{{false, <<\"*/456\">>}, Req, State};\nrange_satisfiable(Req=#{qs := Qs}, State) ->\n\t{Qs =:= <<\"true\">>, Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n\nget_text_plain_bytes(Req, State) ->\n\t%% We send everything in one part, since we are not testing\n\t%% this callback specifically.\n\tBody = <<\"This is ranged REST!\">>,\n\t{[{{0, byte_size(Body) - 1, byte_size(Body)}, Body}], Req, State}.\n"
  },
  {
    "path": "test/handlers/ranges_provided_auto_h.erl",
    "content": "%% This module defines the ranges_provided callback\n%% which returns the auto option for bytes ranges\n%% and the normal ProvideCallback that returns\n%% something different depending on query string.\n\n-module(ranges_provided_auto_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([ranges_provided/2]).\n-export([get_text_plain/2]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\ncontent_types_provided(Req, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain}], Req, State}.\n\nranges_provided(Req, State) ->\n\t{[{<<\"bytes\">>, auto}], Req, State}.\n\nget_text_plain(Req=#{qs := <<\"data\">>}, State) ->\n\t{<<\"This is ranged REST!\">>, Req, State};\nget_text_plain(Req=#{qs := <<\"sendfile\">>}, State) ->\n\tPath = code:lib_dir(cowboy) ++ \"/ebin/cowboy.app\",\n\tSize = filelib:file_size(Path),\n\t{{sendfile, 0, Size, Path}, Req, State}.\n"
  },
  {
    "path": "test/handlers/ranges_provided_h.erl",
    "content": "%% This module defines the ranges_provided callback\n%% and return something different depending on query string.\n\n-module(ranges_provided_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([ranges_provided/2]).\n-export([get_text_plain/2]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\ncontent_types_provided(Req, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain}], Req, State}.\n\nranges_provided(Req=#{qs := <<\"list\">>}, State) ->\n\t{[\n\t\t{<<\"bytes\">>, get_text_plain_bytes},\n\t\t{<<\"pages\">>, get_text_plain_pages},\n\t\t{<<\"chapters\">>, get_text_plain_chapters}\n\t], Req, State};\nranges_provided(Req=#{qs := <<\"none\">>}, State) ->\n\t{[], Req, State};\n%% Simulate the callback being missing in other cases.\nranges_provided(_, _) ->\n\tno_call.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n"
  },
  {
    "path": "test/handlers/rate_limited_h.erl",
    "content": "%% This module does rate limiting based on the query string value.\n\n-module(rate_limited_h).\n\n-export([init/2]).\n-export([rate_limited/2]).\n-export([content_types_provided/2]).\n-export([get_text_plain/2]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\nrate_limited(Req=#{qs := <<\"false\">>}, State) ->\n\t{false, Req, State};\nrate_limited(Req=#{qs := <<\"true-date\">>}, State) ->\n\t{{true, {{2222, 2, 22}, {11, 11, 11}}}, Req, State};\nrate_limited(Req=#{qs := <<\"true\">>}, State) ->\n\t{{true, 3600}, Req, State}.\n\ncontent_types_provided(Req, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain}], Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n"
  },
  {
    "path": "test/handlers/read_body_h.erl",
    "content": "%% This module reads the request body fully and send a 204 response.\n\n-module(read_body_h).\n\n-export([init/2]).\n\ninit(Req0, Opts) ->\n\t{ok, Req} = read_body(Req0),\n\t{ok, cowboy_req:reply(200, #{}, Req), Opts}.\n\nread_body(Req0) ->\n\tcase cowboy_req:read_body(Req0) of\n\t\t{ok, _, Req} -> {ok, Req};\n\t\t{more, _, Req} -> read_body(Req)\n\tend.\n"
  },
  {
    "path": "test/handlers/resp_h.erl",
    "content": "%% This module echoes back the value the test is interested in.\n\n-module(resp_h).\n\n%% @todo Probably should have a separate handler for errors,\n%% so that we can dialyze all the other correct calls.\n-dialyzer({nowarn_function, do/3}).\n\n-export([init/2]).\n\ninit(Req, Opts) ->\n\tdo(cowboy_req:binding(key, Req), Req, Opts).\n\ndo(<<\"set_resp_cookie3\">>, Req0, Opts) ->\n\tReq = case cowboy_req:binding(arg, Req0) of\n\t\tundefined ->\n\t\t\tcowboy_req:set_resp_cookie(<<\"mycookie\">>, \"myvalue\", Req0);\n\t\t<<\"multiple\">> ->\n\t\t\tReq1 = cowboy_req:set_resp_cookie(<<\"mycookie\">>, \"myvalue\", Req0),\n\t\t\tcowboy_req:set_resp_cookie(<<\"yourcookie\">>, <<\"yourvalue\">>, Req1);\n\t\t<<\"overwrite\">> ->\n\t\t\tReq1 = cowboy_req:set_resp_cookie(<<\"mycookie\">>, \"myvalue\", Req0),\n\t\t\tcowboy_req:set_resp_cookie(<<\"mycookie\">>, <<\"overwrite\">>, Req1)\n\tend,\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"set_resp_cookie4\">>, Req0, Opts) ->\n\tReq = cowboy_req:set_resp_cookie(<<\"mycookie\">>, \"myvalue\", Req0,\n\t\t#{path => cowboy_req:path(Req0)}),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"set_resp_header\">>, Req0, Opts) ->\n\tReq = cowboy_req:set_resp_header(<<\"content-type\">>, <<\"text/plain\">>, Req0),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"set_resp_header_cookie\">>, Req0, Opts) ->\n\tct_helper:ignore(cowboy_req, set_resp_header, 3),\n\tReq = cowboy_req:set_resp_header(<<\"set-cookie\">>, <<\"name=value\">>, Req0),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"set_resp_header_server\">>, Req0, Opts) ->\n\tReq = cowboy_req:set_resp_header(<<\"server\">>, <<\"nginx\">>, Req0),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"set_resp_headers\">>, Req0, Opts) ->\n\tReq = cowboy_req:set_resp_headers(#{\n\t\t<<\"content-type\">> => <<\"text/plain\">>,\n\t\t<<\"content-encoding\">> => <<\"compress\">>\n\t}, Req0),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"set_resp_headers_list\">>, Req0, Opts) ->\n\tReq = cowboy_req:set_resp_headers([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>},\n\t\t{<<\"test-header\">>, <<\"one\">>},\n\t\t{<<\"content-encoding\">>, <<\"compress\">>},\n\t\t{<<\"test-header\">>, <<\"two\">>}\n\t], Req0),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"set_resp_headers_cookie\">>, Req0, Opts) ->\n\tct_helper:ignore(cowboy_req, set_resp_headers, 2),\n\tReq = cowboy_req:set_resp_headers(#{\n\t\t<<\"set-cookie\">> => <<\"name=value\">>\n\t}, Req0),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"set_resp_headers_list_cookie\">>, Req0, Opts) ->\n\tct_helper:ignore(cowboy_req, set_resp_headers_list, 3),\n\tReq = cowboy_req:set_resp_headers([\n\t\t{<<\"set-cookie\">>, <<\"name=value\">>},\n\t\t{<<\"set-cookie\">>, <<\"name2=value2\">>}\n\t], Req0),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"set_resp_headers_http11\">>, Req0, Opts) ->\n\tReq = cowboy_req:set_resp_headers(#{\n\t\t<<\"connection\">> => <<\"custom-header, close\">>,\n\t\t<<\"custom-header\">> => <<\"value\">>,\n\t\t<<\"keep-alive\">> => <<\"timeout=5, max=1000\">>,\n\t\t<<\"proxy-connection\">> => <<\"close\">>,\n\t\t<<\"transfer-encoding\">> => <<\"chunked\">>,\n\t\t<<\"upgrade\">> => <<\"HTTP/1.1\">>\n\t}, Req0),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"resp_header_defined\">>, Req0, Opts) ->\n\tReq1 = cowboy_req:set_resp_header(<<\"content-type\">>, <<\"text/plain\">>, Req0),\n\t<<\"text/plain\">> = cowboy_req:resp_header(<<\"content-type\">>, Req1),\n\t<<\"text/plain\">> = cowboy_req:resp_header(<<\"content-type\">>, Req1, default),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req0), Opts};\ndo(<<\"resp_header_default\">>, Req, Opts) ->\n\tundefined = cowboy_req:resp_header(<<\"content-type\">>, Req),\n\tdefault = cowboy_req:resp_header(<<\"content-type\">>, Req, default),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"resp_headers\">>, Req0, Opts) ->\n\tReq1 = cowboy_req:set_resp_header(<<\"server\">>, <<\"nginx\">>, Req0),\n\tReq = cowboy_req:set_resp_headers(#{\n\t\t<<\"content-type\">> => <<\"text/plain\">>,\n\t\t<<\"content-encoding\">> => <<\"compress\">>\n\t}, Req1),\n\tHeaders = cowboy_req:resp_headers(Req),\n\ttrue = maps:is_key(<<\"server\">>, Headers),\n\ttrue = maps:is_key(<<\"content-type\">>, Headers),\n\ttrue = maps:is_key(<<\"content-encoding\">>, Headers),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"resp_headers_empty\">>, Req, Opts) ->\n\t#{} = cowboy_req:resp_headers(Req),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"set_resp_body\">>, Req0, Opts) ->\n\tArg = cowboy_req:binding(arg, Req0),\n\tReq1 = case Arg of\n\t\t<<\"sendfile0\">> ->\n\t\t\tAppFile = code:where_is_file(\"cowboy.app\"),\n\t\t\tcowboy_req:set_resp_body({sendfile, 0, 0, AppFile}, Req0);\n\t\t<<\"sendfile\">> ->\n\t\t\tAppFile = code:where_is_file(\"cowboy.app\"),\n\t\t\tcowboy_req:set_resp_body({sendfile, 0, filelib:file_size(AppFile), AppFile}, Req0);\n\t\t_ ->\n\t\t\tcowboy_req:set_resp_body(<<\"OK\">>, Req0)\n\tend,\n\tReq = case Arg of\n\t\t<<\"override\">> ->\n\t\t\tcowboy_req:reply(200, #{}, <<\"OVERRIDE\">>, Req1);\n\t\t_ ->\n\t\t\tcowboy_req:reply(200, Req1)\n\tend,\n\t{ok, Req, Opts};\ndo(<<\"has_resp_header\">>, Req0, Opts) ->\n\tfalse = cowboy_req:has_resp_header(<<\"content-type\">>, Req0),\n\tReq = cowboy_req:set_resp_header(<<\"content-type\">>, <<\"text/plain\">>, Req0),\n\ttrue = cowboy_req:has_resp_header(<<\"content-type\">>, Req),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"has_resp_body\">>, Req0, Opts) ->\n\tcase cowboy_req:binding(arg, Req0) of\n\t\t<<\"sendfile\">> ->\n\t\t\t%% @todo Cases for sendfile. Note that sendfile 0 is unallowed.\n\t\t\tfalse = cowboy_req:has_resp_body(Req0),\n\t\t\tReq = cowboy_req:set_resp_body({sendfile, 0, 10, code:where_is_file(\"cowboy.app\")}, Req0),\n\t\t\ttrue = cowboy_req:has_resp_body(Req),\n\t\t\t{ok, cowboy_req:reply(200, #{}, <<\"OK\">>, Req), Opts};\n\t\tundefined ->\n\t\t\tfalse = cowboy_req:has_resp_body(Req0),\n\t\t\tReq = cowboy_req:set_resp_body(<<\"OK\">>, Req0),\n\t\t\ttrue = cowboy_req:has_resp_body(Req),\n\t\t\t{ok, cowboy_req:reply(200, #{}, Req), Opts}\n\tend;\ndo(<<\"delete_resp_header\">>, Req0, Opts) ->\n\t%% We try to delete first even though it hasn't been set to\n\t%% make sure this noop is possible.\n\tReq1 = cowboy_req:delete_resp_header(<<\"content-type\">>, Req0),\n\tfalse = cowboy_req:has_resp_header(<<\"content-type\">>, Req1),\n\tReq2 = cowboy_req:set_resp_header(<<\"content-type\">>, <<\"text/plain\">>, Req1),\n\ttrue = cowboy_req:has_resp_header(<<\"content-type\">>, Req2),\n\tReq = cowboy_req:delete_resp_header(<<\"content-type\">>, Req2),\n\tfalse = cowboy_req:has_resp_header(<<\"content-type\">>, Req),\n\t{ok, cowboy_req:reply(200, #{}, \"OK\", Req), Opts};\ndo(<<\"inform2\">>, Req0, Opts) ->\n\tcase cowboy_req:binding(arg, Req0) of\n\t\t<<\"binary\">> ->\n\t\t\tcowboy_req:inform(<<\"102 On my way\">>, Req0);\n\t\t<<\"error\">> ->\n\t\t\tct_helper:ignore(cowboy_req, inform, 3),\n\t\t\tcowboy_req:inform(ok, Req0);\n\t\t<<\"twice\">> ->\n\t\t\tcowboy_req:inform(102, Req0),\n\t\t\tcowboy_req:inform(102, Req0);\n\t\t<<\"after_reply\">> ->\n\t\t\tct_helper:ignore(cowboy_req, inform, 3),\n\t\t\tReq1 = cowboy_req:reply(200, Req0),\n\t\t\tcowboy_req:inform(102, Req1);\n\t\tStatus ->\n\t\t\tcowboy_req:inform(binary_to_integer(Status), Req0)\n\tend,\n\tReq = cowboy_req:reply(200, Req0),\n\t{ok, Req, Opts};\ndo(<<\"inform3\">>, Req0, Opts) ->\n\tHeaders = #{<<\"ext-header\">> => <<\"ext-value\">>},\n\tcase cowboy_req:binding(arg, Req0) of\n\t\t<<\"binary\">> ->\n\t\t\tcowboy_req:inform(<<\"102 On my way\">>, Headers, Req0);\n\t\t<<\"error\">> ->\n\t\t\tct_helper:ignore(cowboy_req, inform, 3),\n\t\t\tcowboy_req:inform(ok, Headers, Req0);\n\t\t<<\"set_cookie\">> ->\n\t\t\tct_helper:ignore(cowboy_req, inform, 3),\n\t\t\tcowboy_req:inform(102, #{<<\"set-cookie\">> => <<\"name=value\">>}, Req0);\n\t\t<<\"twice\">> ->\n\t\t\tcowboy_req:inform(102, Headers, Req0),\n\t\t\tcowboy_req:inform(102, Headers, Req0);\n\t\t<<\"after_reply\">> ->\n\t\t\tct_helper:ignore(cowboy_req, inform, 3),\n\t\t\tReq1 = cowboy_req:reply(200, Req0),\n\t\t\tcowboy_req:inform(102, Headers, Req1);\n\t\tStatus ->\n\t\t\tcowboy_req:inform(binary_to_integer(Status), Headers, Req0)\n\tend,\n\tReq = cowboy_req:reply(200, Req0),\n\t{ok, Req, Opts};\ndo(<<\"reply2\">>, Req0, Opts) ->\n\tReq = case cowboy_req:binding(arg, Req0) of\n\t\t<<\"binary\">> ->\n\t\t\tcowboy_req:reply(<<\"200 GOOD\">>, Req0);\n\t\t<<\"error\">> ->\n\t\t\tct_helper:ignore(cowboy_req, reply, 4),\n\t\t\tcowboy_req:reply(ok, Req0);\n\t\t<<\"twice\">> ->\n\t\t\tct_helper:ignore(cowboy_req, reply, 4),\n\t\t\tReq1 = cowboy_req:reply(200, Req0),\n\t\t\ttimer:sleep(100),\n\t\t\tcowboy_req:reply(200, Req1);\n\t\tStatus ->\n\t\t\tcowboy_req:reply(binary_to_integer(Status), Req0)\n\tend,\n\t{ok, Req, Opts};\ndo(<<\"reply3\">>, Req0, Opts) ->\n\tReq = case cowboy_req:binding(arg, Req0) of\n\t\t<<\"error\">> ->\n\t\t\tct_helper:ignore(cowboy_req, reply, 4),\n\t\t\tcowboy_req:reply(200, ok, Req0);\n\t\t<<\"set_cookie\">> ->\n\t\t\tct_helper:ignore(cowboy_req, reply, 4),\n\t\t\tcowboy_req:reply(200, #{<<\"set-cookie\">> => <<\"name=value\">>}, Req0);\n\t\tStatus ->\n\t\t\tcowboy_req:reply(binary_to_integer(Status),\n\t\t\t\t#{<<\"content-type\">> => <<\"text/plain\">>}, Req0)\n\tend,\n\t{ok, Req, Opts};\ndo(<<\"reply4\">>, Req0, Opts) ->\n\tReq = case cowboy_req:binding(arg, Req0) of\n\t\t<<\"error\">> ->\n\t\t\tct_helper:ignore(erlang, iolist_size, 1),\n\t\t\tcowboy_req:reply(200, #{}, ok, Req0);\n\t\t<<\"set_cookie\">> ->\n\t\t\tct_helper:ignore(cowboy_req, reply, 4),\n\t\t\tcowboy_req:reply(200, #{<<\"set-cookie\">> => <<\"name=value\">>}, <<\"OK\">>, Req0);\n\t\t<<\"204body\">> ->\n\t\t\tct_helper:ignore(cowboy_req, do_reply_ensure_no_body, 4),\n\t\t\tcowboy_req:reply(204, #{}, <<\"OK\">>, Req0);\n\t\t<<\"304body\">> ->\n\t\t\tct_helper:ignore(cowboy_req, do_reply_ensure_no_body, 4),\n\t\t\tcowboy_req:reply(304, #{}, <<\"OK\">>, Req0);\n\t\tStatus ->\n\t\t\tcowboy_req:reply(binary_to_integer(Status), #{}, <<\"OK\">>, Req0)\n\tend,\n\t{ok, Req, Opts};\ndo(<<\"stream_reply2\">>, Req0, Opts) ->\n\tcase cowboy_req:binding(arg, Req0) of\n\t\t<<\"binary\">> ->\n\t\t\tReq = cowboy_req:stream_reply(<<\"200 GOOD\">>, Req0),\n\t\t\tstream_body(Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"error\">> ->\n\t\t\tct_helper:ignore(cowboy_req, stream_reply, 3),\n\t\t\tReq = cowboy_req:stream_reply(ok, Req0),\n\t\t\tstream_body(Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"204\">> ->\n\t\t\tReq = cowboy_req:stream_reply(204, Req0),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"204body\">> ->\n\t\t\tct_helper:ignore(cowboy_req, stream_body, 3),\n\t\t\tReq = cowboy_req:stream_reply(204, Req0),\n\t\t\tstream_body(Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"304body\">> ->\n\t\t\tct_helper:ignore(cowboy_req, stream_body, 3),\n\t\t\tReq = cowboy_req:stream_reply(304, Req0),\n\t\t\tstream_body(Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"twice\">> ->\n\t\t\tct_helper:ignore(cowboy_req, stream_reply, 3),\n\t\t\tReq1 = cowboy_req:stream_reply(200, Req0),\n\t\t\ttimer:sleep(100),\n\t\t\t%% We will crash here so the body shouldn't be sent.\n\t\t\tReq = cowboy_req:stream_reply(200, Req1),\n\t\t\tstream_body(Req),\n\t\t\t{ok, Req, Opts};\n\t\tStatus ->\n\t\t\tReq = cowboy_req:stream_reply(binary_to_integer(Status), Req0),\n\t\t\tstream_body(Req),\n\t\t\t{ok, Req, Opts}\n\tend;\ndo(<<\"stream_reply3\">>, Req0, Opts) ->\n\tReq = case cowboy_req:binding(arg, Req0) of\n\t\t<<\"error\">> ->\n\t\t\tct_helper:ignore(cowboy_req, stream_reply, 3),\n\t\t\tcowboy_req:stream_reply(200, ok, Req0);\n\t\t<<\"set_cookie\">> ->\n\t\t\tct_helper:ignore(cowboy_req, stream_reply, 3),\n\t\t\tcowboy_req:stream_reply(200, #{<<\"set-cookie\">> => <<\"name=value\">>}, Req0);\n\t\tStatus ->\n\t\t\tcowboy_req:stream_reply(binary_to_integer(Status),\n\t\t\t\t#{<<\"content-type\">> => <<\"text/plain\">>}, Req0)\n\tend,\n\tstream_body(Req),\n\t{ok, Req, Opts};\ndo(<<\"stream_body\">>, Req0, Opts) ->\n\tcase cowboy_req:binding(arg, Req0) of\n\t\t<<\"fin0\">> ->\n\t\t\tReq = cowboy_req:stream_reply(200, Req0),\n\t\t\tcowboy_req:stream_body(<<\"Hello world!\">>, nofin, Req),\n\t\t\tcowboy_req:stream_body(<<>>, fin, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"multiple\">> ->\n\t\t\tReq = cowboy_req:stream_reply(200, Req0),\n\t\t\tcowboy_req:stream_body(<<\"Hello \">>, nofin, Req),\n\t\t\tcowboy_req:stream_body(<<\"world\">>, nofin, Req),\n\t\t\tcowboy_req:stream_body(<<\"!\">>, fin, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"loop\">> ->\n\t\t\tReq = cowboy_req:stream_reply(200, Req0),\n\t\t\t_ = [cowboy_req:stream_body(<<0:1000000/unit:8>>, nofin, Req)\n\t\t\t\t|| _ <- lists:seq(1, 32)],\n\t\t\t{ok, Req, Opts};\n\t\t<<\"nofin\">> ->\n\t\t\tReq = cowboy_req:stream_reply(200, Req0),\n\t\t\tcowboy_req:stream_body(<<\"Hello world!\">>, nofin, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"sendfile\">> ->\n\t\t\tAppFile = code:where_is_file(\"cowboy.app\"),\n\t\t\tAppSize = filelib:file_size(AppFile),\n\t\t\tReq = cowboy_req:stream_reply(200, Req0),\n\t\t\tcowboy_req:stream_body(<<\"Hello \">>, nofin, Req),\n\t\t\tcowboy_req:stream_body({sendfile, 0, AppSize, AppFile}, nofin, Req),\n\t\t\tcowboy_req:stream_body(<<\" interspersed \">>, nofin, Req),\n\t\t\tcowboy_req:stream_body({sendfile, 0, AppSize, AppFile}, nofin, Req),\n\t\t\tcowboy_req:stream_body(<<\" world!\">>, fin, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"sendfile_fin\">> ->\n\t\t\tAppFile = code:where_is_file(\"cowboy.app\"),\n\t\t\tAppSize = filelib:file_size(AppFile),\n\t\t\tReq = cowboy_req:stream_reply(200, Req0),\n\t\t\tcowboy_req:stream_body(<<\"Hello! \">>, nofin, Req),\n\t\t\tcowboy_req:stream_body({sendfile, 0, AppSize, AppFile}, fin, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"spawn\">> ->\n\t\t\tReq = cowboy_req:stream_reply(200, Req0),\n\t\t\tParent = self(),\n\t\t\tPid = spawn(fun() ->\n\t\t\t\tcowboy_req:stream_body(<<\"Hello \">>, nofin, Req),\n\t\t\t\tcowboy_req:stream_body(<<\"world\">>, nofin, Req),\n\t\t\t\tcowboy_req:stream_body(<<\"!\">>, fin, Req),\n\t\t\t\tParent ! {self(), ok}\n\t\t\tend),\n\t\t\treceive\n\t\t\t\t{Pid, ok} -> ok\n\t\t\tafter 5000 ->\n\t\t\t\terror(timeout)\n\t\t\tend,\n\t\t\t{ok, Req, Opts};\n\t\t_ ->\n\t\t\t%% Call stream_body without initiating streaming.\n\t\t\tcowboy_req:stream_body(<<0:800000>>, fin, Req0),\n\t\t\t{ok, Req0, Opts}\n\tend;\ndo(<<\"stream_body_content_length\">>, Req0, Opts) ->\n\tcase cowboy_req:binding(arg, Req0) of\n\t\t<<\"fin0\">> ->\n\t\t\tReq1 = cowboy_req:set_resp_header(<<\"content-length\">>, <<\"12\">>, Req0),\n\t\t\tReq = cowboy_req:stream_reply(200, Req1),\n\t\t\tcowboy_req:stream_body(<<\"Hello world!\">>, nofin, Req),\n\t\t\tcowboy_req:stream_body(<<>>, fin, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"multiple\">> ->\n\t\t\tReq1 = cowboy_req:set_resp_header(<<\"content-length\">>, <<\"12\">>, Req0),\n\t\t\tReq = cowboy_req:stream_reply(200, Req1),\n\t\t\tcowboy_req:stream_body(<<\"Hello \">>, nofin, Req),\n\t\t\tcowboy_req:stream_body(<<\"world\">>, nofin, Req),\n\t\t\tcowboy_req:stream_body(<<\"!\">>, fin, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"nofin\">> ->\n\t\t\tReq1 = cowboy_req:set_resp_header(<<\"content-length\">>, <<\"12\">>, Req0),\n\t\t\tReq = cowboy_req:stream_reply(200, Req1),\n\t\t\tcowboy_req:stream_body(<<\"Hello world!\">>, nofin, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"nofin-error\">> ->\n\t\t\tReq1 = cowboy_req:set_resp_header(<<\"content-length\">>, <<\"12\">>, Req0),\n\t\t\tReq = cowboy_req:stream_reply(200, Req1),\n\t\t\tcowboy_req:stream_body(<<\"Hello\">>, nofin, Req),\n\t\t\t{ok, Req, Opts}\n\tend;\ndo(<<\"stream_events\">>, Req0, Opts) ->\n\tcase cowboy_req:binding(arg, Req0) of\n\t\t%%<<\"single\">>\n\t\t%%<<\"list\">>\n\t\t<<\"single\">> ->\n\t\t\tReq = cowboy_req:stream_reply(200,\n\t\t\t\t#{<<\"content-type\">> => <<\"text/event-stream\">>},\n\t\t\t\tReq0),\n\t\t\tcowboy_req:stream_events(#{\n\t\t\t\tevent => <<\"add_comment\">>,\n\t\t\t\tdata => <<\"Comment text.\\nWith many lines.\">>\n\t\t\t}, fin, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"list\">> ->\n\t\t\tReq = cowboy_req:stream_reply(200,\n\t\t\t\t#{<<\"content-type\">> => <<\"text/event-stream\">>},\n\t\t\t\tReq0),\n\t\t\tcowboy_req:stream_events([\n\t\t\t\t#{\n\t\t\t\t\tevent => <<\"add_comment\">>,\n\t\t\t\t\tdata => <<\"Comment text.\\nWith many lines.\">>\n\t\t\t\t},\n\t\t\t\t#{\n\t\t\t\t\tcomment => <<\"Set retry higher\\nwith many lines also.\">>,\n\t\t\t\t\tretry => 10000\n\t\t\t\t},\n\t\t\t\t#{\n\t\t\t\t\tid => <<\"123\">>,\n\t\t\t\t\tevent => <<\"add_comment\">>,\n\t\t\t\t\tdata => <<\"Closing!\">>\n\t\t\t\t}\n\t\t\t], fin, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"multiple\">> ->\n\t\t\tReq = cowboy_req:stream_reply(200,\n\t\t\t\t#{<<\"content-type\">> => <<\"text/event-stream\">>},\n\t\t\t\tReq0),\n\t\t\tcowboy_req:stream_events(#{\n\t\t\t\tevent => <<\"add_comment\">>,\n\t\t\t\tdata => <<\"Comment text.\\nWith many lines.\">>\n\t\t\t}, nofin, Req),\n\t\t\tcowboy_req:stream_events(#{\n\t\t\t\tcomment => <<\"Set retry higher\\nwith many lines also.\">>,\n\t\t\t\tretry => 10000\n\t\t\t}, nofin, Req),\n\t\t\tcowboy_req:stream_events(#{\n\t\t\t\tid => <<\"123\">>,\n\t\t\t\tevent => <<\"add_comment\">>,\n\t\t\t\tdata => <<\"Closing!\">>\n\t\t\t}, fin, Req),\n\t\t\t{ok, Req, Opts}\n\tend;\ndo(<<\"stream_trailers\">>, Req0, Opts) ->\n\tcase cowboy_req:binding(arg, Req0) of\n\t\t<<\"large\">> ->\n\t\t\tReq = cowboy_req:stream_reply(200, #{\n\t\t\t\t<<\"trailer\">> => <<\"grpc-status\">>\n\t\t\t}, Req0),\n\t\t\t%% The size should be larger than StreamSize and ConnSize\n\t\t\tcowboy_req:stream_body(<<0:80000000>>, nofin, Req),\n\t\t\tcowboy_req:stream_trailers(#{\n\t\t\t\t<<\"grpc-status\">> => <<\"0\">>\n\t\t\t}, Req),\n\t\t\t{ok, Req, Opts};\n\t\t<<\"set_cookie\">> ->\n\t\t\tct_helper:ignore(cowboy_req, stream_trailers, 2),\n\t\t\tReq = cowboy_req:stream_reply(200, #{\n\t\t\t\t<<\"trailer\">> => <<\"set-cookie\">>\n\t\t\t}, Req0),\n\t\t\tcowboy_req:stream_body(<<\"Hello world!\">>, nofin, Req),\n\t\t\tcowboy_req:stream_trailers(#{\n\t\t\t\t<<\"set-cookie\">> => <<\"name=value\">>\n\t\t\t}, Req),\n\t\t\t{ok, Req, Opts};\n\t\t_ ->\n\t\t\tReq = cowboy_req:stream_reply(200, #{\n\t\t\t\t<<\"trailer\">> => <<\"grpc-status\">>\n\t\t\t}, Req0),\n\t\t\tcowboy_req:stream_body(<<\"Hello world!\">>, nofin, Req),\n\t\t\tcowboy_req:stream_trailers(#{\n\t\t\t\t<<\"grpc-status\">> => <<\"0\">>\n\t\t\t}, Req),\n\t\t\t{ok, Req, Opts}\n\tend;\ndo(<<\"push\">>, Req, Opts) ->\n\tcase cowboy_req:binding(arg, Req) of\n\t\t<<\"read_body\">> ->\n\t\t\tcowboy_req:push(\"/echo/read_body\", #{}, Req, #{});\n\t\t<<\"method\">> ->\n\t\t\tcowboy_req:push(\"/static/style.css\", #{<<\"accept\">> => <<\"text/css\">>}, Req,\n\t\t\t\t#{method => <<\"HEAD\">>});\n\t\t<<\"origin\">> ->\n\t\t\tcowboy_req:push(\"/static/style.css\", #{<<\"accept\">> => <<\"text/css\">>}, Req,\n\t\t\t\t#{scheme => <<\"ftp\">>, host => <<\"127.0.0.1\">>, port => 21});\n\t\t<<\"qs\">> ->\n\t\t\tcowboy_req:push(\"/static/style.css\", #{<<\"accept\">> => <<\"text/css\">>}, Req,\n\t\t\t\t#{qs => <<\"server=cowboy&version=2.0\">>});\n\t\t<<\"after_reply\">> ->\n\t\t\tct_helper:ignore(cowboy_req, push, 4),\n\t\t\tReq1 = cowboy_req:reply(200, Req),\n\t\t\t%% We will crash here so no need to worry about propagating Req1.\n\t\t\tcowboy_req:push(\"/static/style.css\", #{<<\"accept\">> => <<\"text/css\">>}, Req1);\n\t\t_ ->\n\t\t\tcowboy_req:push(\"/static/style.css\", #{<<\"accept\">> => <<\"text/css\">>}, Req),\n\t\t\t%% The text/plain mime is not defined by default, so a 406 will be returned.\n\t\t\tcowboy_req:push(\"/static/plain.txt\", #{<<\"accept\">> => <<\"text/plain\">>}, Req)\n\tend,\n\t{ok, cowboy_req:reply(200, Req), Opts}.\n\nstream_body(Req) ->\n\t_ = [cowboy_req:stream_body(<<0:800000>>, nofin, Req) || _ <- lists:seq(1,9)],\n\tcowboy_req:stream_body(<<0:800000>>, fin, Req).\n"
  },
  {
    "path": "test/handlers/resp_iolist_body_h.erl",
    "content": "%% This module sends an iolist with various odd elements in it as a response.\n\n-module(resp_iolist_body_h).\n\n-dialyzer(no_improper_lists).\n\n-export([init/2]).\n\ninit(Req0, State) ->\n\tReq = cowboy_req:reply(200, #{\n\t\t<<\"content-type\">> => <<\"text/html\">>\n\t}, [[[[<<>> | <<>> ] | <<>>] | [ <<>> ]], <<\"\n    <p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p>\n    <p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n    <p>\n    Ut eam dicunt voluptua principes, dicit perfecto mediocrem ad eam. Te suas integre quo. Nec posse atqui omittantur no, ad sea sumo veritus mandamus. Qui facer viris latine et. Cu tation altera quo, illud oporteat est ne.\n    </p>\n    <p>\n    His in quod noluisse vivendum. Modus etiam alterum et pri. Id quo dolorem indoctum. Elitr signiferumque at cum. Id habeo graeci consetetur qui. Nam ut sumo epicuri.\n    </p>\n    <p>\n    Assentior voluptatum ex eum. Ea est nemore democritum, ei odio iriure accumsan nam, no veniam voluptua perpetua vim. Vim et volumus denique, ad verear argumentum vim. At idque velit cum, quis illum ponderum eos te. Integre labitur disputando et pri. Te atqui legere adipisci has, no eum erant verear appellantur.\n    </p>\n    <p>\n    Quod audire abhorreant in est, pro novum partiendo ei, et quot porro pericula cum. Quaestio interesset scribentur cu nec, usu ei tritani eligendi adipiscing. Mea at antiopam dissentias constituam, an eam illud graece, probo habeo minim eam no. Sit aliquam interesset et.\n    </p>\n    <p>\n    An purto tota equidem his, et nec aliquid splendide, has ut ridens deserunt. Has at omittam appellantur, ei lorem audire gubergren vis. Ei sumo erat comprehensam nam, eam an enim ceteros corpora. Mea ut eirmod eripuit ornatus ceteros.\n    <p><p>\n    Ne per causae definitiones, ut veniam vocent cum. Eu torquatos expetendis eam. Volumus delicata neglegentur ne eam. Ut mel ubique facilis fastidii, cum no temporibus adversarium. Mucius scribentur intellegebat quo eu, id luptatum inciderint scribentur nam. Duis propriae in eam, an cum forensibus temporibus. Magna animal necessitatibus et sed, erroribus evertitur an est.\n    </p>\n    <p>\n    Posse ipsum sapientem at pri, eam ut option vocibus. Cu nullam corpora ius, ne stet splendide est. Meliore ponderum nec ea, quo ea suscipit phaedrum. Per wisi elaboraret ut.\n    </p>\n    <p>\n    Agam dicta sensibus quo an, vel ipsum veniam graeco cu. An viris aeterno dolorem est, novum diceret gubergren cum ad. Usu menandri patrioque scripserit te, usu an fugit molestiae. Qui tollit appellantur ut, pri solet aperiam facilis te. Integre electram quo et, persequeris consectetuer ne nam.\n    </p>\n\t\">>, <<\"THIS WILL NEVER EVER LOAD\">>], Req0),\n\t{ok, Req, State}.\n"
  },
  {
    "path": "test/handlers/rest_hello_h.erl",
    "content": "%% This module sends a hello world response via a REST handler.\n\n-module(rest_hello_h).\n\n-export([init/2]).\n-export([content_types_provided/2]).\n-export([get_text_plain/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_rest, Req, Opts}.\n\ncontent_types_provided(Req, State) ->\n\t{[{{<<\"text\">>, <<\"plain\">>, []}, get_text_plain}], Req, State}.\n\nget_text_plain(Req, State) ->\n\t{<<\"This is REST!\">>, Req, State}.\n"
  },
  {
    "path": "test/handlers/send_message_h.erl",
    "content": "%% This module sends a message to the pid passed in a header.\n\n-module(send_message_h).\n-export([init/2]).\n\ninit(Req, State) ->\n\tPid = list_to_pid(binary_to_list(cowboy_req:header(<<\"x-test-pid\">>, Req))),\n\tPid ! {Pid, self(), init, Req, State},\n\t{ok, cowboy_req:reply(200, Req), State}.\n"
  },
  {
    "path": "test/handlers/set_options_h.erl",
    "content": "%% This module sets options dynamically and performs\n%% some related relevant operation for testing the change.\n\n-module(set_options_h).\n\n-export([init/2]).\n\ninit(Req, State) ->\n\tset_options(cowboy_req:binding(key, Req), Req, State).\n\nset_options(<<\"chunked_false\">>, Req0, State) ->\n\tcowboy_req:cast({set_options, #{chunked => false}}, Req0),\n\tReq = cowboy_req:stream_reply(200, Req0),\n\tcowboy_req:stream_body(<<0:8000000>>, fin, Req),\n\t{ok, Req, State};\nset_options(<<\"chunked_false_ignored\">>, Req0, State) ->\n\tcowboy_req:cast({set_options, #{chunked => false}}, Req0),\n\tReq = cowboy_req:reply(200, #{}, <<\"Hello world!\">>, Req0),\n\t{ok, Req, State};\nset_options(<<\"idle_timeout_short\">>, Req0, State) ->\n\tcowboy_req:cast({set_options, #{idle_timeout => 500}}, Req0),\n\t{_, Body, Req} = cowboy_req:read_body(Req0),\n\t{ok, cowboy_req:reply(200, #{}, Body, Req), State};\nset_options(<<\"idle_timeout_long\">>, Req0, State) ->\n\tcowboy_req:cast({set_options, #{idle_timeout => 60000}}, Req0),\n\t{_, Body, Req} = cowboy_req:read_body(Req0),\n\t{ok, cowboy_req:reply(200, #{}, Body, Req), State};\nset_options(<<\"metrics_user_data\">>, Req, State) ->\n\tcowboy_req:cast({set_options, #{metrics_user_data => #{handler => ?MODULE}}}, Req),\n\t{ok, cowboy_req:reply(200, #{}, <<\"Hello world!\">>, Req), State}.\n"
  },
  {
    "path": "test/handlers/stop_handler_h.erl",
    "content": "%% This module returns stop based on the query string.\n%% Success is indicated via a 248 status code in the response.\n\n-module(stop_handler_h).\n\n-export([init/2]).\n\n-export([allowed_methods/2]).\n-export([allow_missing_post/2]).\n-export([charsets_provided/2]).\n-export([content_types_accepted/2]).\n-export([content_types_provided/2]).\n-export([delete_completed/2]).\n-export([delete_resource/2]).\n-export([forbidden/2]).\n-export([is_authorized/2]).\n-export([is_conflict/2]).\n-export([known_methods/2]).\n-export([languages_provided/2]).\n-export([malformed_request/2]).\n-export([moved_permanently/2]).\n-export([moved_temporarily/2]).\n-export([multiple_choices/2]).\n-export([options/2]).\n-export([previously_existed/2]).\n-export([range_satisfiable/2]).\n-export([ranges_provided/2]).\n-export([rate_limited/2]).\n-export([resource_exists/2]).\n-export([service_available/2]).\n-export([uri_too_long/2]).\n-export([valid_content_headers/2]).\n-export([valid_entity_length/2]).\n\n-export([accept/2]).\n-export([provide/2]).\n-export([provide_range/2]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\nallowed_methods(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nallow_missing_post(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\ncharsets_provided(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\ncontent_types_accepted(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\ncontent_types_provided(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\ndelete_completed(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\ndelete_resource(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nforbidden(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nis_authorized(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nis_conflict(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nknown_methods(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nlanguages_provided(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nmalformed_request(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nmoved_permanently(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nmoved_temporarily(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nmultiple_choices(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\noptions(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\npreviously_existed(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nrange_satisfiable(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nranges_provided(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nrate_limited(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nresource_exists(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nservice_available(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nuri_too_long(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nvalid_content_headers(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nvalid_entity_length(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\naccept(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nprovide(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nprovide_range(Req, State) ->\n\tmaybe_stop_handler(Req, State, ?FUNCTION_NAME).\n\nmaybe_stop_handler(Req=#{qs := Qs}, State, StateName) ->\n\tcase atom_to_binary(StateName, latin1) of\n\t\tQs -> do_stop_handler(Req, State);\n\t\t_ -> do_default(Req, State, StateName)\n\tend.\n\n%% These are all the methods necessary to reach all callbacks.\ndo_default(Req, State, allowed_methods) ->\n\t{[<<\"GET\">>, <<\"POST\">>, <<\"PUT\">>, <<\"DELETE\">>, <<\"OPTIONS\">>], Req, State};\n%% We need to accept/provide media types to reach these callbacks.\ndo_default(Req, State, content_types_accepted) ->\n\t{[{<<\"text/plain\">>, accept}], Req, State};\ndo_default(Req, State, content_types_provided) ->\n\t{[{<<\"text/plain\">>, provide}], Req, State};\n%% We need to accept ranges to reach these callbacks.\ndo_default(Req=#{qs := <<\"range_satisfiable\">>}, State, ranges_provided) ->\n\t{[{<<\"bytes\">>, provide_range}], Req, State};\ndo_default(Req=#{qs := <<\"provide_range\">>}, State, ranges_provided) ->\n\t{[{<<\"bytes\">>, provide_range}], Req, State};\n%% We need resource_exists to return false to reach these callbacks.\ndo_default(Req=#{qs := <<\"allow_missing_post\">>}, State, resource_exists) ->\n\t{false, Req, State};\ndo_default(Req=#{qs := <<\"moved_permanently\">>}, State, resource_exists) ->\n\t{false, Req, State};\ndo_default(Req=#{qs := <<\"moved_temporarily\">>}, State, resource_exists) ->\n\t{false, Req, State};\ndo_default(Req=#{qs := <<\"previously_existed\">>}, State, resource_exists) ->\n\t{false, Req, State};\n%% We need previously_existed to return true to reach these callbacks.\ndo_default(Req=#{qs := <<\"moved_permanently\">>}, State, previously_existed) ->\n\t{true, Req, State};\ndo_default(Req=#{qs := <<\"moved_temporarily\">>}, State, previously_existed) ->\n\t{true, Req, State};\n%% We need the DELETE to suceed to reach this callback.\ndo_default(Req=#{qs := <<\"delete_completed\">>}, State, delete_resource) ->\n\t{true, Req, State};\n%% We should never reach these callbacks.\ndo_default(Req, State, accept) ->\n\t{false, Req, State};\ndo_default(Req, State, provide) ->\n\t{<<\"This is REST!\">>, Req, State};\ndo_default(Req, State, provide_range) ->\n\t{<<\"This is ranged REST!\">>, Req, State};\n%% Simulate the callback being missing in any other cases.\ndo_default(_, _, _) ->\n\tno_call.\n\ndo_stop_handler(Req0, State) ->\n\tReq = cowboy_req:reply(<<\"248 REST handler stopped!\">>, #{}, <<>>, Req0),\n\t{stop, Req, State}.\n"
  },
  {
    "path": "test/handlers/stream_handler_h.erl",
    "content": "%% This module behaves differently depending on a specific header.\n\n-module(stream_handler_h).\n-behavior(cowboy_stream).\n\n-export([init/3]).\n-export([data/4]).\n-export([info/3]).\n-export([terminate/3]).\n-export([early_error/5]).\n\n%% For switch_protocol.\n-export([takeover/7]).\n\n-record(state, {\n\tpid,\n\ttest\n}).\n\ninit(StreamID, Req, Opts) ->\n\tPid = list_to_pid(binary_to_list(cowboy_req:header(<<\"x-test-pid\">>, Req))),\n\tTest = binary_to_atom(cowboy_req:header(<<\"x-test-case\">>, Req), latin1),\n\tState = #state{pid=Pid, test=Test},\n\tPid ! {Pid, self(), init, StreamID, Req, Opts},\n\t{init_commands(StreamID, Req, State), State}.\n\ninit_commands(_, _, #state{test=crash_in_init}) ->\n\terror(crash);\ninit_commands(_, _, #state{test=crash_in_data}) ->\n\t[];\ninit_commands(_, _, #state{test=crash_in_info}) ->\n\t[];\ninit_commands(_, _, #state{test=crash_in_terminate}) ->\n\t[{response, 200, #{<<\"content-length\">> => <<\"12\">>}, <<\"Hello world!\">>}, stop];\ninit_commands(_, _, #state{test=crash_in_early_error}) ->\n\terror(crash);\ninit_commands(_, _, #state{test=flow_after_body_fully_read}) ->\n\t[];\ninit_commands(_, _, #state{test=set_options_ignore_unknown}) ->\n\t[\n\t\t{set_options, #{unknown_options => true}},\n\t\t{response, 200, #{<<\"content-length\">> => <<\"12\">>}, <<\"Hello world!\">>},\n\t\tstop\n\t];\ninit_commands(_, _, State=#state{test=shutdown_on_stream_stop}) ->\n\tSpawn = init_process(false, State),\n\t[{spawn, Spawn, 5000}, {headers, 200, #{}}, stop];\ninit_commands(_, _, State=#state{test=shutdown_on_socket_close}) ->\n\tSpawn = init_process(false, State),\n\t[{spawn, Spawn, 5000}, {headers, 200, #{}}];\ninit_commands(_, _, State=#state{test=shutdown_timeout_on_stream_stop}) ->\n\tSpawn = init_process(true, State),\n\t[{spawn, Spawn, 2000}, {headers, 200, #{}}, stop];\ninit_commands(_, _, State=#state{test=shutdown_timeout_on_socket_close}) ->\n\tSpawn = init_process(true, State),\n\t[{spawn, Spawn, 2000}, {headers, 200, #{}}];\ninit_commands(_, _, State=#state{test=switch_protocol_after_headers}) ->\n\t[{headers, 200, #{}}, {switch_protocol, #{}, ?MODULE, State}];\ninit_commands(_, _, State=#state{test=switch_protocol_after_headers_data}) ->\n\t[{headers, 200, #{}}, {data, fin, <<\"{}\">>}, {switch_protocol, #{}, ?MODULE, State}];\ninit_commands(_, _, State=#state{test=switch_protocol_after_response}) ->\n\t[{response, 200, #{}, <<\"{}\">>}, {switch_protocol, #{}, ?MODULE, State}];\ninit_commands(_, _, State=#state{test=terminate_on_switch_protocol}) ->\n\t[{switch_protocol, #{}, ?MODULE, State}];\ninit_commands(_, _, #state{test=terminate_on_stop}) ->\n\t[{response, 204, #{}, <<>>}];\ninit_commands(_, _, _) ->\n\t[{headers, 200, #{}}].\n\ninit_process(TrapExit, #state{pid=Pid}) ->\n\tSelf = self(),\n\tSpawn = spawn_link(fun() ->\n\t\tprocess_flag(trap_exit, TrapExit),\n\t\tPid ! {Pid, Self, spawned, self()},\n\t\treceive {Pid, ready} -> ok after 1000 -> error(timeout) end,\n\t\tSelf ! {self(), ready},\n\t\treceive after 5000 ->\n\t\t\tPid ! {Pid, Self, still_alive, self()}\n\t\tend\n\tend),\n\treceive {Spawn, ready} -> ok after 1000 -> error(timeout) end,\n\tSpawn.\n\ndata(_, _, _, #state{test=crash_in_data}) ->\n\terror(crash);\ndata(_, fin, <<\"Hello world!\">>, State=#state{test=flow_after_body_fully_read}) ->\n\t{[{flow, 12}, {response, 200, #{}, <<\"{}\">>}], State};\ndata(StreamID, IsFin, Data, State=#state{pid=Pid}) ->\n\tPid ! {Pid, self(), data, StreamID, IsFin, Data, State},\n\t{[], State}.\n\ninfo(_, Resp={response, _, _, _}, State) ->\n\t{[Resp], State};\ninfo(_, crash, #state{test=crash_in_info}) ->\n\terror(crash);\ninfo(StreamID, Info, State=#state{pid=Pid}) ->\n\tPid ! {Pid, self(), info, StreamID, Info, State},\n\tcase Info of\n\t\tplease_stop -> {[stop], State};\n\t\t_ -> {[Info], State}\n\tend.\n\nterminate(StreamID, Reason, State=#state{pid=Pid, test=crash_in_terminate}) ->\n\tPid ! {Pid, self(), terminate, StreamID, Reason, State},\n\terror(crash);\nterminate(StreamID, Reason, State=#state{pid=Pid}) ->\n\tPid ! {Pid, self(), terminate, StreamID, Reason, State},\n\tok.\n\n%% This clause can only test for early errors that reached the required headers.\nearly_error(StreamID, Reason, PartialReq, Resp, Opts) ->\n\tPid = list_to_pid(binary_to_list(cowboy_req:header(<<\"x-test-pid\">>, PartialReq))),\n\tPid ! {Pid, self(), early_error, StreamID, Reason, PartialReq, Resp, Opts},\n\tcase cowboy_req:header(<<\"x-test-case\">>, PartialReq) of\n\t\t<<\"crash_in_early_error\",_/bits>> -> error(crash);\n\t\t_ -> Resp\n\tend.\n\n%% @todo It would be good if we could allow this function to return normally.\n-spec takeover(_, _, _, _, _, _, _) -> no_return().\ntakeover(Parent, Ref, Socket, Transport, Opts, Buffer, State=#state{pid=Pid}) ->\n\tPid ! {Pid, self(), takeover, Parent, Ref, Socket, Transport, Opts, Buffer, State},\n\texit(normal).\n"
  },
  {
    "path": "test/handlers/stream_hello_h.erl",
    "content": "%% This module is the fastest way of producing a Hello world!\n\n-module(stream_hello_h).\n\n-export([init/3]).\n-export([terminate/3]).\n\ninit(_, _, State) ->\n\t{[\n\t\t{response, 200, #{<<\"content-length\">> => <<\"12\">>}, <<\"Hello world!\">>},\n\t\tstop\n\t], State}.\n\nterminate(_, _, _) ->\n\tok.\n"
  },
  {
    "path": "test/handlers/streamed_result_h.erl",
    "content": "-module(streamed_result_h).\n\n-export([init/2]).\n\ninit(Req, Opts) ->\n\tN = list_to_integer(binary_to_list(cowboy_req:binding(n, Req))),\n\tInterval = list_to_integer(binary_to_list(cowboy_req:binding(interval, Req))),\n\tchunked(N, Interval, Req, Opts).\n\nchunked(N, Interval, Req0, Opts) ->\n\tReq = cowboy_req:stream_reply(200, Req0),\n\t{ok, loop(N, Interval, Req), Opts}.\n\nloop(0, _Interval, Req) ->\n\tok = cowboy_req:stream_body(\"Finished!\\n\", fin, Req),\n\tReq;\nloop(N, Interval, Req) ->\n\tok = cowboy_req:stream_body(iolist_to_binary([integer_to_list(N), <<\"\\n\">>]), nofin, Req),\n\ttimer:sleep(Interval),\n\tloop(N-1, Interval, Req).\n"
  },
  {
    "path": "test/handlers/switch_handler_h.erl",
    "content": "%% This module returns switch_handler based on the query string.\n\n-module(switch_handler_h).\n\n-export([init/2]).\n\n-export([allowed_methods/2]).\n-export([allow_missing_post/2]).\n-export([charsets_provided/2]).\n-export([content_types_accepted/2]).\n-export([content_types_provided/2]).\n-export([delete_completed/2]).\n-export([delete_resource/2]).\n-export([forbidden/2]).\n-export([is_authorized/2]).\n-export([is_conflict/2]).\n-export([known_methods/2]).\n-export([languages_provided/2]).\n-export([malformed_request/2]).\n-export([moved_permanently/2]).\n-export([moved_temporarily/2]).\n-export([multiple_choices/2]).\n-export([options/2]).\n-export([previously_existed/2]).\n-export([range_satisfiable/2]).\n-export([ranges_provided/2]).\n-export([rate_limited/2]).\n-export([resource_exists/2]).\n-export([service_available/2]).\n-export([uri_too_long/2]).\n-export([valid_content_headers/2]).\n-export([valid_entity_length/2]).\n\n-export([accept/2]).\n-export([provide/2]).\n-export([provide_range/2]).\n\n-export([info/3]).\n\ninit(Req, State) ->\n\t{cowboy_rest, Req, State}.\n\nallowed_methods(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nallow_missing_post(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\ncharsets_provided(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\ncontent_types_accepted(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\ncontent_types_provided(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\ndelete_completed(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\ndelete_resource(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nforbidden(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nis_authorized(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nis_conflict(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nknown_methods(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nlanguages_provided(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nmalformed_request(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nmoved_permanently(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nmoved_temporarily(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nmultiple_choices(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\noptions(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\npreviously_existed(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nrange_satisfiable(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nranges_provided(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nrate_limited(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nresource_exists(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nservice_available(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nuri_too_long(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nvalid_content_headers(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nvalid_entity_length(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\naccept(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nprovide(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nprovide_range(Req, State) ->\n\tmaybe_switch_handler(Req, State, ?FUNCTION_NAME).\n\nmaybe_switch_handler(Req=#{qs := Qs}, State, StateName) ->\n\tcase atom_to_binary(StateName, latin1) of\n\t\tQs -> do_switch_handler(Req, State);\n\t\t_ -> do_default(Req, State, StateName)\n\tend.\n\n%% These are all the methods necessary to reach all callbacks.\ndo_default(Req, State, allowed_methods) ->\n\t{[<<\"GET\">>, <<\"POST\">>, <<\"PUT\">>, <<\"DELETE\">>, <<\"OPTIONS\">>], Req, State};\n%% We need to accept/provide media types to reach these callbacks.\ndo_default(Req, State, content_types_accepted) ->\n\t{[{<<\"text/plain\">>, accept}], Req, State};\ndo_default(Req, State, content_types_provided) ->\n\t{[{<<\"text/plain\">>, provide}], Req, State};\n%% We need to accept ranges to reach these callbacks.\ndo_default(Req=#{qs := <<\"range_satisfiable\">>}, State, ranges_provided) ->\n\t{[{<<\"bytes\">>, provide_range}], Req, State};\ndo_default(Req=#{qs := <<\"provide_range\">>}, State, ranges_provided) ->\n\t{[{<<\"bytes\">>, provide_range}], Req, State};\n%% We need resource_exists to return false to reach these callbacks.\ndo_default(Req=#{qs := <<\"allow_missing_post\">>}, State, resource_exists) ->\n\t{false, Req, State};\ndo_default(Req=#{qs := <<\"moved_permanently\">>}, State, resource_exists) ->\n\t{false, Req, State};\ndo_default(Req=#{qs := <<\"moved_temporarily\">>}, State, resource_exists) ->\n\t{false, Req, State};\ndo_default(Req=#{qs := <<\"previously_existed\">>}, State, resource_exists) ->\n\t{false, Req, State};\n%% We need previously_existed to return true to reach these callbacks.\ndo_default(Req=#{qs := <<\"moved_permanently\">>}, State, previously_existed) ->\n\t{true, Req, State};\ndo_default(Req=#{qs := <<\"moved_temporarily\">>}, State, previously_existed) ->\n\t{true, Req, State};\n%% We need the DELETE to suceed to reach this callback.\ndo_default(Req=#{qs := <<\"delete_completed\">>}, State, delete_resource) ->\n\t{true, Req, State};\n%% We should never reach these callbacks.\ndo_default(Req, State, accept) ->\n\t{false, Req, State};\ndo_default(Req, State, provide) ->\n\t{<<\"This is REST!\">>, Req, State};\ndo_default(Req, State, provide_range) ->\n\t{<<\"This is ranged REST!\">>, Req, State};\n%% Simulate the callback being missing in any other cases.\ndo_default(_, _, _) ->\n\tno_call.\n\ndo_switch_handler(Req0, run) ->\n\tReq = cowboy_req:stream_reply(200, Req0),\n\tsend_after(0),\n\t{{switch_handler, cowboy_loop}, Req, 0};\ndo_switch_handler(Req0, hibernate) ->\n\tReq = cowboy_req:stream_reply(200, Req0),\n\tsend_after(0),\n\t{{switch_handler, cowboy_loop, hibernate}, Req, 0}.\n\nsend_after(N) ->\n\terlang:send_after(100, self(), {stream, msg(N)}).\n\nmsg(0) -> <<\"Hello\\n\">>;\nmsg(1) -> <<\"streamed\\n\">>;\nmsg(2) -> <<\"world!\\n\">>;\nmsg(3) -> stop.\n\ninfo({stream, stop}, Req, State) ->\n\t{stop, Req, State};\ninfo({stream, What}, Req, State) ->\n\tcowboy_req:stream_body(What, nofin, Req),\n\tsend_after(State + 1),\n\t{ok, Req, State + 1}.\n"
  },
  {
    "path": "test/handlers/switch_protocol_flush_h.erl",
    "content": "%% This module is used to test the flushing of messages when\n%% switch_protocol is executed by cowboy_http.\n\n-module(switch_protocol_flush_h).\n\n-export([init/3]).\n-export([info/3]).\n-export([terminate/3]).\n-export([takeover/7]).\n-export([validate/1]).\n\ninit(StreamID, Req, _) ->\n\tPid = list_to_pid(binary_to_list(cowboy_req:header(<<\"x-test-pid\">>, Req))),\n\t%% Send ourselves a few messages that may or may not be flushed.\n\tself() ! good,\n\tself() ! {'EXIT', Pid, normal},\n\tself() ! {system, a, b},\n\tself() ! {{self(), StreamID}, hello},\n\tself() ! {'$gen_call', a, b},\n\tself() ! {timeout, make_ref(), ?MODULE},\n\tself() ! {ranch_tcp, socket, <<\"123\">>},\n\t{[{switch_protocol, #{}, ?MODULE, Pid}], undefined}.\n\ninfo(_, _, State) ->\n\t{[], State}.\n\nterminate(_, _, _) ->\n\tok.\n\n%% @todo It would be good if we could allow this function to return normally.\n-spec takeover(_, _, _, _, _, _, _) -> no_return().\ntakeover(_, _, _, _, _, _, Pid) ->\n\tMsgs = receive_all([]),\n\tPid ! {Pid, Msgs},\n\texit(normal).\n\nreceive_all(Acc) ->\n\treceive\n\t\tMsg ->\n\t\t\treceive_all([Msg|Acc])\n\tafter 0 ->\n\t\tAcc\n\tend.\n\nvalidate(Msgs) ->\n\t[\n\t\t{ranch_tcp, socket, <<\"123\">>},\n\t\t{'$gen_call', a, b},\n\t\t{system, a, b},\n\t\tgood\n\t] = Msgs,\n\tok.\n"
  },
  {
    "path": "test/handlers/ws_active_commands_h.erl",
    "content": "%% This module starts with active mode disabled\n%% and enables it again once a timeout is triggered.\n\n-module(ws_active_commands_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_websocket, Req, maps:get(run_or_hibernate, Opts), Opts}.\n\nwebsocket_init(State=run) ->\n\terlang:send_after(1500, self(), active_true),\n\t{[{active, false}], State};\nwebsocket_init(State=hibernate) ->\n\terlang:send_after(1500, self(), active_true),\n\t{[{active, false}], State, hibernate}.\n\nwebsocket_handle(Frame, State=run) ->\n\t{[Frame], State};\nwebsocket_handle(Frame, State=hibernate) ->\n\t{[Frame], State, hibernate}.\n\nwebsocket_info(active_true, State=run) ->\n\t{[{active, true}], State};\nwebsocket_info(active_true, State=hibernate) ->\n\t{[{active, true}], State, hibernate}.\n"
  },
  {
    "path": "test/handlers/ws_deflate_commands_h.erl",
    "content": "%% This module enables/disables compression\n%% every time it echoes a frame.\n\n-module(ws_deflate_commands_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\tDataDelivery = maps:get(data_delivery, Opts, stream_handlers),\n\t{cowboy_websocket, Req,\n\t\t#{deflate => true, hibernate => maps:get(run_or_hibernate, Opts)},\n\t\t#{compress => true, data_delivery => DataDelivery}}.\n\nwebsocket_handle(Frame, State=#{deflate := Deflate0, hibernate := run}) ->\n\tDeflate = not Deflate0,\n\t{[Frame, {deflate, Deflate}], State#{deflate => Deflate}};\nwebsocket_handle(Frame, State=#{deflate := Deflate0, hibernate := hibernate}) ->\n\tDeflate = not Deflate0,\n\t{[Frame, {deflate, Deflate}], State#{deflate => Deflate}, hibernate}.\n\nwebsocket_info(_Info, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/handlers/ws_deflate_opts_h.erl",
    "content": "%% This module enables compression and returns deflate\n%% options depending on the query string.\n\n-module(ws_deflate_opts_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req=#{qs := Qs}, State) ->\n\t{Name, Value} = case Qs of\n\t\t<<\"server_context_takeover\">> -> {server_context_takeover, takeover};\n\t\t<<\"server_no_context_takeover\">> -> {server_context_takeover, no_takeover};\n\t\t<<\"client_context_takeover\">> -> {client_context_takeover, takeover};\n\t\t<<\"client_no_context_takeover\">> -> {client_context_takeover, no_takeover};\n\t\t<<\"server_max_window_bits\">> -> {server_max_window_bits, 9};\n\t\t<<\"client_max_window_bits\">> -> {client_max_window_bits, 9};\n\t\t<<\"level\">> -> {level, best_speed};\n\t\t<<\"mem_level\">> -> {mem_level, 1};\n\t\t<<\"strategy\">> -> {strategy, rle}\n\tend,\n\t{cowboy_websocket, Req, State, #{\n\t\tcompress => true,\n\t\tdeflate_opts => #{Name => Value}\n\t}}.\n\nwebsocket_handle({text, Data}, State) ->\n\t{[{text, Data}], State};\nwebsocket_handle({binary, Data}, State) ->\n\t{[{binary, Data}], State};\nwebsocket_handle(_, State) ->\n\t{[], State}.\n\nwebsocket_info(_, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/handlers/ws_dont_validate_utf8_h.erl",
    "content": "%% This module disables UTF-8 validation.\n\n-module(ws_dont_validate_utf8_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, State) ->\n\t{cowboy_websocket, Req, State, #{\n\t\tvalidate_utf8 => false\n\t}}.\n\nwebsocket_handle({text, Data}, State) ->\n\t{[{text, Data}], State};\nwebsocket_handle({binary, Data}, State) ->\n\t{[{binary, Data}], State};\nwebsocket_handle(_, State) ->\n\t{[], State}.\n\nwebsocket_info(_, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/handlers/ws_handle_commands_h.erl",
    "content": "%% This module takes commands from the x-commands header\n%% and returns them in the websocket_handle/2 callback.\n\n-module(ws_handle_commands_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\tCommands0 = cowboy_req:header(<<\"x-commands\">>, Req),\n\tCommands = binary_to_term(base64:decode(Commands0)),\n\tcase Commands of\n\t\tbad ->\n\t\t\tPid = case Req of\n\t\t\t\t#{version := 'HTTP/2'} -> self();\n\t\t\t\t#{pid := Pid0} -> Pid0\n\t\t\tend,\n\t\t\tct_helper_error_h:ignore(Pid, cowboy_websocket, handler_call, 6);\n\t\t_ ->\n\t\t\tok\n\tend,\n\t{cowboy_websocket, Req, {Commands, maps:get(run_or_hibernate, Opts)}, Opts}.\n\nwebsocket_init(State) ->\n\t{[], State}.\n\nwebsocket_handle(_, State={Commands, run}) ->\n\t{Commands, State};\nwebsocket_handle(_, State={Commands, hibernate}) ->\n\t{Commands, State, hibernate}.\n\nwebsocket_info(_, State) ->\n\t{[], State}.\n\n"
  },
  {
    "path": "test/handlers/ws_ignore.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n-module(ws_ignore).\n\n-export([init/2]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, _) ->\n\t{cowboy_websocket, Req, undefined, #{\n\t\tdata_delivery => relay,\n\t\tcompress => true\n\t}}.\n\nwebsocket_handle({text, <<\"CHECK\">>}, State) ->\n\t{[{text, <<\"CHECK\">>}], State};\nwebsocket_handle(_Frame, State) ->\n\t{[], State}.\n\nwebsocket_info(_Info, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/handlers/ws_info_commands_h.erl",
    "content": "%% This module takes commands from the x-commands header\n%% and returns them in the websocket_info/2 callback.\n%% This callback is triggered via a message.\n\n-module(ws_info_commands_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\tCommands0 = cowboy_req:header(<<\"x-commands\">>, Req),\n\tCommands = binary_to_term(base64:decode(Commands0)),\n\tcase Commands of\n\t\tbad ->\n\t\t\tPid = case Req of\n\t\t\t\t#{version := 'HTTP/2'} -> self();\n\t\t\t\t#{pid := Pid0} -> Pid0\n\t\t\tend,\n\t\t\tct_helper_error_h:ignore(Pid, cowboy_websocket, handler_call, 6);\n\t\t_ ->\n\t\t\tok\n\tend,\n\t{cowboy_websocket, Req, {Commands, maps:get(run_or_hibernate, Opts)}, Opts}.\n\nwebsocket_init(State) ->\n\tself() ! shoot,\n\t{[], State}.\n\nwebsocket_handle(_, State) ->\n\t{[], State}.\n\nwebsocket_info(_, State={Commands, run}) ->\n\t{Commands, State};\nwebsocket_info(_, State={Commands, hibernate}) ->\n\t{Commands, State, hibernate}.\n"
  },
  {
    "path": "test/handlers/ws_init_commands_h.erl",
    "content": "%% This module takes commands from the x-commands header\n%% and returns them in the websocket_init/1 callback.\n\n-module(ws_init_commands_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\tCommands0 = cowboy_req:header(<<\"x-commands\">>, Req),\n\tCommands = binary_to_term(base64:decode(Commands0)),\n\tcase Commands of\n\t\tbad ->\n\t\t\tPid = case Req of\n\t\t\t\t#{version := 'HTTP/2'} -> self();\n\t\t\t\t#{pid := Pid0} -> Pid0\n\t\t\tend,\n\t\t\tct_helper_error_h:ignore(Pid, cowboy_websocket, handler_call, 6);\n\t\t_ ->\n\t\t\tok\n\tend,\n\t{cowboy_websocket, Req, {Commands, maps:get(run_or_hibernate, Opts)}, Opts}.\n\nwebsocket_init(State={Commands, run}) ->\n\t{Commands, State};\nwebsocket_init(State={Commands, hibernate}) ->\n\t{Commands, State, hibernate}.\n\nwebsocket_handle(_, State) ->\n\t{[], State}.\n\nwebsocket_info(_, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/handlers/ws_init_h.erl",
    "content": "%% This module returns a different value in websocket_init/1 depending on the query string.\n\n-module(ws_init_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\tState = binary_to_atom(cowboy_req:qs(Req), latin1),\n\t{cowboy_websocket, Req, State, Opts}.\n\n%% Sleep to make sure the HTTP response was sent.\nwebsocket_init(State) ->\n\ttimer:sleep(100),\n\tdo_websocket_init(State).\n\ndo_websocket_init(State=ok) ->\n\t{[], State};\ndo_websocket_init(State=ok_hibernate) ->\n\t{[], State, hibernate};\ndo_websocket_init(State=reply) ->\n\t{[{text, \"Hello\"}], State};\ndo_websocket_init(State=reply_hibernate) ->\n\t{[{text, \"Hello\"}], State, hibernate};\ndo_websocket_init(State=reply_close) ->\n\t{[close], State};\ndo_websocket_init(State=reply_close_hibernate) ->\n\t{[close], State, hibernate};\ndo_websocket_init(State=reply_many) ->\n\t{[{text, \"Hello\"}, {binary, \"World\"}], State};\ndo_websocket_init(State=reply_many_hibernate) ->\n\t{[{text, \"Hello\"}, {binary, \"World\"}], State, hibernate};\ndo_websocket_init(State=reply_many_close) ->\n\t{[{text, \"Hello\"}, close], State};\ndo_websocket_init(State=reply_many_close_hibernate) ->\n\t{[{text, \"Hello\"}, close], State, hibernate};\ndo_websocket_init(State=reply_trap_exit) ->\n\tText = \"trap_exit: \" ++ atom_to_list(element(2, process_info(self(), trap_exit))),\n\t{[{text, Text}, close], State, hibernate}.\n\nwebsocket_handle(_, State) ->\n\t{[], State}.\n\nwebsocket_info(_, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/handlers/ws_ping_h.erl",
    "content": "%% This module sends an empty ping to the client and\n%% waits for a pong before sending a text frame. It\n%% is used to confirm server-initiated pings work.\n\n-module(ws_ping_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, _) ->\n\t{cowboy_websocket, Req, undefined}.\n\nwebsocket_init(State) ->\n\t{[{ping, <<>>}], State}.\n\nwebsocket_handle(pong, State) ->\n\t{[{text, <<\"OK!!\">>}], State}.\n\nwebsocket_info(_, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/handlers/ws_set_options_commands_h.erl",
    "content": "%% This module sets options based on the frame received.\n\n-module(ws_set_options_commands_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\tDataDelivery = maps:get(data_delivery, Opts, stream_handlers),\n\t{cowboy_websocket, Req, maps:get(run_or_hibernate, Opts),\n\t\t#{idle_timeout => infinity, data_delivery => DataDelivery}}.\n\n%% Set the idle_timeout option dynamically.\nwebsocket_handle({text, <<\"idle_timeout_short\">>}, State=run) ->\n\t{[{set_options, #{idle_timeout => 500}}], State};\nwebsocket_handle({text, <<\"idle_timeout_short\">>}, State=hibernate) ->\n\t{[{set_options, #{idle_timeout => 500}}], State, hibernate};\n%% Set the max_frame_size option dynamically.\nwebsocket_handle({text, <<\"max_frame_size_small\">>}, State=run) ->\n\t{[{set_options, #{max_frame_size => 1000}}], State};\nwebsocket_handle({text, <<\"max_frame_size_small\">>}, State=hibernate) ->\n\t{[{set_options, #{max_frame_size => 1000}}], State, hibernate};\n%% We just echo binary frames.\nwebsocket_handle(Frame={binary, _}, State=run) ->\n\t{[Frame], State};\nwebsocket_handle(Frame={binary, _}, State=hibernate) ->\n\t{[Frame], State, hibernate}.\n\nwebsocket_info(_Info, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/handlers/ws_shutdown_reason_commands_h.erl",
    "content": "%% This module sends the process pid to the test pid\n%% found in the x-test-pid header, then changes the\n%% shutdown reason and closes the connection normally.\n\n-module(ws_shutdown_reason_commands_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\tTestPid = list_to_pid(binary_to_list(cowboy_req:header(<<\"x-test-pid\">>, Req))),\n\t{cowboy_websocket, Req, {TestPid, maps:get(run_or_hibernate, Opts)}, Opts}.\n\nwebsocket_init(State={TestPid, RunOrHibernate}) ->\n\tTestPid ! {ws_pid, self()},\n\tShutdownReason = receive\n\t\t{TestPid, SR} ->\n\t\t\tSR\n\tafter 1000 ->\n\t\terror(timeout)\n\tend,\n\tCommands = [\n\t\t{shutdown_reason, ShutdownReason},\n\t\tclose\n\t],\n\tcase RunOrHibernate of\n\t\trun -> {Commands, State};\n\t\thibernate -> {Commands, State, hibernate}\n\tend.\n\nwebsocket_handle(_, State) ->\n\t{[], State}.\n\nwebsocket_info(_, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/handlers/ws_terminate_h.erl",
    "content": "%% This module sends a message with terminate arguments to the test case process.\n\n-module(ws_terminate_h).\n-behavior(cowboy_websocket).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n-export([terminate/3]).\n\n-record(state, {\n\tpid\n}).\n\ninit(Req, _) ->\n\tPid = list_to_pid(binary_to_list(cowboy_req:header(<<\"x-test-pid\">>, Req))),\n\tOpts = case cowboy_req:qs(Req) of\n\t\t<<\"req_filter\">> -> #{req_filter => fun(_) -> filtered end};\n\t\t_ -> #{}\n\tend,\n\t{cowboy_websocket, Req, #state{pid=Pid}, Opts}.\n\nwebsocket_init(State=#state{pid=Pid}) ->\n\tPid ! {ws_pid, self()},\n\t%% We must trap 'EXIT' signals for HTTP/2 to call terminate/3.\n\tprocess_flag(trap_exit, true),\n\t{ok, State}.\n\nwebsocket_handle(_, State) ->\n\t{ok, State}.\n\nwebsocket_info(_, State) ->\n\t{ok, State}.\n\nterminate(Reason, Req, #state{pid=Pid}) ->\n\tPid ! {terminate, Reason, Req},\n\tok.\n"
  },
  {
    "path": "test/handlers/wt_echo_h.erl",
    "content": "%% This module echoes client events back,\n%% including creating new streams.\n\n-module(wt_echo_h).\n-behavior(cowboy_webtransport).\n\n-export([init/2]).\n-export([webtransport_handle/2]).\n-export([webtransport_info/2]).\n-export([terminate/3]).\n\n%% -define(DEBUG, 1).\n-ifdef(DEBUG).\n-define(LOG(Fmt, Args), ct:pal(Fmt, Args)).\n-else.\n-define(LOG(Fmt, Args), _ = Fmt, _ = Args, ok).\n-endif.\n\ninit(Req0, _) ->\n\t?LOG(\"WT init ~p~n\", [Req0]),\n\tReq = case cowboy_req:parse_header(<<\"wt-available-protocols\">>, Req0) of\n\t\tundefined ->\n\t\t\tReq0;\n\t\t[Protocol|_] ->\n\t\t\tcowboy_req:set_resp_header(<<\"wt-protocol\">>, cow_http_hd:wt_protocol(Protocol), Req0)\n\tend,\n\t{cowboy_webtransport, Req, #{}}.\n\nwebtransport_handle(Event = {stream_open, StreamID, bidi}, Streams) ->\n\t?LOG(\"WT handle ~p~n\", [Event]),\n\t{[], Streams#{StreamID => bidi}};\nwebtransport_handle(Event = {stream_open, StreamID, unidi}, Streams) ->\n\t?LOG(\"WT handle ~p~n\", [Event]),\n\tOpenStreamRef = make_ref(),\n\t{[{open_stream, OpenStreamRef, unidi, <<>>}], Streams#{\n\t\tStreamID => {unidi_remote, OpenStreamRef},\n\t\tOpenStreamRef => {unidi_local, StreamID}}};\nwebtransport_handle(Event = {opened_stream_id, OpenStreamRef, OpenStreamID}, Streams) ->\n\t?LOG(\"WT handle ~p~n\", [Event]),\n\tcase Streams of\n\t\t#{OpenStreamRef := bidi} ->\n\t\t\t{[], maps:remove(OpenStreamRef, Streams#{\n\t\t\t\tOpenStreamID => bidi\n\t\t\t})};\n\t\t#{OpenStreamRef := {unidi_local, RemoteStreamID}} ->\n\t\t\t#{RemoteStreamID := {unidi_remote, OpenStreamRef}} = Streams,\n\t\t\t{[], maps:remove(OpenStreamRef, Streams#{\n\t\t\t\tRemoteStreamID => {unidi_remote, OpenStreamID},\n\t\t\t\tOpenStreamID => {unidi_local, RemoteStreamID}\n\t\t\t})}\n\tend;\nwebtransport_handle(Event = {stream_data, StreamID, _IsFin, <<\"TEST:\", Test/bits>>}, Streams) ->\n\t?LOG(\"WT handle ~p~n\", [Event]),\n\tcase Test of\n\t\t<<\"open_bidi\">> ->\n\t\t\tOpenStreamRef = make_ref(),\n\t\t\t{[{open_stream, OpenStreamRef, bidi, <<>>}],\n\t\t\t\tStreams#{OpenStreamRef => bidi}};\n\t\t<<\"initiate_close\">> ->\n\t\t\t{[initiate_close], Streams};\n\t\t<<\"close\">> ->\n\t\t\t{[close], Streams};\n\t\t<<\"close_app_code\">> ->\n\t\t\t{[{close, 1234567890}], Streams};\n\t\t<<\"close_app_code_msg\">> ->\n\t\t\t{[{close, 1234567890, <<\"onetwothreefourfivesixseveneightnineten\">>}], Streams};\n\t\t<<\"event_pid:\", EventPidBin/bits>> ->\n\t\t\t{[{send, StreamID, nofin, <<\"event_pid_received\">>}],\n\t\t\t\tStreams#{event_pid => binary_to_term(EventPidBin)}}\n\tend;\nwebtransport_handle(Event = {stream_data, StreamID, IsFin, Data}, Streams) ->\n\t?LOG(\"WT handle ~p~n\", [Event]),\n\tcase Streams of\n\t\t#{StreamID := bidi} ->\n\t\t\t{[{send, StreamID, IsFin, Data}], Streams};\n\t\t#{StreamID := {unidi_remote, Ref}} when is_reference(Ref) ->\n\t\t\t%% The stream isn't ready. We try again later.\n\t\t\terlang:send_after(100, self(), {try_again, Event}),\n\t\t\t{[], Streams};\n\t\t#{StreamID := {unidi_remote, LocalStreamID}} ->\n\t\t\t{[{send, LocalStreamID, IsFin, Data}], Streams}\n\tend;\nwebtransport_handle(Event = {datagram, Data}, Streams) ->\n\t?LOG(\"WT handle ~p~n\", [Event]),\n\t{[{send, datagram, Data}], Streams};\nwebtransport_handle(Event = close_initiated, Streams) ->\n\t?LOG(\"WT handle ~p~n\", [Event]),\n\t{[{send, datagram, <<\"TEST:close_initiated\">>}], Streams};\nwebtransport_handle(Event, Streams) ->\n\t?LOG(\"WT handle ignore ~p~n\", [Event]),\n\t{[], Streams}.\n\nwebtransport_info({try_again, Event}, Streams) ->\n\t?LOG(\"WT try_again ~p\", [Event]),\n\twebtransport_handle(Event, Streams).\n\nterminate(Reason, Req, State=#{event_pid := EventPid}) ->\n\t?LOG(\"WT terminate ~0p~n~0p~n~0p\", [Reason, Req, State]),\n\tEventPid ! {'$wt_echo_h', terminate, Reason, Req, State},\n\tok;\nterminate(Reason, Req, State) ->\n\t?LOG(\"WT terminate ~0p~n~0p~n~0p\", [Reason, Req, State]),\n\tok.\n"
  },
  {
    "path": "test/http2_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(http2_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(ct_helper, [get_remote_pid_tcp/1]).\n-import(cowboy_test, [gun_open/1]).\n\nall() -> [{group, clear}].\n\ngroups() -> [{clear, [parallel], ct_helper:all(?MODULE)}].\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{\"localhost\", [\n\t\t{\"/\", hello_h, []},\n\t\t{\"/echo/:key\", echo_h, []},\n\t\t{\"/resp_iolist_body\", resp_iolist_body_h, []},\n\t\t{\"/streamed_result/:n/:interval\", streamed_result_h, []}\n\t]}]).\n\n%% Do a prior knowledge handshake (function originally copied from rfc7540_SUITE).\ndo_handshake(Config) ->\n\tdo_handshake(#{}, Config).\n\ndo_handshake(Settings, Config) ->\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}|proplists:get_value(tcp_opts, Config, [])]),\n\t%% Send a valid preface.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(Settings)]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Send the SETTINGS ack.\n\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, Socket}.\n\nhibernate(Config) ->\n\tdoc(\"Ensure that we can enable hibernation for HTTP/1.1 connections.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\thibernate => true\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]),\n\t\t{ok, http2} = gun:await_up(ConnPid),\n\t\tStreamRef1 = gun:get(ConnPid, \"/\"),\n\t\tStreamRef2 = gun:get(ConnPid, \"/\"),\n\t\tStreamRef3 = gun:get(ConnPid, \"/\"),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef1),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef2),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef3),\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nidle_timeout(Config) ->\n\tdoc(\"Terminate when the idle timeout is reached.\"),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tidle_timeout => 1000\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = do_handshake([{port, Port}|Config]),\n\t\ttimer:sleep(1000),\n\t\t%% Receive a GOAWAY frame back with NO_ERROR.\n\t\t{ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 1000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nidle_timeout_infinity(Config) ->\n\tdoc(\"Ensure the idle_timeout option accepts the infinity value.\"),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tidle_timeout => infinity\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = do_handshake([{port, Port}|Config]),\n\t\ttimer:sleep(1000),\n\t\t%% Don't receive a GOAWAY frame.\n\t\t{error, timeout} = gen_tcp:recv(Socket, 17, 1000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nidle_timeout_reset_on_data(Config) ->\n\tdoc(\"Terminate when the idle timeout is reached.\"),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tidle_timeout => 1000\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = do_handshake([{port, Port}|Config]),\n\t\t%% We wait a little, send a PING, receive a PING ack.\n\t\t{error, timeout} = gen_tcp:recv(Socket, 17, 500),\n\t\tok = gen_tcp:send(Socket, cow_http2:ping(0)),\n\t\t{ok, <<8:24, 6:8, 0:7, 1:1, 0:96>>} = gen_tcp:recv(Socket, 17, 1000),\n\t\t%% Again.\n\t\t{error, timeout} = gen_tcp:recv(Socket, 17, 500),\n\t\tok = gen_tcp:send(Socket, cow_http2:ping(0)),\n\t\t{ok, <<8:24, 6:8, 0:7, 1:1, 0:96>>} = gen_tcp:recv(Socket, 17, 1000),\n\t\t%% And one more time.\n\t\t{error, timeout} = gen_tcp:recv(Socket, 17, 500),\n\t\tok = gen_tcp:send(Socket, cow_http2:ping(0)),\n\t\t{ok, <<8:24, 6:8, 0:7, 1:1, 0:96>>} = gen_tcp:recv(Socket, 17, 1000),\n\t\t%% The connection goes away soon after we stop sending data.\n\t\ttimer:sleep(1000),\n\t\t{ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 1000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nidle_timeout_on_send(Config) ->\n\tdoc(\"Ensure the idle timeout is not reset when sending (by default).\"),\n\thttp_SUITE:do_idle_timeout_on_send(Config, http2).\n\nidle_timeout_reset_on_send(Config) ->\n\tdoc(\"Ensure the reset_idle_timeout_on_send results in the \"\n\t\t\"idle timeout resetting when sending .\"),\n\thttp_SUITE:do_idle_timeout_reset_on_send(Config, http2).\n\ninactivity_timeout(Config) ->\n\tdoc(\"Terminate when the inactivity timeout is reached.\"),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tinactivity_timeout => 1000\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = do_handshake([{port, Port}|Config]),\n\t\treceive after 1000 -> ok end,\n\t\t%% Receive a GOAWAY frame back with an INTERNAL_ERROR.\n\t\t{ok, << _:24, 7:8, _:72, 2:32 >>} = gen_tcp:recv(Socket, 17, 1000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\ninitial_connection_window_size(Config) ->\n\tdoc(\"Confirm a WINDOW_UPDATE frame is sent when the configured \"\n\t\t\"connection window is larger than the default.\"),\n\tConfiguredSize = 100000,\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tinitial_connection_window_size => ConfiguredSize\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = gen_tcp:connect(\"localhost\", Port, [binary, {active, false}]),\n\t\t%% Send a valid preface.\n\t\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t\t%% Receive the server preface.\n\t\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t\t%% Receive a WINDOW_UPDATE frame incrementing the connection window to 100000.\n\t\t{ok, <<4:24, 8:8, 0:41, Size:31>>} = gen_tcp:recv(Socket, 13, 1000),\n\t\tConfiguredSize = Size + 65535,\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nmax_frame_size_sent(Config) ->\n\tdoc(\"Confirm that frames sent by Cowboy are limited in size \"\n\t\t\"by the max_frame_size_sent configuration value.\"),\n\tMaxFrameSize = 20000,\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tmax_frame_size_sent => MaxFrameSize\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = do_handshake(#{max_frame_size => MaxFrameSize + 10000},\n\t\t\t[{port, Port}|Config]),\n\t\t%% Send a request with a 30000 bytes body.\n\t\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t\t]),\n\t\tok = gen_tcp:send(Socket, [\n\t\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t\tcow_http2:data(1, nofin, <<0:16384/unit:8>>),\n\t\t\tcow_http2:data(1, fin, <<0:13616/unit:8>>)\n\t\t]),\n\t\t%% Receive a HEADERS frame as a response.\n\t\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = case gen_tcp:recv(Socket, 9, 1000) of\n\t\t\t%% We received a WINDOW_UPDATE first. Skip it and the next.\n\t\t\t{ok, <<4:24, 8:8, 0:40>>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, 4 + 13, 1000),\n\t\t\t\tgen_tcp:recv(Socket, 9, 1000);\n\t\t\tRes ->\n\t\t\t\tRes\n\t\tend,\n\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 6000),\n\t\t%% The DATA frames following must have lengths of 20000\n\t\t%% and then 10000 due to the limit.\n\t\t{ok, <<20000:24, 0:8, _:40, _:20000/unit:8>>} = gen_tcp:recv(Socket, 20009, 6000),\n\t\t{ok, <<10000:24, 0:8, _:40, _:10000/unit:8>>} = gen_tcp:recv(Socket, 10009, 6000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\npersistent_term_router(Config) ->\n\tdoc(\"The router can retrieve the routes from persistent_term storage.\"),\n\tcase erlang:function_exported(persistent_term, get, 1) of\n\t\ttrue -> do_persistent_term_router(Config);\n\t\tfalse -> {skip, \"This test uses the persistent_term functionality added in Erlang/OTP 21.2.\"}\n\tend.\n\ndo_persistent_term_router(Config) ->\n\tpersistent_term:put(?FUNCTION_NAME, init_dispatch(Config)),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => {persistent_term, ?FUNCTION_NAME}}\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]),\n\t\t{ok, http2} = gun:await_up(ConnPid),\n\t\tStreamRef = gun:get(ConnPid, \"/\"),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef),\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\npreface_timeout_infinity(Config) ->\n\tdoc(\"Ensure infinity for preface_timeout is accepted.\"),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tpreface_timeout => infinity\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = do_handshake([{port, Port}|Config]),\n\t\tPid = get_remote_pid_tcp(Socket),\n\t\tRef = erlang:monitor(process, Pid),\n\t\treceive\n\t\t\t{'DOWN', Ref, process, Pid, Reason} ->\n\t\t\t\terror(Reason)\n\t\tafter 1000 ->\n\t\t\tgen_tcp:close(Socket)\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nresp_iolist_body(Config) ->\n\tdoc(\"Regression test when response bodies are iolists that \"\n\t\t\"include improper lists, empty lists and empty binaries. \"\n\t\t\"The original issue failed to split the body into frames properly.\"),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)}\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]),\n\t\tRef = gun:get(ConnPid, \"/resp_iolist_body\"),\n\t\t{response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref),\n\t\t{_, BinLen} = lists:keyfind(<<\"content-length\">>, 1, RespHeaders),\n\t\tLen = binary_to_integer(BinLen),\n\t\t{ok, RespBody} = gun:await_body(ConnPid, Ref),\n\t\tLen = iolist_size(RespBody),\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nsettings_timeout_infinity(Config) ->\n\tdoc(\"Ensure infinity for settings_timeout is accepted.\"),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tsettings_timeout => infinity\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = do_handshake([{port, Port}|Config]),\n\t\tPid = get_remote_pid_tcp(Socket),\n\t\tRef = erlang:monitor(process, Pid),\n\t\treceive\n\t\t\t{'DOWN', Ref, process, Pid, Reason} ->\n\t\t\t\terror(Reason)\n\t\tafter 1000 ->\n\t\t\tgen_tcp:close(Socket)\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\ngraceful_shutdown_connection(Config) ->\n\tdoc(\"Check that ongoing requests are handled before gracefully shutting down a connection.\"),\n\tDispatch = cowboy_router:compile([{\"localhost\", [\n\t\t{\"/delay_hello\", delay_hello_h,\n\t\t\t#{delay => 500, notify_received => self()}}\n\t]}]),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => Dispatch}\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]),\n\t\tRef = gun:get(ConnPid, \"/delay_hello\"),\n\t\t%% Make sure the request is received.\n\t\treceive {request_received, <<\"/delay_hello\">>} -> ok end,\n\t\t%% Tell the connection to shutdown while the handler is working.\n\t\t[CowboyConnPid] = ranch:procs(?FUNCTION_NAME, connections),\n\t\tmonitor(process, CowboyConnPid),\n\t\tok = sys:terminate(CowboyConnPid, goaway),\n\t\t%% Check that the response is sent to the client before the\n\t\t%% connection goes down.\n\t\t{response, nofin, 200, _RespHeaders} = gun:await(ConnPid, Ref),\n\t\t{ok, RespBody} = gun:await_body(ConnPid, Ref),\n\t\t<<\"Hello world!\">> = iolist_to_binary(RespBody),\n\t\t%% Check that the connection is gone soon afterwards. (The exit\n\t\t%% reason is supposed to be 'goaway' as passed to\n\t\t%% sys:terminate/2, but it is {shutdown, closed}.)\n\t\treceive\n\t\t\t{'DOWN', _, process, CowboyConnPid, _Reason} ->\n\t\t\t\tok\n\t\tend,\n\t\t[] = ranch:procs(?FUNCTION_NAME, connections),\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\ngraceful_shutdown_timeout(Config) ->\n\tdoc(\"Check that a connection is closed when gracefully shutting down times out.\"),\n\tDispatch = cowboy_router:compile([{\"localhost\", [\n\t\t{\"/long_delay_hello\", delay_hello_h,\n\t\t\t#{delay => 10000, notify_received => self()}}\n\t]}]),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => Dispatch},\n\t\tgoaway_initial_timeout => 200,\n\t\tgoaway_complete_timeout => 500\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]),\n\t\tRef = gun:get(ConnPid, \"/long_delay_hello\"),\n\t\t%% Make sure the request is received.\n\t\treceive {request_received, <<\"/long_delay_hello\">>} -> ok end,\n\t\t%% Tell the connection to shutdown while the handler is working.\n\t\t[CowboyConnPid] = ranch:procs(?FUNCTION_NAME, connections),\n\t\tmonitor(process, CowboyConnPid),\n\t\tok = sys:terminate(CowboyConnPid, goaway),\n\t\t%% Check that connection didn't wait for the slow handler.\n\t\t{error, {stream_error, closed}} = gun:await(ConnPid, Ref),\n\t\t%% Check that the connection is gone. (The exit reason is\n\t\t%% supposed to be 'goaway' as passed to sys:terminate/2, but it\n\t\t%% is {shutdown, {stop, {exit, goaway}, 'Graceful shutdown timed\n\t\t%% out.'}}.)\n\t\treceive\n\t\t\t{'DOWN', _, process, CowboyConnPid, _Reason} ->\n\t\t\t\tok\n\t\tafter 100 ->\n\t\t       error(still_alive)\n\t\tend,\n\t\t[] = ranch:procs(?FUNCTION_NAME, connections),\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\ngraceful_shutdown_listener(Config) ->\n\tdoc(\"Check that connections are shut down gracefully when stopping a listener.\"),\n\tTransOpts = #{\n\t\tsocket_opts => [{port, 0}],\n\t\tshutdown => 1000 %% Shorter timeout to make the test case faster.\n\t},\n\tDispatch = cowboy_router:compile([{\"localhost\", [\n\t\t{\"/delay_hello\", delay_hello_h,\n\t\t\t#{delay => 500, notify_received => self()}}\n\t]}]),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => Dispatch}\n\t},\n\t{ok, Listener} = cowboy:start_clear(?FUNCTION_NAME, TransOpts, ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\tConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]),\n\tRef = gun:get(ConnPid, \"/delay_hello\"),\n\t%% Shutdown listener while the handlers are working.\n\treceive {request_received, <<\"/delay_hello\">>} -> ok end,\n\tListenerMonitorRef = monitor(process, Listener),\n\t%% Note: This call does not complete quickly and will\n\t%% prevent other cowboy:stop_listener/1 calls to complete.\n\tok = cowboy:stop_listener(?FUNCTION_NAME),\n\treceive\n\t\t{'DOWN', ListenerMonitorRef, process, Listener, _Reason} ->\n\t\t\tok\n\tend,\n\t%% Check that the request is handled before shutting down.\n\t{response, nofin, 200, _RespHeaders} = gun:await(ConnPid, Ref),\n\t{ok, RespBody} = gun:await_body(ConnPid, Ref),\n\t<<\"Hello world!\">> = iolist_to_binary(RespBody),\n\tgun:close(ConnPid).\n\ngraceful_shutdown_listener_timeout(Config) ->\n\tdoc(\"Check that connections are shut down when gracefully stopping a listener times out.\"),\n\tTransOpts = #{\n\t\tsocket_opts => [{port, 0}],\n\t\tshutdown => 1000 %% Shorter timeout to make the test case faster.\n\t},\n\tDispatch = cowboy_router:compile([{\"localhost\", [\n\t\t{\"/long_delay_hello\", delay_hello_h,\n\t\t\t#{delay => 10000, notify_received => self()}}\n\t]}]),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => Dispatch},\n\t\tgoaway_initial_timeout => 200,\n\t\tgoaway_complete_timeout => 500\n\t},\n\t{ok, Listener} = cowboy:start_clear(?FUNCTION_NAME, TransOpts, ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\tConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]),\n\tRef = gun:get(ConnPid, \"/long_delay_hello\"),\n\t%% Shutdown listener while the handlers are working.\n\treceive {request_received, <<\"/long_delay_hello\">>} -> ok end,\n\tListenerMonitorRef = monitor(process, Listener),\n\t%% Note: This call does not complete quickly and will\n\t%% prevent other cowboy:stop_listener/1 calls to complete.\n\tok = cowboy:stop_listener(?FUNCTION_NAME),\n\treceive\n\t\t{'DOWN', ListenerMonitorRef, process, Listener, _Reason} ->\n\t\t\tok\n\tend,\n\t%% Check that the slow request is aborted.\n\t{error, {stream_error, closed}} = gun:await(ConnPid, Ref),\n\tgun:close(ConnPid).\n\nsend_timeout_close(Config) ->\n\tdoc(\"Check that connections are closed on send timeout.\"),\n\tTransOpts = #{\n\t\tsocket_opts => [\n\t\t\t{port, 0},\n\t\t\t{send_timeout, 100},\n\t\t\t{send_timeout_close, true},\n\t\t\t{sndbuf, 10}\n\t\t]\n\t},\n\tDispatch = cowboy_router:compile([{\"localhost\", [\n\t\t{\"/endless\", loop_handler_endless_h, #{delay => 100}}\n\t]}]),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => Dispatch},\n\t\tidle_timeout => infinity\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, TransOpts, ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t%% Connect a client that sends a request and waits indefinitely.\n\t\t{ok, ClientSocket} = do_handshake([{port, Port},\n\t\t\t{tcp_opts, [{recbuf, 10}, {buffer, 10}, {active, false}]}|Config]),\n\t\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/endless\">>},\n\t\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t\t]),\n\t\tok = gen_tcp:send(ClientSocket, [\n\t\t\tcow_http2:headers(1, fin, HeadersBlock),\n\t\t\t%% Greatly increase the window to make sure we don't run\n\t\t\t%% out of space before we get send timeouts.\n\t\t\tcow_http2:window_update(10000000),\n\t\t\tcow_http2:window_update(1, 10000000)\n\t\t]),\n\t\t%% Wait for the handler to start then get its pid,\n\t\t%% the remote connection's pid and socket.\n\t\tStreamPid = receive\n\t\t\t{Self, StreamPid0, init} when Self =:= self() ->\n\t\t\t\tStreamPid0\n\t\tafter 1000 ->\n\t\t\terror(timeout)\n\t\tend,\n\t\tServerPid = ct_helper:get_remote_pid_tcp(ClientSocket),\n\t\t{links, ServerLinks} = process_info(ServerPid, links),\n\t\t[ServerSocket] = [PidOrPort || PidOrPort <- ServerLinks, is_port(PidOrPort)],\n\t\t%% Poll the socket repeatedly until it is closed by the server.\n\t\tWaitClosedFun =\n\t\t\tfun F(T) when T =< 0 ->\n\t\t\t\t\terror({status, prim_inet:getstatus(ServerSocket)});\n\t\t\t\tF(T) ->\n\t\t\t\t\tSnooze = 100,\n\t\t\t\t\tcase inet:sockname(ServerSocket) of\n\t\t\t\t\t\t{error, _} ->\n\t\t\t\t\t\t\ttimer:sleep(Snooze);\n\t\t\t\t\t\t{ok, _} ->\n\t\t\t\t\t\t\ttimer:sleep(Snooze),\n\t\t\t\t\t\t\tF(T - Snooze)\n\t\t\t\t\tend\n\t\t\tend,\n\t\tok = WaitClosedFun(2000),\n\t\tfalse = erlang:is_process_alive(StreamPid),\n\t\tfalse = erlang:is_process_alive(ServerPid),\n\t\tgen_tcp:close(ClientSocket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n"
  },
  {
    "path": "test/http_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(http_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(ct_helper, [get_remote_pid_tcp/1]).\n-import(cowboy_test, [gun_open/1]).\n-import(cowboy_test, [gun_down/1]).\n-import(cowboy_test, [raw_open/1]).\n-import(cowboy_test, [raw_send/2]).\n-import(cowboy_test, [raw_recv_head/1]).\n-import(cowboy_test, [raw_recv_rest/3]).\n-import(cowboy_test, [raw_recv/3]).\n-import(cowboy_test, [raw_expect_recv/2]).\n\nall() ->\n\t[{group, clear_no_parallel}, {group, clear}].\n\ngroups() ->\n\t[\n\t\t%% cowboy:stop_listener can be slow when called many times\n\t\t%% in parallel so we must run this test separately from the others.\n\t\t{clear_no_parallel, [], [graceful_shutdown_listener]},\n\t\t{clear, [parallel], ct_helper:all(?MODULE) -- [graceful_shutdown_listener]}\n\t].\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_http(Name, #{\n\t\tenv => #{dispatch => init_dispatch(Config)}\n\t}, Config).\n\nend_per_group(Name, _) ->\n\tcowboy:stop_listener(Name).\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{\"localhost\", [\n\t\t{\"/\", hello_h, []},\n\t\t{\"/delay_hello\", delay_hello_h, #{delay => 1000, notify_received => self()}},\n\t\t{\"/echo/:key\", echo_h, []},\n\t\t{\"/resp/:key[/:arg]\", resp_h, []},\n\t\t{\"/set_options/:key\", set_options_h, []},\n\t\t{\"/streamed_result/:n/:interval\", streamed_result_h, []}\n\t]}]).\n\nchunked_false(Config) ->\n\tdoc(\"Confirm the option chunked => false disables chunked \"\n\t\t\"transfer-encoding for HTTP/1.1 connections.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tchunked => false\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tRequest = \"GET /resp/stream_reply2/200 HTTP/1.1\\r\\nhost: localhost\\r\\n\\r\\n\",\n\t\tClient = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]),\n\t\tok = raw_send(Client, Request),\n\t\tRest = case catch raw_recv_head(Client) of\n\t\t\t{'EXIT', _} -> error(closed);\n\t\t\tData ->\n\t\t\t\t%% Cowboy always advertises itself as HTTP/1.1.\n\t\t\t\t{'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data),\n\t\t\t\t{Headers, Rest1} = cow_http:parse_headers(Rest0),\n\t\t\t\tfalse = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\t\t\t\tfalse = lists:keyfind(<<\"transfer-encoding\">>, 1, Headers),\n\t\t\t\tRest1\n\t\tend,\n\t\tBits = 8000000 - bit_size(Rest),\n\t\traw_expect_recv(Client, <<0:Bits>>),\n\t\t{error, closed} = raw_recv(Client, 1, 1000)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nchunked_one_byte_at_a_time(Config) ->\n\tdoc(\"Confirm that chunked transfer-encoding works when \"\n\t\t\"the body is received one byte at a time.\"),\n\tBody = list_to_binary(io_lib:format(\"~p\", [lists:seq(1, 100)])),\n\tChunkedBody = iolist_to_binary(do_chunked_body(50, Body, [])),\n\tClient = raw_open(Config),\n\tok = raw_send(Client,\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\\r\\n\"),\n\t_ = [begin\n\t\traw_send(Client, <<C>>),\n\t\ttimer:sleep(1)\n\tend || <<C>> <= ChunkedBody],\n\tRest = case catch raw_recv_head(Client) of\n\t\t{'EXIT', _} -> error(closed);\n\t\tData ->\n\t\t\t{'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data),\n\t\t\t{_, Rest1} = cow_http:parse_headers(Rest0),\n\t\t\tRest1\n\tend,\n\tRestSize = byte_size(Rest),\n\t<<Rest:RestSize/binary, Expect/bits>> = Body,\n\traw_expect_recv(Client, Expect).\n\nchunked_one_chunk_at_a_time(Config) ->\n\tdoc(\"Confirm that chunked transfer-encoding works when \"\n\t\t\"the body is received one chunk at a time.\"),\n\tBody = list_to_binary(io_lib:format(\"~p\", [lists:seq(1, 100)])),\n\tChunks = do_chunked_body(50, Body, []),\n\tClient = raw_open(Config),\n\tok = raw_send(Client,\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\\r\\n\"),\n\t_ = [begin\n\t\traw_send(Client, Chunk),\n\t\ttimer:sleep(10)\n\tend || Chunk <- Chunks],\n\tRest = case catch raw_recv_head(Client) of\n\t\t{'EXIT', _} -> error(closed);\n\t\tData ->\n\t\t\t{'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data),\n\t\t\t{_, Rest1} = cow_http:parse_headers(Rest0),\n\t\t\tRest1\n\tend,\n\tRestSize = byte_size(Rest),\n\t<<Rest:RestSize/binary, Expect/bits>> = Body,\n\traw_expect_recv(Client, Expect).\n\nchunked_split_delay_in_chunk_body(Config) ->\n\tdoc(\"Confirm that chunked transfer-encoding works when \"\n\t\t\"the body is received with a delay inside the chunks.\"),\n\tBody = list_to_binary(io_lib:format(\"~p\", [lists:seq(1, 100)])),\n\tChunks = do_chunked_body(50, Body, []),\n\tClient = raw_open(Config),\n\tok = raw_send(Client,\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\\r\\n\"),\n\t_ = [begin\n\t\tcase Chunk of\n\t\t\t<<\"0\\r\\n\\r\\n\">> ->\n\t\t\t\traw_send(Client, Chunk);\n\t\t\t_ ->\n\t\t\t\t[Size, ChunkBody, <<>>] = binary:split(Chunk, <<\"\\r\\n\">>, [global]),\n\t\t\t\tPartASize = rand:uniform(byte_size(ChunkBody)),\n\t\t\t\t<<PartA:PartASize/binary, PartB/binary>> = ChunkBody,\n\t\t\t\traw_send(Client, [Size, <<\"\\r\\n\">>, PartA]),\n\t\t\t\ttimer:sleep(10),\n\t\t\t\traw_send(Client, [PartB, <<\"\\r\\n\">>])\n\t\tend\n\tend || Chunk <- Chunks],\n\tRest = case catch raw_recv_head(Client) of\n\t\t{'EXIT', _} -> error(closed);\n\t\tData ->\n\t\t\t{'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data),\n\t\t\t{_, Rest1} = cow_http:parse_headers(Rest0),\n\t\t\tRest1\n\tend,\n\tRestSize = byte_size(Rest),\n\t<<Rest:RestSize/binary, Expect/bits>> = Body,\n\traw_expect_recv(Client, Expect).\n\nchunked_split_delay_in_chunk_crlf(Config) ->\n\tdoc(\"Confirm that chunked transfer-encoding works when \"\n\t\t\"the body is received with a delay inside the chunks end CRLF.\"),\n\tBody = list_to_binary(io_lib:format(\"~p\", [lists:seq(1, 100)])),\n\tChunks = do_chunked_body(50, Body, []),\n\tClient = raw_open(Config),\n\tok = raw_send(Client,\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\\r\\n\"),\n\t_ = [begin\n\t\tLen = byte_size(Chunk) - (rand:uniform(2) - 1),\n\t\t<<Begin:Len/binary, End/binary>> = Chunk,\n\t\traw_send(Client, Begin),\n\t\ttimer:sleep(10),\n\t\traw_send(Client, End)\n\tend || Chunk <- Chunks],\n\tRest = case catch raw_recv_head(Client) of\n\t\t{'EXIT', _} -> error(closed);\n\t\tData ->\n\t\t\t{'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data),\n\t\t\t{_, Rest1} = cow_http:parse_headers(Rest0),\n\t\t\tRest1\n\tend,\n\tRestSize = byte_size(Rest),\n\t<<Rest:RestSize/binary, Expect/bits>> = Body,\n\traw_expect_recv(Client, Expect).\n\ndo_chunked_body(_, <<>>, Acc) ->\n\tlists:reverse([cow_http_te:last_chunk()|Acc]);\ndo_chunked_body(ChunkSize0, Data, Acc) ->\n\tChunkSize = min(byte_size(Data), ChunkSize0),\n\t<<Chunk:ChunkSize/binary, Rest/binary>> = Data,\n\tdo_chunked_body(ChunkSize, Rest,\n\t\t[iolist_to_binary(cow_http_te:chunk(Chunk))|Acc]).\n\ndisable_http1_tls(Config) ->\n\tdoc(\"Ensure that we can disable HTTP/1.1 over TLS (force HTTP/2).\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, _} = cowboy:start_tls(?FUNCTION_NAME, TlsOpts ++ [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\talpn_default_protocol => http2\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = ssl:connect(\"localhost\", Port,\n\t\t\t[binary, {active, false}|TlsOpts]),\n\t\t%% ALPN was not negotiated but we're still over HTTP/2.\n\t\t{error, protocol_not_negotiated} = ssl:negotiated_protocol(Socket),\n\t\t%% Send a valid preface.\n\t\tok = ssl:send(Socket, [\n\t\t\t\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\",\n\t\t\tcow_http2:settings(#{})]),\n\t\t%% Receive the server preface.\n\t\t{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),\n\t\t{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),\n\t\tok\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\ndisable_http2_prior_knowledge(Config) ->\n\tdoc(\"Ensure that we can disable prior knowledge HTTP/2 upgrade.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tprotocols => [http]\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = gen_tcp:connect(\"localhost\", Port, [binary, {active, false}]),\n\t\t%% Send a valid preface.\n\t\tok = gen_tcp:send(Socket, [\n\t\t\t\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\",\n\t\t\tcow_http2:settings(#{})]),\n\t\t{ok, <<\"HTTP/1.1 501\">>} = gen_tcp:recv(Socket, 12, 1000),\n\t\tok\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\ndisable_http2_upgrade(Config) ->\n\tdoc(\"Ensure that we can disable HTTP/1.1 upgrade to HTTP/2.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tprotocols => [http]\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t{ok, Socket} = gen_tcp:connect(\"localhost\", Port, [binary, {active, false}]),\n\t\t%% Send a valid preface.\n\t\tok = gen_tcp:send(Socket, [\n\t\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\t\"Host: localhost\\r\\n\"\n\t\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\t\"\\r\\n\"]),\n\t\t{ok, <<\"HTTP/1.1 200\">>} = gen_tcp:recv(Socket, 12, 1000),\n\t\tok\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nhibernate(Config) ->\n\tdoc(\"Ensure that we can enable hibernation for HTTP/1.1 connections.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\thibernate => true\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\tStreamRef1 = gun:get(ConnPid, \"/\"),\n\t\tStreamRef2 = gun:get(ConnPid, \"/\"),\n\t\tStreamRef3 = gun:get(ConnPid, \"/\"),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef1),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef2),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef3),\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nhttp10_keepalive_false(Config) ->\n\tdoc(\"Confirm the option http10_keepalive => false disables keep-alive \"\n\t\t\"completely for HTTP/1.0 connections.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\thttp10_keepalive => false\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tKeepalive = \"GET / HTTP/1.0\\r\\nhost: localhost\\r\\nConnection: keep-alive\\r\\n\\r\\n\",\n\t\tClient = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]),\n\t\tok = raw_send(Client, Keepalive),\n\t\t_ = case catch raw_recv_head(Client) of\n\t\t\t{'EXIT', _} -> error(closed);\n\t\t\tData ->\n\t\t\t\t%% Cowboy always advertises itself as HTTP/1.1.\n\t\t\t\t{'HTTP/1.1', 200, _, Rest} = cow_http:parse_status_line(Data),\n\t\t\t\t{Headers, _} = cow_http:parse_headers(Rest),\n\t\t\t\t{_, <<\"close\">>} = lists:keyfind(<<\"connection\">>, 1, Headers)\n\t\tend,\n\t\tok = raw_send(Client, Keepalive),\n\t\tcase catch raw_recv_head(Client) of\n\t\t\t{'EXIT', _} -> closed;\n\t\t\t_ -> error(not_closed)\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nidle_timeout_read_body(Config) ->\n\tdoc(\"Ensure the idle_timeout drops connections when the \"\n\t\t\"connection is idle too long reading the request body.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\trequest_timeout => 60000,\n\t\tidle_timeout => 500\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\t_StreamRef = gun:post(ConnPid, \"/echo/read_body\",\n\t\t\t#{<<\"content-length\">> => <<\"12\">>}),\n\t\t{error, {down, {shutdown, closed}}} = gun:await(ConnPid, undefined, 1000)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nidle_timeout_read_body_pipeline(Config) ->\n\tdoc(\"Ensure the idle_timeout drops connections when the \"\n\t\t\"connection is idle too long reading the request body.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\trequest_timeout => 60000,\n\t\tidle_timeout => 500\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\tStreamRef1 = gun:get(ConnPid, \"/\"),\n\t\tStreamRef2 = gun:get(ConnPid, \"/\"),\n\t\t_StreamRef3 = gun:post(ConnPid, \"/echo/read_body\",\n\t\t\t#{<<\"content-length\">> => <<\"12\">>}),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef1),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef2),\n\t\t{error, {down, {shutdown, closed}}} = gun:await(ConnPid, undefined, 1000)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nidle_timeout_skip_body(Config) ->\n\tdoc(\"Ensure the idle_timeout drops connections when the \"\n\t\t\"connection is idle too long skipping the request body.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\trequest_timeout => 60000,\n\t\tidle_timeout => 500\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\tStreamRef = gun:post(ConnPid, \"/\",\n\t\t\t#{<<\"content-length\">> => <<\"12\">>}),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef),\n\t\t{error, {down, {shutdown, closed}}} = gun:await(ConnPid, undefined, 1000)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nidle_timeout_infinity(Config) ->\n\tdoc(\"Ensure the idle_timeout option accepts the infinity value.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tidle_timeout => infinity\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\ttimer:sleep(500),\n\t\t#{socket := Socket} = gun:info(ConnPid),\n\t\tPid = get_remote_pid_tcp(Socket),\n\t\t_ = gun:post(ConnPid, \"/echo/read_body\",\n\t\t\t[{<<\"content-type\">>, <<\"text/plain\">>}]),\n\t\tRef = erlang:monitor(process, Pid),\n\t\treceive\n\t\t\t{'DOWN', Ref, process, Pid, Reason} ->\n\t\t\t\terror(Reason)\n\t\tafter 1000 ->\n\t\t\tgun:close(ConnPid)\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nidle_timeout_on_send(Config) ->\n\tdoc(\"Ensure the idle timeout is not reset when sending (by default).\"),\n\tdo_idle_timeout_on_send(Config, http).\n\n%% Also used by http2_SUITE.\ndo_idle_timeout_on_send(Config, Protocol) ->\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tidle_timeout => 1000\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, Protocol}, {port, Port}|Config]),\n\t\t{ok, Protocol} = gun:await_up(ConnPid),\n\t\ttimer:sleep(500),\n\t\t#{socket := Socket} = gun:info(ConnPid),\n\t\tPid = get_remote_pid_tcp(Socket),\n\t\tStreamRef = gun:get(ConnPid, \"/streamed_result/10/250\"),\n\t\tRef = erlang:monitor(process, Pid),\n\t\treceive\n\t\t\t{gun_response, ConnPid, StreamRef, nofin, _Status, _Headers} ->\n\t\t\t\tdo_idle_timeout_recv_loop(Ref, Pid, ConnPid, StreamRef, false)\n\t\tafter 2000 ->\n\t\t      error(timeout)\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nidle_timeout_reset_on_send(Config) ->\n\tdoc(\"Ensure the reset_idle_timeout_on_send results in the \"\n\t\t\"idle timeout resetting when sending .\"),\n\tdo_idle_timeout_reset_on_send(Config, http).\n\n%% Also used by http2_SUITE.\ndo_idle_timeout_reset_on_send(Config, Protocol) ->\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tidle_timeout => 1000,\n\t\treset_idle_timeout_on_send => true\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, Protocol}, {port, Port}|Config]),\n\t\t{ok, Protocol} = gun:await_up(ConnPid),\n\t\ttimer:sleep(500),\n\t\t#{socket := Socket} = gun:info(ConnPid),\n\t\tPid = get_remote_pid_tcp(Socket),\n\t\tStreamRef = gun:get(ConnPid, \"/streamed_result/10/250\"),\n\t\tRef = erlang:monitor(process, Pid),\n\t\treceive\n\t\t\t{gun_response, ConnPid, StreamRef, nofin, _Status, _Headers} ->\n\t\t\t\tdo_idle_timeout_recv_loop(Ref, Pid, ConnPid, StreamRef, true)\n\t\tafter 2000 ->\n\t\t      error(timeout)\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\ndo_idle_timeout_recv_loop(Ref, Pid, ConnPid, StreamRef, ExpectCompletion) ->\n\treceive\n\t\t{gun_data, ConnPid, StreamRef, nofin, _Data} ->\n\t\t\tdo_idle_timeout_recv_loop(Ref, Pid, ConnPid, StreamRef, ExpectCompletion);\n\t\t{gun_data, ConnPid, StreamRef, fin, _Data} when ExpectCompletion ->\n\t\t\tgun:close(ConnPid);\n\t\t{gun_data, ConnPid, StreamRef, fin, _Data} ->\n\t\t\tgun:close(ConnPid),\n\t\t\terror(completed);\n\t\t{'DOWN', Ref, process, Pid, _} when ExpectCompletion ->\n\t\t\tgun:close(ConnPid),\n\t\t\terror(exited);\n\t\t{'DOWN', Ref, process, Pid, _} ->\n\t\t\t ok\n\tafter 2000 ->\n\t      error(timeout)\n\tend.\n\nmax_authorization_header_value_length(Config) ->\n\tdoc(\"Confirm the max_authorization_header_value_length option \"\n\t\t\"correctly limits the length of authorization header values.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tmax_authorization_header_value_length => 2048\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tdo_max_header_value_length(Config, Port,\n\t\t\t<<\"authorization\">>, 2048),\n\t\t%% Confirm that other headers still use the default limit.\n\t\tdo_max_header_value_length(Config, Port,\n\t\t\t<<\"my-header\">>, 4096)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nmax_cookie_header_value_length(Config) ->\n\tdoc(\"Confirm the max_cookie_header_value_length option \"\n\t\t\"correctly limits the length of cookie header values.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tmax_cookie_header_value_length => 2048\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tdo_max_header_value_length(Config, Port,\n\t\t\t<<\"cookie\">>, 2048),\n\t\t%% Confirm that other headers still use the default limit.\n\t\tdo_max_header_value_length(Config, Port,\n\t\t\t<<\"my-header\">>, 4096)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nmax_header_value_length(Config) ->\n\tdoc(\"Confirm the max_header_value_length option \"\n\t\t\"correctly limits the length of header values.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tmax_header_value_length => 2048\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tdo_max_header_value_length(Config, Port,\n\t\t\t<<\"my-header\">>, 2048)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nmax_header_value_length_default(Config) ->\n\tdoc(\"Confirm the max_header_value_length option \"\n\t\t\"correctly limits the length of header values.\"),\n\tdo_max_header_value_length(Config, config(port, Config),\n\t\t<<\"my-header\">>, 4096).\n\ndo_max_header_value_length(Config, Port, Name, MaxLen) ->\n\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t{ok, http} = gun:await_up(ConnPid),\n\tStreamRef1 = gun:get(ConnPid, \"/\", #{Name => lists:duplicate(MaxLen, $a)}),\n\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef1),\n\t%% We * 2 because this is a soft limit.\n\tStreamRef2 = gun:get(ConnPid, \"/\", #{Name => lists:duplicate(MaxLen * 2, $a)}),\n\t{response, fin, 431, _} = gun:await(ConnPid, StreamRef2),\n\tgun:close(ConnPid).\n\npersistent_term_router(Config) ->\n\tdoc(\"The router can retrieve the routes from persistent_term storage.\"),\n\tcase erlang:function_exported(persistent_term, get, 1) of\n\t\ttrue -> do_persistent_term_router(Config);\n\t\tfalse -> {skip, \"This test uses the persistent_term functionality added in Erlang/OTP 21.2.\"}\n\tend.\n\ndo_persistent_term_router(Config) ->\n\tpersistent_term:put(?FUNCTION_NAME, init_dispatch(Config)),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => {persistent_term, ?FUNCTION_NAME}}\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\tStreamRef = gun:get(ConnPid, \"/\"),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef),\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nrequest_timeout(Config) ->\n\tdoc(\"Ensure the request_timeout drops connections when requests \"\n\t\t\"fail to come in fast enough.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\trequest_timeout => 500\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\t{error, {down, {shutdown, closed}}} = gun:await(ConnPid, undefined, 1000)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nrequest_timeout_pipeline(Config) ->\n\tdoc(\"Ensure the request_timeout drops connections when requests \"\n\t\t\"fail to come in fast enough after pipelined requests went through.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\trequest_timeout => 500\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\tStreamRef1 = gun:get(ConnPid, \"/\"),\n\t\tStreamRef2 = gun:get(ConnPid, \"/\"),\n\t\tStreamRef3 = gun:get(ConnPid, \"/\"),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef1),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef2),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef3),\n\t\t{error, {down, {shutdown, closed}}} = gun:await(ConnPid, undefined, 1000)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nrequest_timeout_pipeline_delay(Config) ->\n\tdoc(\"Ensure the request_timeout does not trigger on requests \"\n\t\t\"coming in after a large request body.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\trequest_timeout => 500\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\tStreamRef1 = gun:post(ConnPid, \"/\", #{}, <<0:8000000>>),\n\t\tStreamRef2 = gun:get(ConnPid, \"/delay_hello\"),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef1),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef2),\n\t\t{error, {down, {shutdown, closed}}} = gun:await(ConnPid, undefined, 1000)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nrequest_timeout_skip_body(Config) ->\n\tdoc(\"Ensure the request_timeout drops connections when requests \"\n\t\t\"fail to come in fast enough after skipping a request body.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\trequest_timeout => 500\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tClient = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]),\n\t\tok = raw_send(Client, <<\n\t\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\t\"host: localhost\\r\\n\"\n\t\t\t\"content-length: 12\\r\\n\\r\\n\"\n\t\t>>),\n\t\tData = raw_recv_head(Client),\n\t\t{'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data),\n\t\t{Headers, Rest} = cow_http:parse_headers(Rest0),\n\t\t{_, Len} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\t\t<<\"Hello world!\">> = raw_recv_rest(Client, binary_to_integer(Len), Rest),\n\t\t%% We then send the request data that should be skipped by Cowboy.\n\t\ttimer:sleep(100),\n\t\traw_send(Client, <<\"Hello world!\">>),\n\t\t%% Connection should be closed by the request_timeout after that.\n\t\t{error, closed} = raw_recv(Client, 1, 1000)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nrequest_timeout_skip_body_more(Config) ->\n\tdoc(\"Ensure the request_timeout drops connections when requests \"\n\t\t\"fail to come in fast enough after skipping a request body.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\trequest_timeout => 500\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tClient = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]),\n\t\tok = raw_send(Client, <<\n\t\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\t\"host: localhost\\r\\n\"\n\t\t\t\"content-length: 12\\r\\n\\r\\n\"\n\t\t>>),\n\t\tData = raw_recv_head(Client),\n\t\t{'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data),\n\t\t{Headers, Rest} = cow_http:parse_headers(Rest0),\n\t\t{_, Len} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\t\t<<\"Hello world!\">> = raw_recv_rest(Client, binary_to_integer(Len), Rest),\n\t\t%% We then send the request data that should be skipped by Cowboy.\n\t\ttimer:sleep(100),\n\t\traw_send(Client, <<\"Hello world!\">>),\n\t\t%% Send the start of another request.\n\t\tok = raw_send(Client, <<\n\t\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\t\"host: localhost\\r\\n\"\n\t\t\t%% Missing final \\r\\n on purpose.\n\t\t>>),\n\t\t%% Connection should be closed by the request_timeout after that.\n\t\t%% We attempt to send a 408 response on a best effort basis so\n\t\t%% that is accepted as well.\n\t\tcase raw_recv(Client, 13, 1000) of\n\t\t\t{error, closed} -> ok;\n\t\t\t{ok, <<\"HTTP/1.1 408 \", _/bits>>} -> ok\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nrequest_timeout_infinity(Config) ->\n\tdoc(\"Ensure the request_timeout option accepts the infinity value.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\trequest_timeout => infinity\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\ttimer:sleep(500),\n\t\t#{socket := Socket} = gun:info(ConnPid),\n\t\tPid = get_remote_pid_tcp(Socket),\n\t\tRef = erlang:monitor(process, Pid),\n\t\treceive\n\t\t\t{'DOWN', Ref, process, Pid, Reason} ->\n\t\t\t\terror(Reason)\n\t\tafter 1000 ->\n\t\t\tgun:close(ConnPid)\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_options_chunked_false(Config) ->\n\tdoc(\"Confirm the option chunked can be dynamically set to disable \"\n\t\t\"chunked transfer-encoding. This results in the closing of the \"\n\t\t\"connection after the current request.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tchunked => true\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tRequest = \"GET /set_options/chunked_false HTTP/1.1\\r\\nhost: localhost\\r\\n\\r\\n\",\n\t\tClient = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]),\n\t\tok = raw_send(Client, Request),\n\t\tRest = case catch raw_recv_head(Client) of\n\t\t\t{'EXIT', _} -> error(closed);\n\t\t\tData ->\n\t\t\t\t%% Cowboy always advertises itself as HTTP/1.1.\n\t\t\t\t{'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data),\n\t\t\t\t{Headers, Rest1} = cow_http:parse_headers(Rest0),\n\t\t\t\tfalse = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\t\t\t\tfalse = lists:keyfind(<<\"transfer-encoding\">>, 1, Headers),\n\t\t\t\tRest1\n\t\tend,\n\t\tBits = 8000000 - bit_size(Rest),\n\t\traw_expect_recv(Client, <<0:Bits>>),\n\t\t{error, closed} = raw_recv(Client, 1, 1000)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_options_chunked_false_ignored(Config) ->\n\tdoc(\"Confirm the option chunked can be dynamically set to disable \"\n\t\t\"chunked transfer-encoding, and that it is ignored if the \"\n\t\t\"response is not streamed.\"),\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tchunked => true\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t%% We do a first request setting the option but not\n\t\t%% using chunked transfer-encoding in the response.\n\t\tStreamRef1 = gun:get(ConnPid, \"/set_options/chunked_false_ignored\"),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef1),\n\t\t{ok, <<\"Hello world!\">>} = gun:await_body(ConnPid, StreamRef1),\n\t\t%% We then do a second request to confirm that chunked\n\t\t%% is not disabled for that second request.\n\t\tStreamRef2 = gun:get(ConnPid, \"/resp/stream_reply2/200\"),\n\t\t{response, nofin, 200, Headers} = gun:await(ConnPid, StreamRef2),\n\t\t{_, <<\"chunked\">>} = lists:keyfind(<<\"transfer-encoding\">>, 1, Headers),\n\t\tgun:close(ConnPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_options_idle_timeout(Config) ->\n\tdoc(\"Confirm that the idle_timeout option can be dynamically \"\n\t\t\"set to change how long Cowboy will wait before it closes the connection.\"),\n\t%% We start with a long timeout and then cut it short.\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tidle_timeout => 60000\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\ttimer:sleep(500),\n\t\t#{socket := Socket} = gun:info(ConnPid),\n\t\tPid = get_remote_pid_tcp(Socket),\n\t\t_ = gun:post(ConnPid, \"/set_options/idle_timeout_short\",\n\t\t\t[{<<\"content-type\">>, <<\"text/plain\">>}]),\n\t\tRef = erlang:monitor(process, Pid),\n\t\treceive\n\t\t\t{'DOWN', Ref, process, Pid, _} ->\n\t\t\t\tok\n\t\tafter 2000 ->\n\t\t\terror(timeout)\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_options_idle_timeout_only_applies_to_current_request(Config) ->\n\tdoc(\"Confirm that changes to the idle_timeout option only apply to the current stream.\"),\n\t%% We start with a long timeout and then cut it short.\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tidle_timeout => 500\n\t}),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tConnPid = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\t\t{ok, http} = gun:await_up(ConnPid),\n\t\ttimer:sleep(500),\n\t\t#{socket := Socket} = gun:info(ConnPid),\n\t\tPid = get_remote_pid_tcp(Socket),\n\t\tStreamRef = gun:post(ConnPid, \"/set_options/idle_timeout_long\",\n\t\t\t[{<<\"content-type\">>, <<\"text/plain\">>}]),\n\t\tRef = erlang:monitor(process, Pid),\n\t\treceive\n\t\t\t{'DOWN', Ref, process, Pid, Reason} ->\n\t\t\t\terror(Reason)\n\t\tafter 2000 ->\n\t\t\tok\n\t\tend,\n\t\t%% Finish the first request and start a second one to confirm\n\t\t%% the idle_timeout option is back to normal.\n\t\tgun:data(ConnPid, StreamRef, fin, <<\"Hello!\">>),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, StreamRef),\n\t\t{ok, <<\"Hello!\">>} = gun:await_body(ConnPid, StreamRef),\n\t\t_ = gun:post(ConnPid, \"/echo/read_body\",\n\t\t\t[{<<\"content-type\">>, <<\"text/plain\">>}]),\n\t\treceive\n\t\t\t{'DOWN', Ref, process, Pid, _} ->\n\t\t\t\tok\n\t\tafter 2000 ->\n\t\t\terror(timeout)\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nswitch_protocol_flush(Config) ->\n\tdoc(\"Confirm that switch_protocol does not flush unrelated messages.\"),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tstream_handlers => [switch_protocol_flush_h]\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tSelf = self(),\n\t\tConnPid = gun_open([{port, Port}, {type, tcp}, {protocol, http}|Config]),\n\t\t_ = gun:get(ConnPid, \"/\", [\n\t\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t\t]),\n\t\treceive\n\t\t\t{Self, Events} ->\n\t\t\t\tswitch_protocol_flush_h:validate(Events)\n\t\tafter 5000 ->\n\t\t\terror(timeout)\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\ngraceful_shutdown_connection(Config) ->\n\tdoc(\"Check that the current request is handled before gracefully \"\n\t    \"shutting down a connection.\"),\n\tDispatch = cowboy_router:compile([{\"localhost\", [\n\t\t{\"/hello\", delay_hello_h,\n\t\t\t#{delay => 0, notify_received => self()}},\n\t\t{\"/delay_hello\", delay_hello_h,\n\t\t\t#{delay => 1000, notify_received => self()}}\n\t]}]),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => Dispatch}\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, [{port, 0}], ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\tClient = raw_open([{type, tcp}, {port, Port}, {opts, []}|Config]),\n\t\tok = raw_send(Client,\n\t\t\t\"GET /delay_hello HTTP/1.1\\r\\n\"\n\t\t\t\"Host: localhost\\r\\n\\r\\n\"\n\t\t\t\"GET /hello HTTP/1.1\\r\\n\"\n\t\t\t\"Host: localhost\\r\\n\\r\\n\"),\n\t\treceive {request_received, <<\"/delay_hello\">>} -> ok end,\n\t\treceive {request_received, <<\"/hello\">>} -> ok end,\n\t\tCowboyConnPid = get_remote_pid_tcp(element(2, Client)),\n\t\tCowboyConnRef = erlang:monitor(process, CowboyConnPid),\n\t\tok = sys:terminate(CowboyConnPid, system_is_going_down),\n\t\tRest = case catch raw_recv_head(Client) of\n\t\t\t{'EXIT', _} -> error(closed);\n\t\t\tData ->\n\t\t\t\t{'HTTP/1.1', 200, _, Rest0} = cow_http:parse_status_line(Data),\n\t\t\t\t{Headers, Rest1} = cow_http:parse_headers(Rest0),\n\t\t\t\t<<\"close\">> = proplists:get_value(<<\"connection\">>, Headers),\n\t\t\t\tRest1\n\t\tend,\n\t\t<<\"Hello world!\">> = raw_recv_rest(Client, byte_size(<<\"Hello world!\">>), Rest),\n\t\t{error, closed} = raw_recv(Client, 0, 1000),\n\t\treceive\n\t\t\t{'DOWN', CowboyConnRef, process, CowboyConnPid, _Reason} ->\n\t\t\t\tok\n\t\tend\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\ngraceful_shutdown_listener(Config) ->\n\tdoc(\"Check that connections are shut down gracefully when stopping a listener.\"),\n\tTransOpts = #{\n\t\tsocket_opts => [{port, 0}],\n\t\tshutdown => 1000 %% Shorter timeout to make the test case faster.\n\t},\n\tDispatch = cowboy_router:compile([{\"localhost\", [\n\t\t{\"/delay_hello\", delay_hello_h,\n\t\t\t#{delay => 500, notify_received => self()}},\n\t\t{\"/long_delay_hello\", delay_hello_h,\n\t\t\t#{delay => 10000, notify_received => self()}}\n\t]}]),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => Dispatch}\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, TransOpts, ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\tConnPid1 = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\tRef1 = gun:get(ConnPid1, \"/delay_hello\"),\n\tConnPid2 = gun_open([{type, tcp}, {protocol, http}, {port, Port}|Config]),\n\tRef2 = gun:get(ConnPid2, \"/long_delay_hello\"),\n\t%% Shutdown listener while the handlers are working.\n\treceive {request_received, <<\"/delay_hello\">>} -> ok end,\n\treceive {request_received, <<\"/long_delay_hello\">>} -> ok end,\n\t%% Note: This call does not complete quickly and will\n\t%% prevent other cowboy:stop_listener/1 calls to complete.\n\tok = cowboy:stop_listener(?FUNCTION_NAME),\n\t%% Check that the 1st request is handled before shutting down.\n\t{response, nofin, 200, RespHeaders} = gun:await(ConnPid1, Ref1),\n\t<<\"close\">> = proplists:get_value(<<\"connection\">>, RespHeaders),\n\t{ok, RespBody} = gun:await_body(ConnPid1, Ref1),\n\t<<\"Hello world!\">> = iolist_to_binary(RespBody),\n\tgun:close(ConnPid1),\n\t%% Check that the 2nd (very slow) request is not handled.\n\t{error, {stream_error, closed}} = gun:await(ConnPid2, Ref2),\n\tgun:close(ConnPid2).\n\nsend_timeout_close(_Config) ->\n\tdoc(\"Check that connections are closed on send timeout.\"),\n\tTransOpts = #{\n\t\tsocket_opts => [\n\t\t\t{port, 0},\n\t\t\t{send_timeout, 100},\n\t\t\t{send_timeout_close, true},\n\t\t\t{sndbuf, 10}\n\t\t]\n\t},\n\tDispatch = cowboy_router:compile([{\"localhost\", [\n\t\t{\"/endless\", loop_handler_endless_h, #{delay => 100}}\n\t]}]),\n\tProtoOpts = #{\n\t\tenv => #{dispatch => Dispatch},\n\t\tidle_timeout => infinity\n\t},\n\t{ok, _} = cowboy:start_clear(?FUNCTION_NAME, TransOpts, ProtoOpts),\n\tPort = ranch:get_port(?FUNCTION_NAME),\n\ttry\n\t\t%% Connect a client that sends a request and waits indefinitely.\n\t\t{ok, ClientSocket} = gen_tcp:connect(\"localhost\", Port,\n\t\t\t[{recbuf, 10}, {buffer, 10}, {active, false}, {packet, 0}]),\n\t\tok = gen_tcp:send(ClientSocket, [\n\t\t\t\"GET /endless HTTP/1.1\\r\\n\",\n\t\t\t\"Host: localhost:\", integer_to_list(Port), \"\\r\\n\",\n\t\t\t\"x-test-pid: \", pid_to_list(self()), \"\\r\\n\\r\\n\"\n\t\t]),\n\t\t%% Wait for the handler to start then get its pid,\n\t\t%% the remote connection's pid and socket.\n\t\tStreamPid = receive\n\t\t\t{Self, StreamPid0, init} when Self =:= self() ->\n\t\t\t\tStreamPid0\n\t\tafter 1000 ->\n\t\t\terror(timeout)\n\t\tend,\n\t\tServerPid = ct_helper:get_remote_pid_tcp(ClientSocket),\n\t\t{links, ServerLinks} = process_info(ServerPid, links),\n\t\t[ServerSocket] = [PidOrPort || PidOrPort <- ServerLinks, is_port(PidOrPort)],\n\t\t%% Poll the socket repeatedly until it is closed by the server.\n\t\tWaitClosedFun =\n\t\t\tfun F(T) when T =< 0 ->\n\t\t\t\t\terror({status, prim_inet:getstatus(ServerSocket)});\n\t\t\t\tF(T) ->\n\t\t\t\t\tSnooze = 100,\n\t\t\t\t\tcase inet:sockname(ServerSocket) of\n\t\t\t\t\t\t{error, _} ->\n\t\t\t\t\t\t\ttimer:sleep(Snooze);\n\t\t\t\t\t\t{ok, _} ->\n\t\t\t\t\t\t\ttimer:sleep(Snooze),\n\t\t\t\t\t\t\tF(T - Snooze)\n\t\t\t\t\tend\n\t\t\tend,\n\t\tok = WaitClosedFun(2000),\n\t\tfalse = erlang:is_process_alive(StreamPid),\n\t\tfalse = erlang:is_process_alive(ServerPid)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n"
  },
  {
    "path": "test/http_perf_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(http_perf_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\n%% ct.\n\nall() ->\n\t%% @todo Enable HTTP/3 for this test suite.\n\tcowboy_test:common_all() -- [{group, h3}, {group, h3_compress}].\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE), no_parallel).\n\ninit_per_suite(Config) ->\n\tdo_log(\"\", []),\n\t%% Optionally enable `perf` for the current node.\n%\tspawn(fun() -> ct:pal(os:cmd(\"perf record -g -F 9999 -o /tmp/http_perf.data -p \" ++ os:getpid() ++ \" -- sleep 60\")) end),\n\tConfig.\n\nend_per_suite(_) ->\n\tok.\n\ninit_per_group(Name, Config) ->\n\t[{group, Name}|cowboy_test:init_common_groups(Name, Config, ?MODULE, #{\n\t\t%% HTTP/1.1\n\t\tmax_keepalive => infinity,\n\t\t%% HTTP/2\n\t\t%% @todo Must configure Gun for performance too.\n\t\tconnection_window_margin_size => 64*1024,\n\t\tenable_connect_protocol => true,\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tmax_frame_size_sent => 64*1024,\n\t\tmax_frame_size_received => 16384 * 1024 - 1,\n\t\tmax_received_frame_rate => {10_000_000, 1},\n\t\tstream_window_data_threshold => 1024,\n\t\tstream_window_margin_size => 64*1024\n\t})].\n\nend_per_group(Name, _) ->\n\tdo_log(\"\", []),\n\tcowboy_test:stop_group(Name).\n\n%% Routes.\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{'_', [\n\t\t{\"/\", hello_h, []},\n\t\t{\"/read_body\", read_body_h, []}\n\t]}]).\n\n%% Tests: Hello world.\n\nplain_h_hello_1(Config) ->\n\tdoc(\"Plain HTTP handler Hello World; 10K requests per 1 client.\"),\n\tdo_bench_get(?FUNCTION_NAME, \"/\", #{}, 1, 10000, Config).\n\nplain_h_hello_10(Config) ->\n\tdoc(\"Plain HTTP handler Hello World; 10K requests per 10 clients.\"),\n\tdo_bench_get(?FUNCTION_NAME, \"/\", #{}, 10, 10000, Config).\n\nstream_h_hello_1(Config) ->\n\tdoc(\"Stream handler Hello World; 10K requests per 1 client.\"),\n\tdo_stream_h_hello(Config, 1).\n\nstream_h_hello_10(Config) ->\n\tdoc(\"Stream handler Hello World; 10K requests per 10 clients.\"),\n\tdo_stream_h_hello(Config, 10).\n\ndo_stream_h_hello(Config, NumClients) ->\n\tRef = config(ref, Config),\n\tProtoOpts = ranch:get_protocol_options(Ref),\n\tStreamHandlers = case ProtoOpts of\n\t\t#{stream_handlers := _} -> [cowboy_compress_h, stream_hello_h];\n\t\t_ -> [stream_hello_h]\n\tend,\n\tranch:set_protocol_options(Ref, ProtoOpts#{\n\t\tenv => #{},\n\t\tstream_handlers => StreamHandlers\n\t}),\n\tdo_bench_get(?FUNCTION_NAME, \"/\", #{}, NumClients, 10000, Config),\n\tranch:set_protocol_options(Ref, ProtoOpts).\n\n%% Tests: Large body upload.\n\nplain_h_1M_post_1(Config) ->\n\tdoc(\"Plain HTTP handler body reading; 10K requests per 1 client.\"),\n\tdo_bench_post(?FUNCTION_NAME, \"/read_body\", #{}, <<0:8_000_000>>, 1, 10000, Config).\n\nplain_h_1M_post_10(Config) ->\n\tdoc(\"Plain HTTP handler body reading; 10K requests per 10 clients.\"),\n\tdo_bench_post(?FUNCTION_NAME, \"/read_body\", #{}, <<0:8_000_000>>, 10, 10000, Config).\n\nplain_h_10G_post(Config) ->\n\tdoc(\"Plain HTTP handler body reading; 1 request with a 10GB body.\"),\n\tdo_bench_post_one_large(?FUNCTION_NAME, \"/read_body\", #{}, 10_000, <<0:8_000_000>>, Config).\n\n%% Internal.\n\ndo_bench_get(What, Path, Headers, NumClients, NumRuns, Config) ->\n\tClients = [spawn_link(?MODULE, do_bench_get_proc,\n\t\t[self(), What, Path, Headers, NumRuns, Config])\n\t\t|| _ <- lists:seq(1, NumClients)],\n\t{Time, _} = timer:tc(?MODULE, do_bench_wait, [What, Clients]),\n\tdo_log(\"~32s: ~8bµs ~8.1freqs/s\", [\n\t\t[atom_to_list(config(group, Config)), $., atom_to_list(What)],\n\t\tTime,\n\t\t(NumClients * NumRuns) / Time * 1_000_000]),\n\tok.\n\ndo_bench_get_proc(Parent, What, Path, Headers0, NumRuns, Config) ->\n\tConnPid = gun_open(Config),\n\tHeaders = Headers0#{<<\"accept-encoding\">> => <<\"gzip\">>},\n\tParent ! {What, ready},\n\treceive {What, go} -> ok end,\n\tdo_bench_get_run(ConnPid, Path, Headers, NumRuns),\n\tParent ! {What, done},\n\tgun:close(ConnPid).\n\ndo_bench_get_run(_, _, _, 0) ->\n\tok;\ndo_bench_get_run(ConnPid, Path, Headers, Num) ->\n\tRef = gun:request(ConnPid, <<\"GET\">>, Path, Headers, <<>>),\n\t{response, IsFin, 200, _RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\t{ok, _} = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, Ref, infinity);\n\t\tfin -> {ok, <<>>}\n\tend,\n\tdo_bench_get_run(ConnPid, Path, Headers, Num - 1).\n\ndo_bench_post(What, Path, Headers, Body, NumClients, NumRuns, Config) ->\n\tClients = [spawn_link(?MODULE, do_bench_post_proc,\n\t\t[self(), What, Path, Headers, Body, NumRuns, Config])\n\t\t|| _ <- lists:seq(1, NumClients)],\n\t{Time, _} = timer:tc(?MODULE, do_bench_wait, [What, Clients]),\n\tdo_log(\"~32s: ~8bµs ~8.1freqs/s\", [\n\t\t[atom_to_list(config(group, Config)), $., atom_to_list(What)],\n\t\tTime,\n\t\t(NumClients * NumRuns) / Time * 1_000_000]),\n\tok.\n\ndo_bench_post_proc(Parent, What, Path, Headers0, Body, NumRuns, Config) ->\n\tConnPid = gun_open(Config),\n\tHeaders = Headers0#{<<\"accept-encoding\">> => <<\"gzip\">>},\n\tParent ! {What, ready},\n\treceive {What, go} -> ok end,\n\tdo_bench_post_run(ConnPid, Path, Headers, Body, NumRuns),\n\tParent ! {What, done},\n\tgun:close(ConnPid).\n\ndo_bench_post_run(_, _, _, _, 0) ->\n\tok;\ndo_bench_post_run(ConnPid, Path, Headers, Body, Num) ->\n\tRef = gun:request(ConnPid, <<\"POST\">>, Path, Headers, Body),\n\t{response, IsFin, 200, _RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\t{ok, _} = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, Ref, infinity);\n\t\tfin -> {ok, <<>>}\n\tend,\n\tdo_bench_post_run(ConnPid, Path, Headers, Body, Num - 1).\n\ndo_bench_post_one_large(What, Path, Headers, NumChunks, BodyChunk, Config) ->\n\tClient = spawn_link(?MODULE, do_bench_post_one_large_proc,\n\t\t[self(), What, Path, Headers, NumChunks, BodyChunk, Config]),\n\t{Time, _} = timer:tc(?MODULE, do_bench_wait, [What, [Client]]),\n\tdo_log(\"~32s: ~8bµs ~8.1freqs/s\", [\n\t\t[atom_to_list(config(group, Config)), $., atom_to_list(What)],\n\t\tTime,\n\t\t1 / Time * 1_000_000]),\n\tok.\n\ndo_bench_post_one_large_proc(Parent, What, Path, Headers0, NumChunks, BodyChunk, Config) ->\n\tConnPid = gun_open(Config),\n\tHeaders = Headers0#{<<\"accept-encoding\">> => <<\"gzip\">>},\n\tParent ! {What, ready},\n\treceive {What, go} -> ok end,\n\tStreamRef = gun:headers(ConnPid, <<\"POST\">>, Path, Headers#{\n\t\t<<\"content-length\">> => integer_to_binary(NumChunks * byte_size(BodyChunk))\n\t}),\n\tdo_bench_post_one_large_run(ConnPid, StreamRef, NumChunks - 1, BodyChunk),\n\t{response, IsFin, 200, _RespHeaders} = gun:await(ConnPid, StreamRef, infinity),\n\t{ok, _} = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, StreamRef, infinity);\n\t\tfin -> {ok, <<>>}\n\tend,\n\tParent ! {What, done},\n\tgun:close(ConnPid).\n\ndo_bench_post_one_large_run(ConnPid, StreamRef, 0, BodyChunk) ->\n\tgun:data(ConnPid, StreamRef, fin, BodyChunk);\ndo_bench_post_one_large_run(ConnPid, StreamRef, NumChunks, BodyChunk) ->\n\tgun:data(ConnPid, StreamRef, nofin, BodyChunk),\n\tdo_bench_post_one_large_run(ConnPid, StreamRef, NumChunks - 1, BodyChunk).\n\ndo_bench_wait(What, Clients) ->\n\t_ = [receive {What, ready} -> ok end || _ <- Clients],\n\t_ = [ClientPid ! {What, go} || ClientPid <- Clients],\n\t_ = [receive {What, done} -> ok end || _ <- Clients],\n\tok.\n\ndo_log(Str, Args) ->\n\tct:log(Str, Args),\n\tio:format(ct_default_gl, Str ++ \"~n\", Args).\n"
  },
  {
    "path": "test/loop_handler_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(loop_handler_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\n%% ct.\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\n%% Dispatch configuration.\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{'_', [\n\t\t{\"/long_polling\", long_polling_h, []},\n\t\t{\"/loop_body\", loop_handler_body_h, []},\n\t\t{\"/loop_request_timeout\", loop_handler_timeout_h, []},\n\t\t{\"/loop_timeout_init\", loop_handler_timeout_init_h, []},\n\t\t{\"/loop_timeout_info\", loop_handler_timeout_info_h, []},\n\t\t{\"/loop_timeout_hibernate\", loop_handler_timeout_hibernate_h, []}\n\t]}]).\n\n%% Tests.\n\ninfo_read_body(Config) ->\n\tdoc(\"Check that a loop handler can read the request body in info/3.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, \"/loop_body\", [{<<\"accept-encoding\">>, <<\"gzip\">>}],\n\t\t<< 0:100000/unit:8 >>),\n\t{response, fin, 200, _} = gun:await(ConnPid, Ref),\n\tok.\n\nlong_polling(Config) ->\n\tdoc(\"Simple long-polling.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, fin, 299, _} = gun:await(ConnPid, Ref),\n\tok.\n\nlong_polling_unread_body(Config) ->\n\tdoc(\"Long-polling with a body that is not read by the handler.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, \"/long_polling\", [{<<\"accept-encoding\">>, <<\"gzip\">>}],\n\t\t<< 0:100000/unit:8 >>),\n\t{response, fin, 299, _} = gun:await(ConnPid, Ref),\n\tok.\n\nlong_polling_pipeline(Config) ->\n\tdoc(\"Pipeline of long-polling calls.\"),\n\tConnPid = gun_open(Config),\n\tRefs = [gun:get(ConnPid, \"/long_polling\", [{<<\"accept-encoding\">>, <<\"gzip\">>}])\n\t\t|| _ <- lists:seq(1, 2)],\n\t_ = [{response, fin, 299, _} = gun:await(ConnPid, Ref) || Ref <- Refs],\n\tok.\n\nrequest_timeout(Config) ->\n\tdoc(\"Ensure that the request_timeout isn't applied when a request is ongoing.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/loop_request_timeout\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref, 10000),\n\tok.\n\ntimeout_hibernate(Config) ->\n\tdoc(\"Ensure that loop handler idle timeouts don't trigger after hibernate is returned.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/loop_timeout_hibernate\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, fin, 200, _} = gun:await(ConnPid, Ref),\n\tok.\n\ntimeout_info(Config) ->\n\tdoc(\"Ensure that loop handler idle timeouts trigger on time when set in info/3.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/loop_timeout_info\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, fin, 299, _} = gun:await(ConnPid, Ref),\n\tok.\n\ntimeout_init(Config) ->\n\tdoc(\"Ensure that loop handler idle timeouts trigger on time when set in init/2.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/loop_timeout_init?timeout=1000\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, fin, 200, _} = gun:await(ConnPid, Ref),\n\tRef2 = gun:get(ConnPid, \"/loop_timeout_init?timeout=100\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, fin, 299, _} = gun:await(ConnPid, Ref2),\n\tok.\n"
  },
  {
    "path": "test/metrics_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(metrics_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n-import(cowboy_test, [gun_down/1]).\n-import(cowboy_test, [raw_open/1]).\n-import(cowboy_test, [raw_send/2]).\n-import(cowboy_test, [raw_recv_head/1]).\n\n%% ct.\n\nsuite() ->\n\t[{timetrap, 30000}].\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_group(Name = http, Config) ->\n\tcowboy_test:init_http(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = https, Config) ->\n\tcowboy_test:init_http(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = h2, Config) ->\n\tcowboy_test:init_http2(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = h2c, Config) ->\n\tConfig1 = cowboy_test:init_http(Name, init_plain_opts(Config), Config),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_per_group(Name = h3, Config) ->\n\tcowboy_test:init_http3(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = http_compress, Config) ->\n\tcowboy_test:init_http(Name, init_compress_opts(Config), Config);\ninit_per_group(Name = https_compress, Config) ->\n\tcowboy_test:init_http(Name, init_compress_opts(Config), Config);\ninit_per_group(Name = h2_compress, Config) ->\n\tcowboy_test:init_http2(Name, init_compress_opts(Config), Config);\ninit_per_group(Name = h2c_compress, Config) ->\n\tConfig1 = cowboy_test:init_http(Name, init_compress_opts(Config), Config),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_per_group(Name = h3_compress, Config) ->\n\tcowboy_test:init_http3(Name, init_compress_opts(Config), Config).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_plain_opts(Config) ->\n\t#{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))},\n\t\tmetrics_callback => do_metrics_callback(),\n\t\tstream_handlers => [cowboy_metrics_h, cowboy_stream_h]\n\t}.\n\ninit_compress_opts(Config) ->\n\t#{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))},\n\t\tmetrics_callback => do_metrics_callback(),\n\t\tstream_handlers => [cowboy_metrics_h, cowboy_compress_h, cowboy_stream_h]\n\t}.\n\ninit_routes(_) -> [\n\t{\"localhost\", [\n\t\t{\"/\", hello_h, []},\n\t\t{\"/crash/no_reply\", crash_h, no_reply},\n\t\t{\"/crash/reply\", crash_h, reply},\n\t\t{\"/default\", default_h, []},\n\t\t{\"/full/:key\", echo_h, []},\n\t\t{\"/resp/:key[/:arg]\", resp_h, []},\n\t\t{\"/set_options/:key\", set_options_h, []},\n\t\t{\"/ws_echo\", ws_echo, []}\n\t]}\n].\n\ndo_metrics_callback() ->\n\tfun(Metrics) ->\n\t\tPid = case Metrics of\n\t\t\t#{req := #{headers := #{<<\"x-test-pid\">> := P}}} ->\n\t\t\t\tlist_to_pid(binary_to_list(P));\n\t\t\t#{partial_req := #{headers := #{<<\"x-test-pid\">> := P}}} ->\n\t\t\t\tlist_to_pid(binary_to_list(P));\n\t\t\t_ ->\n\t\t\t\twhereis(early_error_metrics)\n\t\tend,\n\t\tPid ! {metrics, self(), Metrics},\n\t\tok\n\tend.\n\n%% Tests.\n\nhello_world(Config) ->\n\tdoc(\"Confirm metrics are correct for a normal GET request.\"),\n\tdo_get(\"/\", #{}, Config).\n\nuser_data(Config) ->\n\tdoc(\"Confirm user data can be attached to metrics.\"),\n\tdo_get(\"/set_options/metrics_user_data\", #{handler => set_options_h}, Config).\n\ndo_get(Path, UserData, Config) ->\n\t%% Perform a GET request.\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, Path, [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\t{response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\t{ok, RespBody} = gun:await_body(ConnPid, Ref, infinity),\n\t%% Receive the metrics and validate them.\n\treceive\n\t\t{metrics, From, Metrics} ->\n\t\t\t%% Ensure the timestamps are in the expected order.\n\t\t\t#{\n\t\t\t\treq_start := ReqStart, req_end := ReqEnd,\n\t\t\t\tresp_start := RespStart, resp_end := RespEnd\n\t\t\t} = Metrics,\n\t\t\ttrue = (ReqStart =< RespStart)\n\t\t\t\tand (RespStart =< RespEnd)\n\t\t\t\tand (RespEnd =< ReqEnd),\n\t\t\t%% We didn't send a body.\n\t\t\t#{\n\t\t\t\treq_body_start := undefined,\n\t\t\t\treq_body_end := undefined,\n\t\t\t\treq_body_length := 0\n\t\t\t} = Metrics,\n\t\t\t%% We got a 200 response with a body.\n\t\t\t#{\n\t\t\t\tresp_status := 200,\n\t\t\t\tresp_headers := ExpectedRespHeaders,\n\t\t\t\tresp_body_length := RespBodyLen\n\t\t\t} = Metrics,\n\t\t\t%% The transfer-encoding header is hidden from stream handlers.\n\t\t\tExpectedRespHeaders = maps:remove(<<\"transfer-encoding\">>,\n\t\t\t\tmaps:from_list(RespHeaders)),\n\t\t\ttrue = byte_size(RespBody) > 0,\n\t\t\ttrue = RespBodyLen > 0,\n\t\t\t%% The request process executed normally.\n\t\t\t#{procs := Procs} = Metrics,\n\t\t\t[{_, #{\n\t\t\t\tspawn := ProcSpawn,\n\t\t\t\texit := ProcExit,\n\t\t\t\treason := normal\n\t\t\t}}] = maps:to_list(Procs),\n\t\t\ttrue = ProcSpawn =< ProcExit,\n\t\t\t%% Confirm other metadata are as expected.\n\t\t\t#{\n\t\t\t\tref := _,\n\t\t\t\tpid := From,\n\t\t\t\tstreamid := StreamID,\n\t\t\t\treason := normal, %% @todo Getting h3_no_error here.\n\t\t\t\treq := #{},\n\t\t\t\tinformational := [],\n\t\t\t\tuser_data := UserData\n\t\t\t} = Metrics,\n\t\t\tdo_check_streamid(StreamID, Config),\n\t\t\t%% All good!\n\t\t\tgun:close(ConnPid)\n\tend.\n\ndo_check_streamid(StreamID, Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> 1 = StreamID;\n\t\thttp2 -> 1 = StreamID;\n\t\thttp3 -> 0 = StreamID\n\tend.\n\npost_body(Config) ->\n\tdoc(\"Confirm metrics are correct for a normal POST request.\"),\n\t%% Perform a POST request.\n\tConnPid = gun_open(Config),\n\tBody = <<0:8000000>>,\n\tRef = gun:post(ConnPid, \"/full/read_body\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t], Body),\n\t{response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\t{ok, RespBody} = gun:await_body(ConnPid, Ref, infinity),\n\t%% Receive the metrics and validate them.\n\treceive\n\t\t{metrics, From, Metrics} ->\n\t\t\t%% Ensure the timestamps are in the expected order.\n\t\t\t#{\n\t\t\t\treq_start := ReqStart, req_end := ReqEnd,\n\t\t\t\tresp_start := RespStart, resp_end := RespEnd\n\t\t\t} = Metrics,\n\t\t\ttrue = (ReqStart =< RespStart)\n\t\t\t\tand (RespStart =< RespEnd)\n\t\t\t\tand (RespEnd =< ReqEnd),\n\t\t\t%% We didn't send a body.\n\t\t\t#{\n\t\t\t\treq_body_start := ReqBodyStart,\n\t\t\t\treq_body_end := ReqBodyEnd,\n\t\t\t\treq_body_length := ReqBodyLen\n\t\t\t} = Metrics,\n\t\t\ttrue = ReqBodyStart =< ReqBodyEnd,\n\t\t\tReqBodyLen = byte_size(Body),\n\t\t\t%% We got a 200 response with a body.\n\t\t\t#{\n\t\t\t\tresp_status := 200,\n\t\t\t\tresp_headers := ExpectedRespHeaders,\n\t\t\t\tresp_body_length := RespBodyLen\n\t\t\t} = Metrics,\n\t\t\tExpectedRespHeaders = maps:from_list(RespHeaders),\n\t\t\ttrue = byte_size(RespBody) > 0,\n\t\t\ttrue = RespBodyLen > 0,\n\t\t\t%% The request process executed normally.\n\t\t\t#{procs := Procs} = Metrics,\n\t\t\t[{_, #{\n\t\t\t\tspawn := ProcSpawn,\n\t\t\t\texit := ProcExit,\n\t\t\t\treason := normal\n\t\t\t}}] = maps:to_list(Procs),\n\t\t\ttrue = ProcSpawn =< ProcExit,\n\t\t\t%% Confirm other metadata are as expected.\n\t\t\t#{\n\t\t\t\tref := _,\n\t\t\t\tpid := From,\n\t\t\t\tstreamid := StreamID,\n\t\t\t\treason := normal,\n\t\t\t\treq := #{},\n\t\t\t\tinformational := [],\n\t\t\t\tuser_data := #{}\n\t\t\t} = Metrics,\n\t\t\tdo_check_streamid(StreamID, Config),\n\t\t\t%% All good!\n\t\t\tgun:close(ConnPid)\n\tend.\n\nno_resp_body(Config) ->\n\tdoc(\"Confirm metrics are correct for a default 204 response to a GET request.\"),\n\t%% Perform a GET request.\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/default\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\t{response, fin, 204, RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\t%% Receive the metrics and validate them.\n\treceive\n\t\t{metrics, From, Metrics} ->\n\t\t\t%% Ensure the timestamps are in the expected order.\n\t\t\t#{\n\t\t\t\treq_start := ReqStart, req_end := ReqEnd,\n\t\t\t\tresp_start := RespStart, resp_end := RespEnd\n\t\t\t} = Metrics,\n\t\t\ttrue = (ReqStart =< RespStart)\n\t\t\t\tand (RespStart =< RespEnd)\n\t\t\t\tand (RespEnd =< ReqEnd),\n\t\t\t%% We didn't send a body.\n\t\t\t#{\n\t\t\t\treq_body_start := undefined,\n\t\t\t\treq_body_end := undefined,\n\t\t\t\treq_body_length := 0\n\t\t\t} = Metrics,\n\t\t\t%% We got a 200 response with a body.\n\t\t\t#{\n\t\t\t\tresp_status := 204,\n\t\t\t\tresp_headers := ExpectedRespHeaders,\n\t\t\t\tresp_body_length := 0\n\t\t\t} = Metrics,\n\t\t\tExpectedRespHeaders = maps:from_list(RespHeaders),\n\t\t\t%% The request process executed normally.\n\t\t\t#{procs := Procs} = Metrics,\n\t\t\t[{_, #{\n\t\t\t\tspawn := ProcSpawn,\n\t\t\t\texit := ProcExit,\n\t\t\t\treason := normal\n\t\t\t}}] = maps:to_list(Procs),\n\t\t\ttrue = ProcSpawn =< ProcExit,\n\t\t\t%% Confirm other metadata are as expected.\n\t\t\t#{\n\t\t\t\tref := _,\n\t\t\t\tpid := From,\n\t\t\t\tstreamid := StreamID,\n\t\t\t\treason := normal,\n\t\t\t\treq := #{},\n\t\t\t\tinformational := [],\n\t\t\t\tuser_data := #{}\n\t\t\t} = Metrics,\n\t\t\tdo_check_streamid(StreamID, Config),\n\t\t\t%% All good!\n\t\t\tgun:close(ConnPid)\n\tend.\n\nearly_error(Config) ->\n\tdoc(\"Confirm metrics are correct for an early_error response.\"),\n\t%% Perform a malformed GET request.\n\tConnPid = gun_open(Config),\n\t%% We must use different solutions to hit early_error with a stream_error\n\t%% reason in both protocols.\n\t{Method, Headers, Status, Error} = case config(protocol, Config) of\n\t\thttp -> {<<\"GET\">>, [{<<\"host\">>, <<\"host:port\">>}], 400, protocol_error};\n\t\thttp2 -> {<<\"TRACE\">>, [], 501, no_error};\n\t\thttp3 -> {<<\"TRACE\">>, [], 501, h3_no_error}\n\tend,\n\tRef = gun:request(ConnPid, Method, \"/\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t|Headers], <<>>),\n\t{response, fin, Status, RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\t%% Receive the metrics and validate them.\n\treceive\n\t\t{metrics, From, Metrics} ->\n\t\t\t%% Confirm the metadata is there as expected.\n\t\t\t#{\n\t\t\t\tref := _,\n\t\t\t\tpid := From,\n\t\t\t\tstreamid := StreamID,\n\t\t\t\treason := {stream_error, Error, _},\n\t\t\t\tpartial_req := #{},\n\t\t\t\tresp_status := Status,\n\t\t\t\tresp_headers := ExpectedRespHeaders,\n\t\t\t\tearly_error_time := _,\n\t\t\t\tresp_body_length := 0\n\t\t\t} = Metrics,\n\t\t\tdo_check_streamid(StreamID, Config),\n\t\t\tExpectedRespHeaders = maps:from_list(RespHeaders),\n\t\t\t%% All good!\n\t\t\tgun:close(ConnPid)\n\tend.\n\nearly_error_request_line(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_early_error_request_line(Config);\n\t\thttp2 -> doc(\"There are no request lines in HTTP/2.\");\n\t\thttp3 -> doc(\"There are no request lines in HTTP/3.\")\n\tend.\n\ndo_early_error_request_line(Config) ->\n\tdoc(\"Confirm metrics are correct for an early_error response \"\n\t\t\"that occurred on the request-line.\"),\n\t%% Register the process in order to receive the metrics event.\n\tregister(early_error_metrics, self()),\n\t%% Send a malformed request-line.\n\tClient = raw_open(Config),\n\tok = raw_send(Client, <<\"FOO bar\\r\\n\">>),\n\t{'HTTP/1.1', 400, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{RespHeaders, _} = cow_http:parse_headers(Rest),\n\t%% Receive the metrics and validate them.\n\treceive\n\t\t{metrics, From, Metrics} ->\n\t\t\t%% Confirm the metadata is there as expected.\n\t\t\t#{\n\t\t\t\tref := _,\n\t\t\t\tpid := From,\n\t\t\t\tstreamid := StreamID,\n\t\t\t\treason := {connection_error, protocol_error, _},\n\t\t\t\tpartial_req := #{},\n\t\t\t\tresp_status := 400,\n\t\t\t\tresp_headers := ExpectedRespHeaders,\n\t\t\t\tearly_error_time := _,\n\t\t\t\tresp_body_length := 0\n\t\t\t} = Metrics,\n\t\t\tdo_check_streamid(StreamID, Config),\n\t\t\tExpectedRespHeaders = maps:from_list(RespHeaders),\n\t\t\t%% All good!\n\t\t\tok\n\tend.\n\n%% This test is identical to normal GET except for the handler.\nstream_reply(Config) ->\n\tdoc(\"Confirm metrics are correct for long polling.\"),\n\tdo_get(\"/resp/stream_reply2/200\", #{}, Config).\n\nws(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_ws(Config);\n\t\t%% @todo The test can be implemented for HTTP/2.\n\t\thttp2 -> doc(\"It is not currently possible to switch to Websocket over HTTP/2.\");\n\t\thttp3 -> {skip, \"Gun does not currently support Websocket over HTTP/3.\"}\n\tend.\n\ndo_ws(Config) ->\n\tdoc(\"Confirm metrics are correct when switching to Websocket.\"),\n\tConnPid = gun_open(Config),\n\t{ok, http} = gun:await_up(ConnPid, infinity),\n\tStreamRef = gun:ws_upgrade(ConnPid, \"/ws_echo\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\treceive\n\t\t{metrics, From, Metrics} ->\n\t\t\t%% Ensure the timestamps are in the expected order.\n\t\t\t#{\n\t\t\t\treq_start := ReqStart,\n\t\t\t\treq_end := ReqEnd\n\t\t\t} = Metrics,\n\t\t\ttrue = ReqStart =< ReqEnd,\n\t\t\t%% We didn't send a body.\n\t\t\t#{\n\t\t\t\treq_body_start := undefined,\n\t\t\t\treq_body_end := undefined,\n\t\t\t\treq_body_length := 0\n\t\t\t} = Metrics,\n\t\t\t%% We didn't send a response.\n\t\t\t#{\n\t\t\t\tresp_start := undefined,\n\t\t\t\tresp_end := undefined,\n\t\t\t\tresp_status := undefined,\n\t\t\t\tresp_headers := undefined,\n\t\t\t\tresp_body_length := 0\n\t\t\t} = Metrics,\n\t\t\t%% The request process may not have terminated before terminate\n\t\t\t%% is called. We therefore only check when it spawned.\n\t\t\t#{procs := Procs} = Metrics,\n\t\t\t[{_, #{\n\t\t\t\tspawn := _\n\t\t\t}}] = maps:to_list(Procs),\n\t\t\t%% Confirm other metadata are as expected.\n\t\t\t#{\n\t\t\t\tref := _,\n\t\t\t\tpid := From,\n\t\t\t\tstreamid := StreamID,\n\t\t\t\treason := switch_protocol,\n\t\t\t\treq := #{},\n\t\t\t\t%% A 101 upgrade response was sent.\n\t\t\t\tinformational := [#{\n\t\t\t\t\tstatus := 101,\n\t\t\t\t\theaders := #{\n\t\t\t\t\t\t<<\"connection\">> := <<\"Upgrade\">>,\n\t\t\t\t\t\t<<\"upgrade\">> := <<\"websocket\">>,\n\t\t\t\t\t\t<<\"sec-websocket-accept\">> := _\n\t\t\t\t\t},\n\t\t\t\t\ttime := _\n\t\t\t\t}],\n\t\t\t\tuser_data := #{}\n\t\t\t} = Metrics,\n\t\t\tdo_check_streamid(StreamID, Config),\n\t\t\t%% All good!\n\t\t\tok\n\tend,\n\t%% And of course the upgrade completed successfully after that.\n\treceive\n\t\t{gun_upgrade, ConnPid, StreamRef, _, _} ->\n\t\t\tok\n\tend,\n\tgun:close(ConnPid).\n\nerror_response(Config) ->\n\tdoc(\"Confirm metrics are correct when an error_response command is returned.\"),\n\t%% Perform a GET request.\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/crash/no_reply\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\tProtocol = config(protocol, Config),\n\tRespHeaders = case gun:await(ConnPid, Ref, infinity) of\n\t\t{response, fin, 500, RespHeaders0} ->\n\t\t\tRespHeaders0;\n\t\t%% The RST_STREAM arrived before the start of the response.\n\t\t%% See maybe_h3_error comment for details.\n\t\t{error, {stream_error, {stream_error, h3_internal_error, _}}} when Protocol =:= http3 ->\n\t\t\tunknown\n\tend,\n\ttimer:sleep(100),\n\t%% Receive the metrics and validate them.\n\treceive\n\t\t{metrics, From, Metrics} ->\n\t\t\t%% Ensure the timestamps are in the expected order.\n\t\t\t#{\n\t\t\t\treq_start := ReqStart, req_end := ReqEnd,\n\t\t\t\tresp_start := RespStart, resp_end := RespEnd\n\t\t\t} = Metrics,\n\t\t\ttrue = (ReqStart =< RespStart)\n\t\t\t\tand (RespStart =< RespEnd)\n\t\t\t\tand (RespEnd =< ReqEnd),\n\t\t\t%% We didn't send a body.\n\t\t\t#{\n\t\t\t\treq_body_start := undefined,\n\t\t\t\treq_body_end := undefined,\n\t\t\t\treq_body_length := 0\n\t\t\t} = Metrics,\n\t\t\t%% We got a 500 response without a body.\n\t\t\t#{\n\t\t\t\tresp_status := 500,\n\t\t\t\tresp_headers := ExpectedRespHeaders,\n\t\t\t\tresp_body_length := 0\n\t\t\t} = Metrics,\n\t\t\tcase RespHeaders of\n\t\t\t\t%% The HTTP/3 stream has reset too early so we can't\n\t\t\t\t%% verify the response headers.\n\t\t\t\tunknown ->\n\t\t\t\t\tok;\n\t\t\t\t_ ->\n\t\t\t\t\tExpectedRespHeaders = maps:from_list(RespHeaders)\n\t\t\tend,\n\t\t\t%% The request process executed normally.\n\t\t\t#{procs := Procs} = Metrics,\n\t\t\t[{_, #{\n\t\t\t\tspawn := ProcSpawn,\n\t\t\t\texit := ProcExit,\n\t\t\t\treason := {crash, StackTrace}\n\t\t\t}}] = maps:to_list(Procs),\n\t\t\ttrue = ProcSpawn =< ProcExit,\n\t\t\t%% Confirm other metadata are as expected.\n\t\t\t#{\n\t\t\t\tref := _,\n\t\t\t\tpid := From,\n\t\t\t\tstreamid := StreamID,\n\t\t\t\treason := {internal_error, {'EXIT', _Pid, {crash, StackTrace}}, 'Stream process crashed.'},\n\t\t\t\treq := #{},\n\t\t\t\tinformational := [],\n\t\t\t\tuser_data := #{}\n\t\t\t} = Metrics,\n\t\t\tdo_check_streamid(StreamID, Config),\n\t\t\t%% All good!\n\t\t\tgun:close(ConnPid)\n\tend.\n\nerror_response_after_reply(Config) ->\n\tdoc(\"Confirm metrics are correct when an error_response command is returned \"\n\t\t\"after a response was sent.\"),\n\t%% Perform a GET request.\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/crash/reply\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\tProtocol = config(protocol, Config),\n\tRespHeaders = case gun:await(ConnPid, Ref, infinity) of\n\t\t{response, fin, 200, RespHeaders0} ->\n\t\t\tRespHeaders0;\n\t\t%% The RST_STREAM arrived before the start of the response.\n\t\t%% See maybe_h3_error comment for details.\n\t\t{error, {stream_error, {stream_error, h3_internal_error, _}}} when Protocol =:= http3 ->\n\t\t\tunknown\n\tend,\n\ttimer:sleep(100),\n\t%% Receive the metrics and validate them.\n\treceive\n\t\t{metrics, From, Metrics} ->\n\t\t\t%% Ensure the timestamps are in the expected order.\n\t\t\t#{\n\t\t\t\treq_start := ReqStart, req_end := ReqEnd,\n\t\t\t\tresp_start := RespStart, resp_end := RespEnd\n\t\t\t} = Metrics,\n\t\t\ttrue = (ReqStart =< RespStart)\n\t\t\t\tand (RespStart =< RespEnd)\n\t\t\t\tand (RespEnd =< ReqEnd),\n\t\t\t%% We didn't send a body.\n\t\t\t#{\n\t\t\t\treq_body_start := undefined,\n\t\t\t\treq_body_end := undefined,\n\t\t\t\treq_body_length := 0\n\t\t\t} = Metrics,\n\t\t\t%% We got a 200 response without a body.\n\t\t\t#{\n\t\t\t\tresp_status := 200,\n\t\t\t\tresp_headers := ExpectedRespHeaders,\n\t\t\t\tresp_body_length := 0\n\t\t\t} = Metrics,\n\t\t\tcase RespHeaders of\n\t\t\t\t%% The HTTP/3 stream has reset too early so we can't\n\t\t\t\t%% verify the response headers.\n\t\t\t\tunknown ->\n\t\t\t\t\tok;\n\t\t\t\t_ ->\n\t\t\t\t\tExpectedRespHeaders = maps:from_list(RespHeaders)\n\t\t\tend,\n\t\t\t%% The request process executed normally.\n\t\t\t#{procs := Procs} = Metrics,\n\t\t\t[{_, #{\n\t\t\t\tspawn := ProcSpawn,\n\t\t\t\texit := ProcExit,\n\t\t\t\treason := {crash, StackTrace}\n\t\t\t}}] = maps:to_list(Procs),\n\t\t\ttrue = ProcSpawn =< ProcExit,\n\t\t\t%% Confirm other metadata are as expected.\n\t\t\t#{\n\t\t\t\tref := _,\n\t\t\t\tpid := From,\n\t\t\t\tstreamid := StreamID,\n\t\t\t\treason := {internal_error, {'EXIT', _Pid, {crash, StackTrace}}, 'Stream process crashed.'},\n\t\t\t\treq := #{},\n\t\t\t\tinformational := [],\n\t\t\t\tuser_data := #{}\n\t\t\t} = Metrics,\n\t\t\tdo_check_streamid(StreamID, Config),\n\t\t\t%% All good!\n\t\t\tgun:close(ConnPid)\n\tend.\n"
  },
  {
    "path": "test/misc_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(misc_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\nall() ->\n\t[{group, app}, {group, env}|cowboy_test:common_all()].\n\ngroups() ->\n\tCommon = ct_helper:all(?MODULE)\n\t\t-- [restart_gracefully, get_env, set_env, set_env_missing],\n\t[\n\t\t{app, [], [restart_gracefully]},\n\t\t{env, [parallel], [get_env, set_env, set_env_missing]}\n\t|cowboy_test:common_groups(Common)].\n\ninit_per_group(Name=app, Config) ->\n\tcowboy_test:init_http(Name, #{\n\t\tenv => #{dispatch => init_dispatch(Config)}\n\t}, Config);\ninit_per_group(env, Config) ->\n\tConfig;\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(env, _) ->\n\tok;\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{\"localhost\", [\n\t\t{\"/\", hello_h, []}\n\t]}]).\n\n%% Logger function silencing the expected crash.\n\nerror(\"Ranch listener \" ++ _, [set_env_missing|_]) ->\n\tok;\nerror(Format, Args) ->\n\terror_logger:error_msg(Format, Args).\n\n%% Tests.\n\nrestart_gracefully(Config) ->\n\tdoc(\"Ensure we can process request when the cowboy application is being restarted.\"),\n\tConnPid = gun_open(Config),\n\t%% We can do a request before stopping cowboy.\n\tRef1 = gun:get(ConnPid, \"/\"),\n\t{response, _, 200, _} = gun:await(ConnPid, Ref1),\n\t%% Stop the cowboy application.\n\tok = application:stop(cowboy),\n\t%% We can still do a request even though cowboy is stopped.\n\tRef2 = gun:get(ConnPid, \"/\"),\n\t{response, _, 200, _} = gun:await(ConnPid, Ref2),\n\t%% Start the cowboy application again.\n\tok = application:start(cowboy),\n\t%% Even after restarting there are no issues.\n\tRef3 = gun:get(ConnPid, \"/\"),\n\t{response, _, 200, _} = gun:await(ConnPid, Ref3),\n\tok.\n\nrouter_invalid_path(Config) ->\n\tdoc(\"Ensure a path with invalid percent-encoded characters results in a 400.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/version/path/%\\\\u0016\\\\u0016/path\"),\n\t{response, _, 400, _} = gun:await(ConnPid, Ref),\n\tok.\n\nget_env(Config0) ->\n\tdoc(\"Ensure we can retrieve middleware environment values.\"),\n\tDispatch = init_dispatch(Config0),\n\t_Config = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{\n\t\t\tdispatch => Dispatch,\n\t\t\tthe_key => the_value\n\t\t}\n\t}, Config0),\n\ttry\n\t\tDispatch = cowboy:get_env(?FUNCTION_NAME, dispatch),\n\t\tDispatch = cowboy:get_env(?FUNCTION_NAME, dispatch, the_default),\n\t\tthe_value = cowboy:get_env(?FUNCTION_NAME, the_key),\n\t\tthe_value = cowboy:get_env(?FUNCTION_NAME, the_key, the_default),\n\t\t{'EXIT', _} = (catch cowboy:get_env(?FUNCTION_NAME, missing_key)),\n\t\tthe_default = cowboy:get_env(?FUNCTION_NAME, missing_key, the_default)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_env(Config0) ->\n\tdoc(\"Live replace a middleware environment value.\"),\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => []}\n\t}, Config0),\n\ttry\n\t\tConnPid1 = gun_open(Config),\n\t\tRef1 = gun:get(ConnPid1, \"/\"),\n\t\t{response, _, 400, _} = gun:await(ConnPid1, Ref1),\n\t\tcowboy:set_env(?FUNCTION_NAME, dispatch, init_dispatch(Config)),\n\t\t%% Only new connections get the updated environment.\n\t\tConnPid2 = gun_open(Config),\n\t\tRef2 = gun:get(ConnPid2, \"/\"),\n\t\t{response, _, 200, _} = gun:await(ConnPid2, Ref2)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nset_env_missing(Config0) ->\n\tdoc(\"Live replace a middleware environment value when env was not provided.\"),\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tlogger => ?MODULE\n\t}, Config0),\n\ttry\n\t\tConnPid1 = gun_open(Config),\n\t\tRef1 = gun:get(ConnPid1, \"/\"),\n\t\t{response, _, 500, _} = gun:await(ConnPid1, Ref1),\n\t\tcowboy:set_env(?FUNCTION_NAME, dispatch, []),\n\t\t%% Only new connections get the updated environment.\n\t\tConnPid2 = gun_open(Config),\n\t\tRef2 = gun:get(ConnPid2, \"/\"),\n\t\t{response, _, 400, _} = gun:await(ConnPid2, Ref2)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n"
  },
  {
    "path": "test/plain_handler_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(plain_handler_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\n%% ct.\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_suite(Config) ->\n\tct_helper:create_static_dir(config(priv_dir, Config) ++ \"/static\"),\n\tConfig.\n\nend_per_suite(Config) ->\n\tct_helper:delete_static_dir(config(priv_dir, Config) ++ \"/static\").\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\n%% Routes.\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{\"localhost\", [\n\t\t{\"/crash/external_exit\", crash_h, external_exit},\n\t\t{\"/crash/no_reply\", crash_h, no_reply},\n\t\t{\"/crash/reply\", crash_h, reply}\n\t]}]).\n\n%% Tests.\n\ncrash_after_reply(Config) ->\n\tdoc(\"A plain handler crash after a response was sent \"\n\t\t\"results in no 500 response.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/crash/reply\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\tProtocol = config(protocol, Config),\n\t_ = case gun:await(ConnPid, Ref) of\n\t\t{response, fin, 200, _} ->\n\t\t\t{error, timeout} = gun:await(ConnPid, Ref, 1000);\n\t\t%% See maybe_h3_error comment for details.\n\t\t{error, {stream_error, {stream_error, h3_internal_error, _}}}\n\t\t\t\twhen Protocol =:= http3 ->\n\t\t\tok\n\tend,\n\tgun:close(ConnPid).\n\ncrash_before_reply(Config) ->\n\tdoc(\"A plain handler crash before a response was sent \"\n\t\t\"results in a 500 response.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/crash/no_reply\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, fin, 500, _} = gun:await(ConnPid, Ref),\n\tgun:close(ConnPid).\n\nexternal_exit_before_reply(Config) ->\n\tdoc(\"A plain handler exits externally before a response was sent \"\n\t\t\"results in a 500 response.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/crash/external_exit\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, fin, 500, _} = gun:await(ConnPid, Ref),\n\tgun:close(ConnPid).\n"
  },
  {
    "path": "test/proxy_header_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(proxy_header_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [raw_send/2]).\n-import(cowboy_test, [raw_recv_head/1]).\n-import(cowboy_test, [raw_recv/3]).\n\n%% ct.\n\nall() ->\n\t[\n\t\t{group, http},\n\t\t{group, https},\n\t\t{group, h2},\n\t\t{group, h2c},\n\t\t{group, h2c_upgrade}\n\t].\n\ngroups() ->\n\tTests = ct_helper:all(?MODULE),\n\t[{h2c_upgrade, [parallel], Tests}|cowboy_test:common_groups(Tests)].\n\ninit_per_group(Name=http, Config) ->\n\tcowboy_test:init_http(Name, #{\n\t\tenv => #{dispatch => init_dispatch()},\n\t\tproxy_header => true\n\t}, Config);\ninit_per_group(Name=https, Config) ->\n\tcowboy_test:init_https(Name, #{\n\t\tenv => #{dispatch => init_dispatch()},\n\t\tproxy_header => true\n\t}, Config);\ninit_per_group(Name=h2, Config) ->\n\tcowboy_test:init_http2(Name, #{\n\t\tenv => #{dispatch => init_dispatch()},\n\t\tproxy_header => true\n\t}, Config);\ninit_per_group(Name, Config) ->\n\tConfig1 = cowboy_test:init_http(Name, #{\n\t\tenv => #{dispatch => init_dispatch()},\n\t\tproxy_header => true\n\t}, Config),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2}).\n\nend_per_group(Name, _) ->\n\tcowboy:stop_listener(Name).\n\n%% Routes.\n\ninit_dispatch() ->\n\tcowboy_router:compile([{\"[...]\", [\n\t\t{\"/direct/:key/[...]\", echo_h, []}\n\t]}]).\n\n%% Tests.\n\nfail_gracefully_on_disconnect(Config) ->\n\tdoc(\"Probing a port must not generate a crash\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}, {packet, raw}]),\n\ttimer:sleep(50),\n\tPid = case config(type, Config) of\n\t\ttcp -> ct_helper:get_remote_pid_tcp(Socket);\n\t\t%% We connect to a TLS port using a TCP socket so we need\n\t\t%% to first obtain the remote pid of the TCP socket, which\n\t\t%% is a TLS socket on the server, and then get the real\n\t\t%% remote pid from its state.\n\t\tssl -> ct_helper:get_remote_pid_tls_state(ct_helper:get_remote_pid_tcp(Socket))\n\tend,\n\tRef = erlang:monitor(process, Pid),\n\tgen_tcp:close(Socket),\n\treceive\n\t\t{'DOWN', Ref, process, Pid, {shutdown, closed}} ->\n\t\t\tok;\n\t\t{'DOWN', Ref, process, Pid, Reason} ->\n\t\t\terror(Reason)\n\tafter 500 ->\n\t\terror(timeout)\n\tend.\n\nv1_proxy_header(Config) ->\n\tdoc(\"Confirm we can read the proxy header at the start of the connection.\"),\n\tProxyInfo = #{\n\t\tversion => 1,\n\t\tcommand => proxy,\n\t\ttransport_family => ipv4,\n\t\ttransport_protocol => stream,\n\t\tsrc_address => {127, 0, 0, 1},\n\t\tsrc_port => 444,\n\t\tdest_address => {192, 168, 0, 1},\n\t\tdest_port => 443\n\t},\n\tdo_proxy_header(Config, ProxyInfo).\n\nv2_proxy_header(Config) ->\n\tdoc(\"Confirm we can read the proxy header at the start of the connection.\"),\n\tProxyInfo = #{\n\t\tversion => 2,\n\t\tcommand => proxy,\n\t\ttransport_family => ipv4,\n\t\ttransport_protocol => stream,\n\t\tsrc_address => {127, 0, 0, 1},\n\t\tsrc_port => 444,\n\t\tdest_address => {192, 168, 0, 1},\n\t\tdest_port => 443\n\t},\n\tdo_proxy_header(Config, ProxyInfo).\n\nv2_local_header(Config) ->\n\tdoc(\"Confirm we can read the proxy header at the start of the connection.\"),\n\tProxyInfo = #{\n\t\tversion => 2,\n\t\tcommand => local\n\t},\n\tdo_proxy_header(Config, ProxyInfo).\n\ndo_proxy_header(Config, ProxyInfo) ->\n\tcase config(ref, Config) of\n\t\thttp -> do_proxy_header_http(Config, ProxyInfo);\n\t\thttps -> do_proxy_header_https(Config, ProxyInfo);\n\t\th2 -> do_proxy_header_h2(Config, ProxyInfo);\n\t\th2c -> do_proxy_header_h2c(Config, ProxyInfo);\n\t\th2c_upgrade -> do_proxy_header_h2c_upgrade(Config, ProxyInfo)\n\tend.\n\ndo_proxy_header_http(Config, ProxyInfo) ->\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}, {packet, raw}]),\n\tok = gen_tcp:send(Socket, ranch_proxy_header:header(ProxyInfo)),\n\tdo_proxy_header_http_common({raw_client, Socket, gen_tcp}, ProxyInfo).\n\ndo_proxy_header_https(Config, ProxyInfo) ->\n\t{ok, Socket0} = gen_tcp:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}, {packet, raw}]),\n\tok = gen_tcp:send(Socket0, ranch_proxy_header:header(ProxyInfo)),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(Socket0, TlsOpts, 1000),\n\tdo_proxy_header_http_common({raw_client, Socket, ssl}, ProxyInfo).\n\ndo_proxy_header_http_common(Client, ProxyInfo) ->\n\tok = raw_send(Client,\n\t\t\"GET /direct/proxy_header HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{_, 200, _, Rest0} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{Headers, Body0} = cow_http:parse_headers(Rest0),\n\t{_, LenBin} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tLen = binary_to_integer(LenBin),\n\tBody = if\n\t\tbyte_size(Body0) =:= Len -> Body0;\n\t\ttrue ->\n\t\t\t{ok, Body1} = raw_recv(Client, Len - byte_size(Body0), 5000),\n\t\t\t<<Body0/bits, Body1/bits>>\n\tend,\n\tProxyInfo = do_parse_term(Body),\n\tok.\n\ndo_proxy_header_h2(Config, ProxyInfo) ->\n\t{ok, Socket0} = gen_tcp:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}, {packet, raw}]),\n\tok = gen_tcp:send(Socket0, ranch_proxy_header:header(ProxyInfo)),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(Socket0,\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]}|TlsOpts], 1000),\n\tdo_proxy_header_h2_common({raw_client, Socket, ssl}, ProxyInfo).\n\ndo_proxy_header_h2c(Config, ProxyInfo) ->\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}, {packet, raw}]),\n\tok = gen_tcp:send(Socket, ranch_proxy_header:header(ProxyInfo)),\n\tdo_proxy_header_h2_common({raw_client, Socket, gen_tcp}, ProxyInfo).\n\ndo_proxy_header_h2c_upgrade(Config, ProxyInfo) ->\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}, {packet, raw}]),\n\tok = gen_tcp:send(Socket, ranch_proxy_header:header(ProxyInfo)),\n\tClient = {raw_client, Socket, gen_tcp},\n\tok = raw_send(Client, [\n\t\t\"GET /direct/proxy_header HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(iolist_to_binary(cow_http2:settings_payload(#{}))), \"\\r\\n\"\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Client),\n\t%% Receive the server preface.\n\t{ok, <<PrefaceLen:24>>} = raw_recv(Client, 3, 1000),\n\t{ok, <<4:8, 0:40, _:PrefaceLen/binary>>} = raw_recv(Client, 6 + PrefaceLen, 1000),\n\tdo_proxy_header_h2_response_common(Client, ProxyInfo),\n\tok.\n\ndo_proxy_header_h2_common(Client, ProxyInfo) ->\n\t%% Send a valid preface.\n\tok = raw_send(Client, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, <<PrefaceLen:24>>} = raw_recv(Client, 3, 1000),\n\t{ok, <<4:8, 0:40, _:PrefaceLen/binary>>} = raw_recv(Client, 6 + PrefaceLen, 1000),\n\t%% Send the SETTINGS ack.\n\tok = raw_send(Client, cow_http2:settings_ack()),\n\t%% Receive the SETTINGS ack.\n\t{ok, <<0:24, 4:8, 1:8, 0:32>>} = raw_recv(Client, 9, 1000),\n\t%% Send a GET request.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/direct/proxy_header\">>}\n\t]),\n\tLen = iolist_size(HeadersBlock),\n\tok = raw_send(Client, [\n\t\t<<Len:24, 1:8,\n\t\t\t0:2, %% Undefined.\n\t\t\t0:1, %% PRIORITY.\n\t\t\t0:1, %% Undefined.\n\t\t\t0:1, %% PADDED.\n\t\t\t1:1, %% END_HEADERS.\n\t\t\t0:1, %% Undefined.\n\t\t\t1:1, %% END_STREAM.\n\t\t\t0:1, 1:31>>,\n\t\tHeadersBlock\n\t]),\n\tdo_proxy_header_h2_response_common(Client, ProxyInfo).\n\ndo_proxy_header_h2_response_common(Client, ProxyInfo) ->\n\t%% Receive a response with the proxy header data.\n\t{ok, <<SkipLen:24, 1:8, _:8, 1:32>>} = raw_recv(Client, 9, 1000),\n\t{ok, _} = raw_recv(Client, SkipLen, 1000),\n\t{ok, <<BodyLen:24, 0:8, 1:8, 1:32>>} = raw_recv(Client, 9, 1000),\n\t{ok, Body} = raw_recv(Client, BodyLen, 1000),\n\tProxyInfo = do_parse_term(Body),\n\tok.\n\ndo_parse_term(Body) ->\n\t{ok, Tokens, _} = erl_scan:string(binary_to_list(Body) ++ \".\"),\n\t{ok, Exprs} = erl_parse:parse_exprs(Tokens),\n\t{value, Term, _} = erl_eval:exprs(Exprs, erl_eval:new_bindings()),\n\tTerm.\n\n%% Match directly for now.\ndo_recv_101(Client) ->\n\t{ok, <<\n\t\t\"HTTP/1.1 101 Switching Protocols\\r\\n\"\n\t\t\"connection: Upgrade\\r\\n\"\n\t\t\"upgrade: h2c\\r\\n\"\n\t\t\"\\r\\n\"\n\t>>} = raw_recv(Client, 71, 1000),\n\tok.\n"
  },
  {
    "path": "test/req_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(req_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\n%% ct.\n\nsuite() ->\n\tTimeout = case os:type() of\n\t\t{win32, _} -> 120000;\n\t\t_ -> 30000\n\tend,\n\t[{timetrap, Timeout}].\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_suite(Config) ->\n\tct_helper:create_static_dir(config(priv_dir, Config) ++ \"/static\"),\n\tConfig.\n\nend_per_suite(Config) ->\n\tct_helper:delete_static_dir(config(priv_dir, Config) ++ \"/static\").\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\n%% Routes.\n\ninit_dispatch(Config) ->\n\tcowboy_router:compile([{\"[...]\", [\n\t\t{\"/static/[...]\", cowboy_static, {dir, config(priv_dir, Config) ++ \"/static\"}},\n\t\t%% @todo Seriously InitialState should be optional.\n\t\t{\"/resp/:key[/:arg]\", resp_h, []},\n\t\t{\"/multipart[/:key]\", multipart_h, []},\n\t\t{\"/args/:key/:arg[/:default]\", echo_h, []},\n\t\t{\"/crash/:key/period\", echo_h,\n\t\t\t#{length => 999999999, period => 1000, timeout => 5000, crash => true}},\n\t\t{\"/no-opts/:key\", echo_h, #{crash => true}},\n\t\t{\"/opts/:key/length\", echo_h, #{length => 1000}},\n\t\t{\"/opts/:key/period\", echo_h, #{length => 999999999, period => 2000}},\n\t\t{\"/opts/:key/timeout\", echo_h, #{timeout => 1000, crash => true}},\n\t\t{\"/100-continue/:key\", echo_h, []},\n\t\t{\"/full/:key\", echo_h, []},\n\t\t{\"/auto-sync/:key\", echo_h, []},\n\t\t{\"/auto-async/:key\", echo_h, []},\n\t\t{\"/spawn/:key\", echo_h, []},\n\t\t{\"/no/:key\", echo_h, []},\n\t\t{\"/direct/:key/[...]\", echo_h, []},\n\t\t{\"/:key/[...]\", echo_h, []}\n\t]}]).\n\n%% Internal.\n\ndo_body(Method, Path, Config) ->\n\tdo_body(Method, Path, [], Config).\n\ndo_body(Method, Path, Headers, Config) ->\n\tdo_body(Method, Path, Headers, <<>>, Config).\n\ndo_body(Method, Path, Headers0, Body, Config) ->\n\tConnPid = gun_open(Config),\n\tHeaders = [{<<\"accept-encoding\">>, <<\"gzip\">>}|Headers0],\n\tRef = gun:request(ConnPid, Method, Path, Headers, Body),\n\t{response, IsFin, 200, RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\t{ok, RespBody} = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, Ref, infinity);\n\t\tfin -> {ok, <<>>}\n\tend,\n\tgun:close(ConnPid),\n\tdo_decode(RespHeaders, RespBody).\n\ndo_body_error(Method, Path, Headers0, Body, Config) ->\n\tConnPid = gun_open(Config),\n\tHeaders = [{<<\"accept-encoding\">>, <<\"gzip\">>}|Headers0],\n\tRef = gun:request(ConnPid, Method, Path, Headers, Body),\n\t{response, _, Status, RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\tgun:close(ConnPid),\n\t{Status, RespHeaders}.\n\ndo_get(Path, Config) ->\n\tdo_get(Path, [], Config).\n\ndo_get(Path, Headers, Config) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, Path, [{<<\"accept-encoding\">>, <<\"gzip\">>}|Headers]),\n\tcase gun:await(ConnPid, Ref, infinity) of\n\t\t{response, IsFin, Status, RespHeaders} ->\n\t\t\t{ok, RespBody} = case IsFin of\n\t\t\t\tnofin -> gun:await_body(ConnPid, Ref, infinity);\n\t\t\t\tfin -> {ok, <<>>}\n\t\t\tend,\n\t\t\tgun:close(ConnPid),\n\t\t\t{Status, RespHeaders, do_decode(RespHeaders, RespBody)};\n\t\t{error, {stream_error, Error}} ->\n\t\t\tError\n\tend.\n\ndo_get_body(Path, Config) ->\n\tdo_get_body(Path, [], Config).\n\ndo_get_body(Path, Headers, Config) ->\n\tdo_body(\"GET\", Path, Headers, Config).\n\ndo_get_inform(Path, Config) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, Path, [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\tcase gun:await(ConnPid, Ref, infinity) of\n\t\t{response, _, RespStatus, RespHeaders} ->\n\t\t\t%% We don't care about the body.\n\t\t\tgun:close(ConnPid),\n\t\t\t{RespStatus, RespHeaders};\n\t\t{inform, InfoStatus, InfoHeaders} ->\n\t\t\t{response, IsFin, RespStatus, RespHeaders}\n\t\t\t\t= case gun:await(ConnPid, Ref, infinity) of\n\t\t\t\t\t{inform, InfoStatus, InfoHeaders} ->\n\t\t\t\t\t\tgun:await(ConnPid, Ref, infinity);\n\t\t\t\t\tResponse ->\n\t\t\t\t\t\tResponse\n\t\t\tend,\n\t\t\t{ok, RespBody} = case IsFin of\n\t\t\t\tnofin -> gun:await_body(ConnPid, Ref, infinity);\n\t\t\t\tfin -> {ok, <<>>}\n\t\t\tend,\n\t\t\tgun:close(ConnPid),\n\t\t\t{InfoStatus, InfoHeaders, RespStatus, RespHeaders, do_decode(RespHeaders, RespBody)};\n\t\t{error, {stream_error, Error}} ->\n\t\t\tError\n\tend.\n\ndo_decode(Headers, Body) ->\n\tcase lists:keyfind(<<\"content-encoding\">>, 1, Headers) of\n\t\t{_, <<\"gzip\">>} -> zlib:gunzip(Body);\n\t\t_ -> Body\n\tend.\n\ndo_get_error(Path, Config) ->\n\tdo_get_error(Path, [], Config).\n\ndo_get_error(Path, Headers, Config) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, Path, [{<<\"accept-encoding\">>, <<\"gzip\">>}|Headers]),\n\t{response, IsFin, Status, RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\tResult = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, Ref, infinity);\n\t\tfin -> {ok, <<>>}\n\tend,\n\tcase Result of\n\t\t{ok, RespBody} -> {Status, RespHeaders, do_decode(RespHeaders, RespBody)};\n\t\t_ -> Result\n\tend.\n\n%% Tests: Request.\n\nbinding(Config) ->\n\tdoc(\"Value bound from request URI path with/without default.\"),\n\t<<\"binding\">> = do_get_body(\"/args/binding/key\", Config),\n\t<<\"binding\">> = do_get_body(\"/args/binding/key/default\", Config),\n\t<<\"default\">> = do_get_body(\"/args/binding/undefined/default\", Config),\n\tok.\n\nbindings(Config) ->\n\tdoc(\"Values bound from request URI path.\"),\n\t<<\"#{key => <<\\\"bindings\\\">>}\">> = do_get_body(\"/bindings\", Config),\n\tok.\n\ncert(Config) ->\n\tcase config(type, Config) of\n\t\ttcp -> doc(\"TLS certificates can only be provided over TLS.\");\n\t\tssl -> do_cert(Config);\n\t\tquic -> do_cert(Config)\n\tend.\n\ndo_cert(Config) ->\n\tdoc(\"A client TLS certificate was provided.\"),\n\tCert = do_get_body(\"/cert\", Config),\n\tCert = do_get_body(\"/direct/cert\", Config),\n\tok.\n\ncert_undefined(Config) ->\n\tdoc(\"No client TLS certificate was provided.\"),\n\t<<\"undefined\">> = do_get_body(\"/cert\", [{no_cert, true}|Config]),\n\t<<\"undefined\">> = do_get_body(\"/direct/cert\", [{no_cert, true}|Config]),\n\tok.\n\nheader(Config) ->\n\tdoc(\"Request header with/without default.\"),\n\t<<\"value\">> = do_get_body(\"/args/header/defined\", [{<<\"defined\">>, \"value\"}], Config),\n\t<<\"value\">> = do_get_body(\"/args/header/defined/default\", [{<<\"defined\">>, \"value\"}], Config),\n\t<<\"default\">> = do_get_body(\"/args/header/undefined/default\", [{<<\"defined\">>, \"value\"}], Config),\n\tok.\n\nheaders(Config) ->\n\tdoc(\"Request headers.\"),\n\tdo_headers(\"/headers\", Config),\n\tdo_headers(\"/direct/headers\", Config).\n\ndo_headers(Path, Config) ->\n\t%% We always send accept-encoding with this test suite's requests.\n\t<<\"#{<<\\\"accept-encoding\\\">> => <<\\\"gzip\\\">>,\"\n\t\t\"<<\\\"content-length\\\">> => <<\\\"0\\\">>,\"\n\t\t\"<<\\\"header\\\">> => <<\\\"value\\\">>\", _/bits>>\n\t\t= do_get_body(Path, [{<<\"header\">>, \"value\"}], Config),\n\tok.\n\nhost(Config) ->\n\tdoc(\"Request URI host.\"),\n\t<<\"localhost\">> = do_get_body(\"/host\", Config),\n\t<<\"localhost\">> = do_get_body(\"/direct/host\", Config),\n\tok.\n\nhost_info(Config) ->\n\tdoc(\"Request host_info.\"),\n\t<<\"[<<\\\"localhost\\\">>]\">> = do_get_body(\"/host_info\", Config),\n\tok.\n\n%% @todo Actually write the related unit tests.\nmatch_cookies(Config) ->\n\tdoc(\"Matched request cookies.\"),\n\t<<\"#{}\">> = do_get_body(\"/match/cookies\", [{<<\"cookie\">>, \"a=b; c=d\"}], Config),\n\t<<\"#{a => <<\\\"b\\\">>}\">> = do_get_body(\"/match/cookies/a\", [{<<\"cookie\">>, \"a=b; c=d\"}], Config),\n\t<<\"#{c => <<\\\"d\\\">>}\">> = do_get_body(\"/match/cookies/c\", [{<<\"cookie\">>, \"a=b; c=d\"}], Config),\n\tcase do_get_body(\"/match/cookies/a/c\", [{<<\"cookie\">>, \"a=b; c=d\"}], Config) of\n\t\t<<\"#{a => <<\\\"b\\\">>,c => <<\\\"d\\\">>}\">> -> ok;\n\t\t<<\"#{c => <<\\\"d\\\">>,a => <<\\\"b\\\">>}\">> -> ok\n\tend,\n\t%% Ensure match errors result in a 400 response.\n\t{400, _, _} = do_get(\"/match/cookies/a/c\",\n\t\t[{<<\"cookie\">>, \"a=b\"}], Config),\n\t%% This function is tested more extensively through unit tests.\n\tok.\n\n%% @todo Actually write the related unit tests.\nmatch_qs(Config) ->\n\tdoc(\"Matched request URI query string.\"),\n\t<<\"#{}\">> = do_get_body(\"/match/qs?a=b&c=d\", Config),\n\t<<\"#{a => <<\\\"b\\\">>}\">> = do_get_body(\"/match/qs/a?a=b&c=d\", Config),\n\t<<\"#{c => <<\\\"d\\\">>}\">> = do_get_body(\"/match/qs/c?a=b&c=d\", Config),\n\tcase do_get_body(\"/match/qs/a/c?a=b&c=d\", Config) of\n\t\t<<\"#{a => <<\\\"b\\\">>,c => <<\\\"d\\\">>}\">> -> ok;\n\t\t<<\"#{c => <<\\\"d\\\">>,a => <<\\\"b\\\">>}\">> -> ok\n\tend,\n\tcase do_get_body(\"/match/qs/a/c?a=b&c\", Config) of\n\t\t<<\"#{a => <<\\\"b\\\">>,c => true}\">> -> ok;\n\t\t<<\"#{c => true,a => <<\\\"b\\\">>}\">> -> ok\n\tend,\n\tcase do_get_body(\"/match/qs/a/c?a&c=d\", Config) of\n\t\t<<\"#{a => true,c => <<\\\"d\\\">>}\">> -> ok;\n\t\t<<\"#{c => <<\\\"d\\\">>,a => true}\">> -> ok\n\tend,\n\t%% Ensure match errors result in a 400 response.\n\t{400, _, _} = do_get(\"/match/qs/a/c?a=b\", [], Config),\n\t{400, _, _} = do_get(\"/match/qs_with_constraints\", [], Config),\n\t%% This function is tested more extensively through unit tests.\n\tok.\n\nmethod(Config) ->\n\tdoc(\"Request method.\"),\n\tdo_method(\"/method\", Config),\n\tdo_method(\"/direct/method\", Config).\n\ndo_method(Path, Config) ->\n\t<<\"GET\">> = do_body(\"GET\", Path, Config),\n\t<<>> = do_body(\"HEAD\", Path, Config),\n\t<<\"OPTIONS\">> = do_body(\"OPTIONS\", Path, Config),\n\t<<\"PATCH\">> = do_body(\"PATCH\", Path, Config),\n\t<<\"POST\">> = do_body(\"POST\", Path, Config),\n\t<<\"PUT\">> = do_body(\"PUT\", Path, Config),\n\t<<\"ZZZZZZZZ\">> = do_body(\"ZZZZZZZZ\", Path, Config),\n\tok.\n\nparse_cookies(Config) ->\n\tdoc(\"Request cookies.\"),\n\t<<\"[]\">> = do_get_body(\"/parse_cookies\", Config),\n\t<<\"[{<<\\\"cake\\\">>,<<\\\"strawberry\\\">>}]\">>\n\t\t= do_get_body(\"/parse_cookies\", [{<<\"cookie\">>, \"cake=strawberry\"}], Config),\n\t<<\"[{<<\\\"cake\\\">>,<<\\\"strawberry\\\">>},{<<\\\"color\\\">>,<<\\\"blue\\\">>}]\">>\n\t\t= do_get_body(\"/parse_cookies\", [{<<\"cookie\">>, \"cake=strawberry; color=blue\"}], Config),\n\t<<\"[{<<\\\"cake\\\">>,<<\\\"strawberry\\\">>},{<<\\\"color\\\">>,<<\\\"blue\\\">>}]\">>\n\t\t= do_get_body(\"/parse_cookies\",\n\t\t\t[{<<\"cookie\">>, \"cake=strawberry\"}, {<<\"cookie\">>, \"color=blue\"}], Config),\n\t%% Ensure parse errors result in a 400 response.\n\t{400, _, _} = do_get(\"/parse_cookies\",\n\t\t[{<<\"cookie\">>, \"bad\\tname=strawberry\"}], Config),\n\t{400, _, _} = do_get(\"/parse_cookies\",\n\t\t[{<<\"cookie\">>, \"goodname=strawberry\\tmilkshake\"}], Config),\n\tok.\n\nfilter_then_parse_cookies(Config) ->\n\tdoc(\"Filter cookies then parse them.\"),\n\t<<\"[]\">> = do_get_body(\"/filter_then_parse_cookies\", Config),\n\t<<\"[{<<\\\"cake\\\">>,<<\\\"strawberry\\\">>}]\">>\n\t\t= do_get_body(\"/filter_then_parse_cookies\", [{<<\"cookie\">>, \"cake=strawberry\"}], Config),\n\t<<\"[{<<\\\"cake\\\">>,<<\\\"strawberry\\\">>},{<<\\\"color\\\">>,<<\\\"blue\\\">>}]\">>\n\t\t= do_get_body(\"/filter_then_parse_cookies\", [{<<\"cookie\">>, \"cake=strawberry; color=blue\"}], Config),\n\t<<\"[{<<\\\"cake\\\">>,<<\\\"strawberry\\\">>},{<<\\\"color\\\">>,<<\\\"blue\\\">>}]\">>\n\t\t= do_get_body(\"/filter_then_parse_cookies\",\n\t\t\t[{<<\"cookie\">>, \"cake=strawberry\"}, {<<\"cookie\">>, \"color=blue\"}], Config),\n\t<<\"[]\">>\n\t\t= do_get_body(\"/filter_then_parse_cookies\",\n\t\t\t[{<<\"cookie\">>, \"bad name=strawberry\"}], Config),\n\t<<\"[{<<\\\"cake\\\">>,<<\\\"strawberry\\\">>}]\">>\n\t\t= do_get_body(\"/filter_then_parse_cookies\",\n\t\t\t[{<<\"cookie\">>, \"bad name=strawberry; another bad name=strawberry; cake=strawberry\"}], Config),\n\t<<\"[]\">>\n\t\t= do_get_body(\"/filter_then_parse_cookies\",\n\t\t\t[{<<\"cookie\">>, \"Blocked by http://www.example.com/upgrade-to-remove\"}], Config),\n\tok.\n\nparse_header(Config) ->\n\tdoc(\"Parsed request header with/without default.\"),\n\t<<\"[{{<<\\\"text\\\">>,<<\\\"html\\\">>,[]},1000,[]}]\">>\n\t\t= do_get_body(\"/args/parse_header/accept\", [{<<\"accept\">>, \"text/html\"}], Config),\n\t<<\"[{{<<\\\"text\\\">>,<<\\\"html\\\">>,[]},1000,[]}]\">>\n\t\t= do_get_body(\"/args/parse_header/accept/default\", [{<<\"accept\">>, \"text/html\"}], Config),\n\t%% Header not in request but with default defined by Cowboy.\n\t<<\"0\">> = do_get_body(\"/args/parse_header/content-length\", Config),\n\t%% Header not in request and no default from Cowboy.\n\t<<\"undefined\">> = do_get_body(\"/args/parse_header/upgrade\", Config),\n\t%% Header in request and with default provided.\n\t<<\"100-continue\">> = do_get_body(\"/args/parse_header/expect/100-continue\", Config),\n\t%% Ensure parse errors result in a 400 response.\n\t{400, _, _} = do_get(\"/args/parse_header/accept\",\n\t\t[{<<\"accept\">>, \"bad media type\"}], Config),\n\tok.\n\nparse_qs(Config) ->\n\tdoc(\"Parsed request URI query string.\"),\n\t<<\"[]\">> = do_get_body(\"/parse_qs\", Config),\n\t<<\"[{<<\\\"abc\\\">>,true}]\">> = do_get_body(\"/parse_qs?abc\", Config),\n\t<<\"[{<<\\\"a\\\">>,<<\\\"b\\\">>},{<<\\\"c\\\">>,<<\\\"d e\\\">>}]\">> = do_get_body(\"/parse_qs?a=b&c=d+e\", Config),\n\t%% Ensure parse errors result in a 400 response.\n\t{400, _, _} = do_get(\"/parse_qs?%%%%%%%\", Config),\n\tok.\n\npath(Config) ->\n\tdoc(\"Request URI path.\"),\n\tdo_path(\"/path\", Config),\n\tdo_path(\"/direct/path\", Config).\n\ndo_path(Path0, Config) ->\n\tPath = list_to_binary(Path0 ++ \"/to/the/resource\"),\n\tPath = do_get_body(Path, Config),\n\tPath = do_get_body([Path, \"?query\"], Config),\n\tPath = do_get_body([Path, \"?query#fragment\"], Config),\n\tPath = do_get_body([Path, \"#fragment\"], Config),\n\tok.\n\npath_info(Config) ->\n\tdoc(\"Request path_info.\"),\n\t<<\"undefined\">> = do_get_body(\"/no/path_info\", Config),\n\t<<\"[]\">> = do_get_body(\"/path_info\", Config),\n\t<<\"[]\">> = do_get_body(\"/path_info/\", Config),\n\t<<\"[<<\\\"to\\\">>,<<\\\"the\\\">>,<<\\\"resource\\\">>]\">> = do_get_body(\"/path_info/to/the/resource\", Config),\n\t<<\"[<<\\\"to\\\">>,<<\\\"the\\\">>,<<\\\"resource\\\">>]\">> = do_get_body(\"/path_info/to/the/resource?query\", Config),\n\t<<\"[<<\\\"to\\\">>,<<\\\"the\\\">>,<<\\\"resource\\\">>]\">> = do_get_body(\"/path_info/to/the/resource?query#fragment\", Config),\n\t<<\"[<<\\\"to\\\">>,<<\\\"the\\\">>,<<\\\"resource\\\">>]\">> = do_get_body(\"/path_info/to/the/resource#fragment\", Config),\n\tok.\n\npeer(Config) ->\n\tdoc(\"Remote socket address.\"),\n\t<<\"{{127,0,0,1},\", _/bits >> = do_get_body(\"/peer\", Config),\n\t<<\"{{127,0,0,1},\", _/bits >> = do_get_body(\"/direct/peer\", Config),\n\tok.\n\nport(Config) ->\n\tdoc(\"Request URI port.\"),\n\tPort = integer_to_binary(config(port, Config)),\n\tPort = do_get_body(\"/port\", Config),\n\tPort = do_get_body(\"/direct/port\", Config),\n\tExpectedPort = case config(type, Config) of\n\t\ttcp -> <<\"80\">>;\n\t\tssl -> <<\"443\">>;\n\t\tquic -> <<\"443\">>\n\tend,\n\tExpectedPort = do_get_body(\"/port\", [{<<\"host\">>, <<\"localhost\">>}], Config),\n\tExpectedPort = do_get_body(\"/direct/port\", [{<<\"host\">>, <<\"localhost\">>}], Config),\n\tok.\n\nqs(Config) ->\n\tdoc(\"Request URI query string.\"),\n\tdo_qs(\"/qs\", Config),\n\tdo_qs(\"/direct/qs\", Config).\n\ndo_qs(Path, Config) ->\n\t<<>> = do_get_body(Path, Config),\n\t<<\"abc\">> = do_get_body(Path ++ \"?abc\", Config),\n\t<<\"a=b&c=d+e\">> = do_get_body(Path ++ \"?a=b&c=d+e\", Config),\n\tok.\n\nscheme(Config) ->\n\tdoc(\"Request URI scheme.\"),\n\tdo_scheme(\"/scheme\", Config),\n\tdo_scheme(\"/direct/scheme\", Config).\n\ndo_scheme(Path, Config) ->\n\tTransport = config(type, Config),\n\tcase do_get_body(Path, Config) of\n\t\t<<\"http\">> when Transport =:= tcp -> ok;\n\t\t<<\"https\">> when Transport =:= ssl -> ok;\n\t\t<<\"https\">> when Transport =:= quic -> ok\n\tend.\n\nsock(Config) ->\n\tdoc(\"Local socket address.\"),\n\t<<\"{{127,0,0,1},\", _/bits >> = do_get_body(\"/sock\", Config),\n\t<<\"{{127,0,0,1},\", _/bits >> = do_get_body(\"/direct/sock\", Config),\n\tok.\n\nuri(Config) ->\n\tdoc(\"Request URI building/modification.\"),\n\tScheme = case config(type, Config) of\n\t\ttcp -> <<\"http\">>;\n\t\tssl -> <<\"https\">>;\n\t\tquic -> <<\"https\">>\n\tend,\n\tSLen = byte_size(Scheme),\n\tPort = integer_to_binary(config(port, Config)),\n\tPLen = byte_size(Port),\n\t%% Absolute form.\n\t<< Scheme:SLen/binary, \"://localhost:\", Port:PLen/binary, \"/uri?qs\" >>\n\t\t= do_get_body(\"/uri?qs\", Config),\n\t%% Origin form.\n\t<< \"/uri/origin?qs\" >> = do_get_body(\"/uri/origin?qs\", Config),\n\t%% Protocol relative.\n\t<< \"//localhost:\", Port:PLen/binary, \"/uri/protocol-relative?qs\" >>\n\t\t= do_get_body(\"/uri/protocol-relative?qs\", Config),\n\t%% No query string.\n\t<< Scheme:SLen/binary, \"://localhost:\", Port:PLen/binary, \"/uri/no-qs\" >>\n\t\t= do_get_body(\"/uri/no-qs?qs\", Config),\n\t%% No path or query string.\n\t<< Scheme:SLen/binary, \"://localhost:\", Port:PLen/binary >>\n\t\t= do_get_body(\"/uri/no-path?qs\", Config),\n\t%% Changed port.\n\t<< Scheme:SLen/binary, \"://localhost:123/uri/set-port?qs\" >>\n\t\t= do_get_body(\"/uri/set-port?qs\", Config),\n\t%% This function is tested more extensively through unit tests.\n\tok.\n\nversion(Config) ->\n\tdoc(\"Request HTTP version.\"),\n\tdo_version(\"/version\", Config),\n\tdo_version(\"/direct/version\", Config).\n\ndo_version(Path, Config) ->\n\tProtocol = config(protocol, Config),\n\tcase do_get_body(Path, Config) of\n\t\t<<\"HTTP/1.1\">> when Protocol =:= http -> ok;\n\t\t<<\"HTTP/2\">> when Protocol =:= http2 -> ok;\n\t\t<<\"HTTP/3\">> when Protocol =:= http3 -> ok\n\tend.\n\n%% Tests: Request body.\n\nbody_length(Config) ->\n\tdoc(\"Request body length.\"),\n\t<<\"0\">> = do_get_body(\"/body_length\", Config),\n\t<<\"12\">> = do_body(\"POST\", \"/body_length\", [], \"hello world!\", Config),\n\tok.\n\nhas_body(Config) ->\n\tdoc(\"Has a request body?\"),\n\t<<\"false\">> = do_get_body(\"/has_body\", Config),\n\t<<\"true\">> = do_body(\"POST\", \"/has_body\", [], \"hello world!\", Config),\n\tok.\n\nread_body(Config) ->\n\tdoc(\"Request body.\"),\n\t<<>> = do_get_body(\"/read_body\", Config),\n\t<<\"hello world!\">> = do_body(\"POST\", \"/read_body\", [], \"hello world!\", Config),\n\t%% We expect to have read *at least* 1000 bytes.\n\t<<0:8000, _/bits>> = do_body(\"POST\", \"/opts/read_body/length\", [], <<0:8000000>>, Config),\n\t%% The timeout value is set too low on purpose to ensure a crash occurs.\n\tok = do_read_body_timeout(\"/opts/read_body/timeout\", <<0:8000000>>, Config),\n\t%% 10MB body larger than default length.\n\t<<0:80000000>> = do_body(\"POST\", \"/full/read_body\", [], <<0:80000000>>, Config),\n\tok.\n\nread_body_mtu(Config) ->\n\tcase os:type() of\n\t\t{win32, _} ->\n\t\t\t{skip, \"Loopback MTU size is 0xFFFFFFFF on Windows.\"};\n\t\t{unix, _} ->\n\t\t\tdoc(\"Request body whose sizes are around the MTU.\"),\n\t\t\tMTU = ct_helper:get_loopback_mtu(),\n\t\t\t_ = [begin\n\t\t\t\tBody = <<0:Size/unit:8>>,\n\t\t\t\tBody = do_body(\"POST\", \"/full/read_body\", [], Body, Config)\n\t\t\tend || Size <- lists:seq(MTU - 10, MTU + 10)],\n\t\t\tok\n\tend.\n\nread_body_period(Config) ->\n\tdoc(\"Read the request body for at most 2 seconds.\"),\n\tConnPid = gun_open(Config),\n\tBody = <<0:8000000>>,\n\tRef = gun:headers(ConnPid, \"POST\", \"/opts/read_body/period\", [\n\t\t{<<\"content-length\">>, integer_to_binary(byte_size(Body) * 2)}\n\t]),\n\t%% The body is sent without fin. The server will read what it can\n\t%% for 2 seconds. The test succeeds if we get some of the data back\n\t%% (meaning the function will have returned after the period ends).\n\tgun:data(ConnPid, Ref, nofin, Body),\n\tResponse = gun:await(ConnPid, Ref, infinity),\n\tcase Response of\n\t\t{response, nofin, 200, _} ->\n\t\t\t{data, _, Data} = gun:await(ConnPid, Ref, infinity),\n\t\t\t%% We expect to read at least some data.\n\t\t\ttrue = Data =/= <<>>,\n\t\t\tgun:close(ConnPid);\n\t\t%% We got a crash, likely because the environment\n\t\t%% was overloaded and the timeout triggered. Try again.\n\t\t{response, _, 500, _} ->\n\t\t\tgun:close(ConnPid),\n\t\t\tread_body_period(Config)\n\tend.\n\n%% We expect a crash.\ndo_read_body_timeout(Path, Body, Config) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:headers(ConnPid, \"POST\", Path, [\n\t\t{<<\"content-length\">>, integer_to_binary(byte_size(Body))}\n\t]),\n\tcase gun:await(ConnPid, Ref, infinity) of\n\t\t{response, _, 500, _} ->\n\t\t\tok;\n\t\t%% See do_maybe_h3_error comment for details.\n\t\t{error, {stream_error, {stream_error, h3_internal_error, _}}} ->\n\t\t\tok\n\tend,\n\tgun:close(ConnPid).\n\nread_body_auto(Config) ->\n\tdoc(\"Read the request body using auto mode.\"),\n\t<<0:80000000>> = do_body(\"POST\", \"/auto-sync/read_body\", [], <<0:80000000>>, Config),\n\t<<0:80000000>> = do_body(\"POST\", \"/auto-async/read_body\", [], <<0:80000000>>, Config),\n\tok.\n\nread_body_spawn(Config) ->\n\tdoc(\"Confirm we can use cowboy_req:read_body/1,2 from another process.\"),\n\t<<\"hello world!\">> = do_body(\"POST\", \"/spawn/read_body\", [], \"hello world!\", Config),\n\tok.\n\nread_body_expect_100_continue(Config) ->\n\tdoc(\"Request body with a 100-continue expect header.\"),\n\tdo_read_body_expect_100_continue(\"/read_body\", Config).\n\nread_body_expect_100_continue_user_sent(Config) ->\n\tdoc(\"Request body with a 100-continue expect header, 100 response sent by handler.\"),\n\tdo_read_body_expect_100_continue(\"/100-continue/read_body\", Config).\n\ndo_read_body_expect_100_continue(Path, Config) ->\n\tConnPid = gun_open(Config),\n\tBody = <<0:8000000>>,\n\tHeaders = [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"expect\">>, <<\"100-continue\">>},\n\t\t{<<\"content-length\">>, integer_to_binary(byte_size(Body))}\n\t],\n\tRef = gun:post(ConnPid, Path, Headers),\n\t{inform, 100, []} = gun:await(ConnPid, Ref, infinity),\n\tgun:data(ConnPid, Ref, fin, Body),\n\t{response, IsFin, 200, RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\t{ok, RespBody} = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, Ref, infinity);\n\t\tfin -> {ok, <<>>}\n\tend,\n\tgun:close(ConnPid),\n\tdo_decode(RespHeaders, RespBody),\n\tok.\n\nread_urlencoded_body(Config) ->\n\tdoc(\"application/x-www-form-urlencoded request body.\"),\n\t<<\"[]\">> = do_body(\"POST\", \"/read_urlencoded_body\", [], <<>>, Config),\n\t<<\"[{<<\\\"abc\\\">>,true}]\">> = do_body(\"POST\", \"/read_urlencoded_body\", [], \"abc\", Config),\n\t<<\"[{<<\\\"a\\\">>,<<\\\"b\\\">>},{<<\\\"c\\\">>,<<\\\"d e\\\">>}]\">>\n\t\t= do_body(\"POST\", \"/read_urlencoded_body\", [], \"a=b&c=d+e\", Config),\n\t%% The timeout value is set too low on purpose to ensure a crash occurs.\n\tok = do_read_body_timeout(\"/opts/read_urlencoded_body/timeout\", <<\"abc\">>, Config),\n\t%% Ensure parse errors result in a 400 response.\n\t{400, _} = do_body_error(\"POST\", \"/read_urlencoded_body\", [], \"%%%%%\", Config),\n\tok.\n\nread_urlencoded_body_too_large(Config) ->\n\tdoc(\"application/x-www-form-urlencoded request body too large. \"\n\t\t\"Send a 10MB body, larger than the default length, to ensure a crash occurs.\"),\n\tdo_read_urlencoded_body_too_large(\"/no-opts/read_urlencoded_body\",\n\t\tstring:chars($a, 10000000), Config).\n\n%% We expect a crash.\ndo_read_urlencoded_body_too_large(Path, Body, Config) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:headers(ConnPid, \"POST\", Path, [\n\t\t{<<\"content-length\">>, integer_to_binary(iolist_size(Body))}\n\t]),\n\tgun:data(ConnPid, Ref, fin, Body),\n\tResponse = gun:await(ConnPid, Ref, infinity),\n\tgun:close(ConnPid),\n\tcase Response of\n\t\t{response, _, 413, _} ->\n\t\t\tok;\n\t\t%% We got the wrong crash, likely because the environment\n\t\t%% was overloaded and the timeout triggered. Try again.\n\t\t{response, _, 408, _} ->\n\t\t\tdo_read_urlencoded_body_too_large(Path, Body, Config);\n\t\t%% Timing issues make it possible for the connection to be\n\t\t%% closed before the data went through. We retry.\n\t\t{error, {stream_error, {closed, {error,closed}}}} ->\n\t\t\tdo_read_urlencoded_body_too_large(Path, Body, Config)\n\tend.\n\nread_urlencoded_body_too_long(Config) ->\n\tdoc(\"application/x-www-form-urlencoded request body sent too slow. \"\n\t\t\"The body is simply not being sent fully. It is read by the handler \"\n\t\t\"for at most 1 second. A crash occurs because we don't have the full body.\"),\n\tdo_read_urlencoded_body_too_long(\"/crash/read_urlencoded_body/period\", <<\"abc\">>, Config).\n\n%% We expect a crash.\ndo_read_urlencoded_body_too_long(Path, Body, Config) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:headers(ConnPid, \"POST\", Path, [\n\t\t{<<\"content-length\">>, integer_to_binary(byte_size(Body) * 2)}\n\t]),\n\tgun:data(ConnPid, Ref, nofin, Body),\n\tProtocol = config(protocol, Config),\n\tcase gun:await(ConnPid, Ref, infinity) of\n\t\t{response, _, 408, RespHeaders} when Protocol =:= http ->\n\t\t\t%% 408 error responses should close HTTP/1.1 connections.\n\t\t\t{_, <<\"close\">>} = lists:keyfind(<<\"connection\">>, 1, RespHeaders),\n\t\t\tgun:close(ConnPid);\n\t\t{response, _, 408, _} when Protocol =:= http2; Protocol =:= http3 ->\n\t\t\tgun:close(ConnPid);\n\t\t%% We must have hit the timeout due to busy CI environment. Retry.\n\t\t{response, _, 500, _} ->\n\t\t\tgun:close(ConnPid),\n\t\t\tdo_read_urlencoded_body_too_long(Path, Body, Config)\n\tend.\n\nread_and_match_urlencoded_body(Config) ->\n\tdoc(\"Read and match an application/x-www-form-urlencoded request body.\"),\n\t<<\"#{}\">> = do_body(\"POST\", \"/match/body_qs\", [], \"a=b&c=d\", Config),\n\t<<\"#{a => <<\\\"b\\\">>}\">> = do_body(\"POST\", \"/match/body_qs/a\", [], \"a=b&c=d\", Config),\n\t<<\"#{c => <<\\\"d\\\">>}\">> = do_body(\"POST\", \"/match/body_qs/c\", [], \"a=b&c=d\", Config),\n\tcase do_body(\"POST\", \"/match/body_qs/a/c\", [], \"a=b&c=d\", Config) of\n\t\t<<\"#{a => <<\\\"b\\\">>,c => <<\\\"d\\\">>}\">> -> ok;\n\t\t<<\"#{c => <<\\\"d\\\">>,a => <<\\\"b\\\">>}\">> -> ok\n\tend,\n\tcase do_body(\"POST\", \"/match/body_qs/a/c\", [], \"a=b&c\", Config) of\n\t\t<<\"#{a => <<\\\"b\\\">>,c => true}\">> -> ok;\n\t\t<<\"#{c => true,a => <<\\\"b\\\">>}\">> -> ok\n\tend,\n\tcase do_body(\"POST\", \"/match/body_qs/a/c\", [], \"a&c=d\", Config) of\n\t\t<<\"#{a => true,c => <<\\\"d\\\">>}\">> -> ok;\n\t\t<<\"#{c => <<\\\"d\\\">>,a => true}\">> -> ok\n\tend,\n\t%% Ensure match errors result in a 400 response.\n\t{400, _} = do_body_error(\"POST\", \"/match/body_qs/a/c\", [], \"a=b\", Config),\n\t%% Ensure parse errors result in a 400 response.\n\t{400, _} = do_body_error(\"POST\", \"/match/body_qs\", [], \"%%%%%\", Config),\n\t%% The timeout value is set too low on purpose to ensure a crash occurs.\n\tok = do_read_body_timeout(\"/opts/read_and_match_urlencoded_body/timeout\", <<\"abc\">>, Config),\n\tok.\n\nread_and_match_urlencoded_body_too_large(Config) ->\n\tdoc(\"Read and match an application/x-www-form-urlencoded request body too large. \"\n\t\t\"Send a 10MB body, larger than the default length, to ensure a crash occurs.\"),\n\tdo_read_urlencoded_body_too_large(\n\t\t\"/no-opts/read_and_match_urlencoded_body\",\n\t\tstring:chars($a, 10000000), Config).\n\nread_and_match_urlencoded_body_too_long(Config) ->\n\tdoc(\"Read and match an application/x-www-form-urlencoded request body sent too slow. \"\n\t\t\"The body is simply not being sent fully. It is read by the handler \"\n\t\t\"for at most 1 second. A crash occurs because we don't have the full body.\"),\n\tdo_read_urlencoded_body_too_long(\n\t\t\"/crash/read_and_match_urlencoded_body/period\", <<\"abc\">>, Config).\n\nmultipart(Config) ->\n\tdoc(\"Multipart request body.\"),\n\tdo_multipart(\"/multipart\", Config).\n\ndo_multipart(Path, Config) ->\n\tLargeBody = iolist_to_binary(string:chars($a, 10000000)),\n\tReqBody = [\n\t\t\"--deadbeef\\r\\nContent-Type: text/plain\\r\\n\\r\\nCowboy is an HTTP server.\\r\\n\"\n\t\t\"--deadbeef\\r\\nContent-Type: application/octet-stream\\r\\nX-Custom: value\\r\\n\\r\\n\", LargeBody, \"\\r\\n\"\n\t\t\"--deadbeef--\"\n\t],\n\tRespBody = do_body(\"POST\", Path, [\n\t\t{<<\"content-type\">>, <<\"multipart/mixed; boundary=deadbeef\">>}\n\t], ReqBody, Config),\n\t[\n\t\t{#{<<\"content-type\">> := <<\"text/plain\">>}, <<\"Cowboy is an HTTP server.\">>},\n\t\t{LargeHeaders, LargeBody}\n\t] = binary_to_term(RespBody),\n\t#{\n\t\t<<\"content-type\">> := <<\"application/octet-stream\">>,\n\t\t<<\"x-custom\">> := <<\"value\">>\n\t} = LargeHeaders,\n\tok.\n\nmultipart_error_empty(Config) ->\n\tdoc(\"Multipart request body is empty.\"),\n\t%% We use an empty list as a body to make sure Gun knows\n\t%% we want to send an empty body.\n\t%% @todo This is a terrible hack. Improve Gun!\n\tBody = [],\n\t%% Ensure an empty body results in a 400 error.\n\t{400, _} = do_body_error(\"POST\", \"/multipart\", [\n\t\t{<<\"content-type\">>, <<\"multipart/mixed; boundary=deadbeef\">>}\n\t], Body, Config),\n\tok.\n\nmultipart_error_preamble_only(Config) ->\n\tdoc(\"Multipart request body only contains a preamble.\"),\n\t%% Ensure an empty body results in a 400 error.\n\t{400, _} = do_body_error(\"POST\", \"/multipart\", [\n\t\t{<<\"content-type\">>, <<\"multipart/mixed; boundary=deadbeef\">>}\n\t], <<\"Preamble.\">>, Config),\n\tok.\n\nmultipart_error_headers(Config) ->\n\tdoc(\"Multipart request body with invalid part headers.\"),\n\tReqBody = [\n\t\t\"--deadbeef\\r\\nbad-header text/plain\\r\\n\\r\\nCowboy is an HTTP server.\\r\\n\"\n\t\t\"--deadbeef--\"\n\t],\n\t%% Ensure parse errors result in a 400 response.\n\t{400, _} = do_body_error(\"POST\", \"/multipart\", [\n\t\t{<<\"content-type\">>, <<\"multipart/mixed; boundary=deadbeef\">>}\n\t], ReqBody, Config),\n\tok.\n\n%% The function to parse the multipart body currently does not crash,\n%% as far as I can tell. There is therefore no test for it.\n\nmultipart_error_no_final_boundary(Config) ->\n\tdoc(\"Multipart request body with no final boundary.\"),\n\tReqBody = [\n\t\t\"--deadbeef\\r\\nContent-Type: text/plain\\r\\n\\r\\nCowboy is an HTTP server.\\r\\n\"\n\t],\n\t%% Ensure parse errors result in a 400 response.\n\t{400, _} = do_body_error(\"POST\", \"/multipart\", [\n\t\t{<<\"content-type\">>, <<\"multipart/mixed; boundary=deadbeef\">>}\n\t], ReqBody, Config),\n\tok.\n\nmultipart_missing_boundary(Config) ->\n\tdoc(\"Multipart request body without a boundary in the media type.\"),\n\tReqBody = [\n\t\t\"--deadbeef\\r\\nContent-Type: text/plain\\r\\n\\r\\nCowboy is an HTTP server.\\r\\n\"\n\t\t\"--deadbeef--\"\n\t],\n\t%% Ensure parse errors result in a 400 response.\n\t{400, _} = do_body_error(\"POST\", \"/multipart\", [\n\t\t{<<\"content-type\">>, <<\"multipart/mixed\">>}\n\t], ReqBody, Config),\n\tok.\n\nread_part_skip_body(Config) ->\n\tdoc(\"Multipart request body skipping part bodies.\"),\n\tLargeBody = iolist_to_binary(string:chars($a, 10000000)),\n\tReqBody = [\n\t\t\"--deadbeef\\r\\nContent-Type: text/plain\\r\\n\\r\\nCowboy is an HTTP server.\\r\\n\"\n\t\t\"--deadbeef\\r\\nContent-Type: application/octet-stream\\r\\nX-Custom: value\\r\\n\\r\\n\", LargeBody, \"\\r\\n\"\n\t\t\"--deadbeef--\"\n\t],\n\tRespBody = do_body(\"POST\", \"/multipart/skip_body\", [\n\t\t{<<\"content-type\">>, <<\"multipart/mixed; boundary=deadbeef\">>}\n\t], ReqBody, Config),\n\t[\n\t\t#{<<\"content-type\">> := <<\"text/plain\">>},\n\t\tLargeHeaders\n\t] = binary_to_term(RespBody),\n\t#{\n\t\t<<\"content-type\">> := <<\"application/octet-stream\">>,\n\t\t<<\"x-custom\">> := <<\"value\">>\n\t} = LargeHeaders,\n\tok.\n\n%% @todo When reading a multipart body, length and period\n%% only apply to a single read_body call. We may want a\n%% separate option to know how many reads we want to do\n%% before we give up.\n\nread_part2(Config) ->\n\tdoc(\"Multipart request body using read_part/2.\"),\n\t%% Override the length and period values only, making\n\t%% the request process use more read_body calls.\n\t%%\n\t%% We do not try a custom timeout value since this would\n\t%% be the same test as read_body/2.\n\tdo_multipart(\"/multipart/read_part2\", Config).\n\nread_part_body2(Config) ->\n\tdoc(\"Multipart request body using read_part_body/2.\"),\n\t%% Override the length and period values only, making\n\t%% the request process use more read_body calls.\n\t%%\n\t%% We do not try a custom timeout value since this would\n\t%% be the same test as read_body/2.\n\tdo_multipart(\"/multipart/read_part_body2\", Config).\n\n%% Tests: Response.\n\n%% @todo We want to crash when calling set_resp_* or related\n%% functions after the reply has been sent.\n\nset_resp_cookie(Config) ->\n\tdoc(\"Response using set_resp_cookie.\"),\n\t%% Single cookie, no options.\n\t{200, Headers1, _} = do_get(\"/resp/set_resp_cookie3\", Config),\n\t{_, <<\"mycookie=myvalue\">>}\n\t\t= lists:keyfind(<<\"set-cookie\">>, 1, Headers1),\n\t%% Single cookie, with options.\n\t{200, Headers2, _} = do_get(\"/resp/set_resp_cookie4\", Config),\n\t{_, <<\"mycookie=myvalue; Path=/resp/set_resp_cookie4\">>}\n\t\t= lists:keyfind(<<\"set-cookie\">>, 1, Headers2),\n\t%% Multiple cookies.\n\t{200, Headers3, _} = do_get(\"/resp/set_resp_cookie3/multiple\", Config),\n\t[_, _] = [H || H={<<\"set-cookie\">>, _} <- Headers3],\n\t%% Overwrite previously set cookie.\n\t{200, Headers4, _} = do_get(\"/resp/set_resp_cookie3/overwrite\", Config),\n\t{_, <<\"mycookie=overwrite\">>}\n\t\t= lists:keyfind(<<\"set-cookie\">>, 1, Headers4),\n\tok.\n\nset_resp_header(Config) ->\n\tdoc(\"Response using set_resp_header.\"),\n\t{200, Headers, <<\"OK\">>} = do_get(\"/resp/set_resp_header\", Config),\n\ttrue = lists:keymember(<<\"content-type\">>, 1, Headers),\n\t%% The set-cookie header is special. set_resp_cookie must be used.\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/set_resp_header_cookie\", Config)),\n\tok.\n\nset_resp_headers(Config) ->\n\tdoc(\"Response using set_resp_headers.\"),\n\t{200, Headers1, <<\"OK\">>} = do_get(\"/resp/set_resp_headers\", Config),\n\ttrue = lists:keymember(<<\"content-type\">>, 1, Headers1),\n\ttrue = lists:keymember(<<\"content-encoding\">>, 1, Headers1),\n\t{200, Headers2, <<\"OK\">>} = do_get(\"/resp/set_resp_headers_list\", Config),\n\ttrue = lists:keymember(<<\"content-type\">>, 1, Headers2),\n\ttrue = lists:keymember(<<\"content-encoding\">>, 1, Headers2),\n\t{_, <<\"one, two\">>} = lists:keyfind(<<\"test-header\">>, 1, Headers2),\n\t%% The set-cookie header is special. set_resp_cookie must be used.\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/set_resp_headers_cookie\", Config)),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/set_resp_headers_list_cookie\", Config)),\n\tok.\n\nresp_header(Config) ->\n\tdoc(\"Response header with/without default.\"),\n\t{200, _, <<\"OK\">>} = do_get(\"/resp/resp_header_defined\", Config),\n\t{200, _, <<\"OK\">>} = do_get(\"/resp/resp_header_default\", Config),\n\tok.\n\nresp_headers(Config) ->\n\tdoc(\"Get all response headers.\"),\n\t{200, _, <<\"OK\">>} = do_get(\"/resp/resp_headers\", Config),\n\t{200, _, <<\"OK\">>} = do_get(\"/resp/resp_headers_empty\", Config),\n\tok.\n\nset_resp_body(Config) ->\n\tdoc(\"Response using set_resp_body.\"),\n\t{200, _, <<\"OK\">>} = do_get(\"/resp/set_resp_body\", Config),\n\t{200, _, <<\"OVERRIDE\">>} = do_get(\"/resp/set_resp_body/override\", Config),\n\t{ok, AppFile} = file:read_file(code:where_is_file(\"cowboy.app\")),\n\t{200, _, AppFile} = do_get(\"/resp/set_resp_body/sendfile\", Config),\n\tok.\n\nset_resp_body_sendfile0(Config) ->\n\tdoc(\"Response using set_resp_body with a sendfile of length 0.\"),\n\tPath = \"/resp/set_resp_body/sendfile0\",\n\tConnPid = gun_open(Config),\n\t%% First request.\n\tRef1 = gun:get(ConnPid, Path, [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, IsFin, 200, _} = gun:await(ConnPid, Ref1, infinity),\n\t{ok, <<>>} = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, Ref1, infinity);\n\t\tfin -> {ok, <<>>}\n\tend,\n\t%% Second request will confirm everything works as intended.\n\tRef2 = gun:get(ConnPid, Path, [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, IsFin, 200, _} = gun:await(ConnPid, Ref2, infinity),\n\t{ok, <<>>} = case IsFin of\n\t\tnofin -> gun:await_body(ConnPid, Ref2, infinity);\n\t\tfin -> {ok, <<>>}\n\tend,\n\tgun:close(ConnPid),\n\tok.\n\nhas_resp_header(Config) ->\n\tdoc(\"Has response header?\"),\n\t{200, Headers, <<\"OK\">>} = do_get(\"/resp/has_resp_header\", Config),\n\ttrue = lists:keymember(<<\"content-type\">>, 1, Headers),\n\tok.\n\nhas_resp_body(Config) ->\n\tdoc(\"Has response body?\"),\n\t{200, _, <<\"OK\">>} = do_get(\"/resp/has_resp_body\", Config),\n\t{200, _, <<\"OK\">>} = do_get(\"/resp/has_resp_body/sendfile\", Config),\n\tok.\n\ndelete_resp_header(Config) ->\n\tdoc(\"Delete response header.\"),\n\t{200, Headers, <<\"OK\">>} = do_get(\"/resp/delete_resp_header\", Config),\n\tfalse = lists:keymember(<<\"content-type\">>, 1, Headers),\n\tok.\n\n%% Data may be lost due to how RESET_STREAM QUIC frame works.\n%% Because there is ongoing work for a better way to reset streams\n%% (https://www.ietf.org/archive/id/draft-ietf-quic-reliable-stream-reset-03.html)\n%% we convert the error to a 500 to keep the tests more explicit\n%% at what we expect.\n%% @todo When RESET_STREAM_AT gets added we can remove this function.\ndo_maybe_h3_error2({stream_error, h3_internal_error, _}) -> {500, []};\ndo_maybe_h3_error2(Result) -> Result.\n\ndo_maybe_h3_error3({stream_error, h3_internal_error, _}) -> {500, [], <<>>};\ndo_maybe_h3_error3(Result) -> Result.\n\ninform2(Config) ->\n\tdoc(\"Informational response(s) without headers, followed by the real response.\"),\n\t{102, [], 200, _, _} = do_get_inform(\"/resp/inform2/102\", Config),\n\t{102, [], 200, _, _} = do_get_inform(\"/resp/inform2/binary\", Config),\n\t{500, _} = do_maybe_h3_error2(do_get_inform(\"/resp/inform2/error\", Config)),\n\t{102, [], 200, _, _} = do_get_inform(\"/resp/inform2/twice\", Config),\n\t%% With HTTP/1.1 and HTTP/2 we will not get an error.\n\t%% With HTTP/3 however the stream will occasionally\n\t%% be reset before Gun receives the response.\n\tcase do_get_inform(\"/resp/inform2/after_reply\", Config) of\n\t\t{200, _} ->\n\t\t\tok;\n\t\t{stream_error, h3_internal_error, _} ->\n\t\t\tok\n\tend.\n\ninform3(Config) ->\n\tdoc(\"Informational response(s) with headers, followed by the real response.\"),\n\tHeaders = [{<<\"ext-header\">>, <<\"ext-value\">>}],\n\t{102, Headers, 200, _, _} = do_get_inform(\"/resp/inform3/102\", Config),\n\t{102, Headers, 200, _, _} = do_get_inform(\"/resp/inform3/binary\", Config),\n\t{500, _} = do_maybe_h3_error2(do_get_inform(\"/resp/inform3/error\", Config)),\n\t%% The set-cookie header is special. set_resp_cookie must be used.\n\t{500, _} = do_maybe_h3_error2(do_get_inform(\"/resp/inform3/set_cookie\", Config)),\n\t{102, Headers, 200, _, _} = do_get_inform(\"/resp/inform3/twice\", Config),\n\t%% With HTTP/1.1 and HTTP/2 we will not get an error.\n\t%% With HTTP/3 however the stream will occasionally\n\t%% be reset before Gun receives the response.\n\tcase do_get_inform(\"/resp/inform3/after_reply\", Config) of\n\t\t{200, _} ->\n\t\t\tok;\n\t\t{stream_error, h3_internal_error, _} ->\n\t\t\tok\n\tend.\n\nreply2(Config) ->\n\tdoc(\"Response with default headers and no body.\"),\n\t{200, _, _} = do_get(\"/resp/reply2/200\", Config),\n\t{201, _, _} = do_get(\"/resp/reply2/201\", Config),\n\t{404, _, _} = do_get(\"/resp/reply2/404\", Config),\n\t{200, _, _} = do_get(\"/resp/reply2/binary\", Config),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/reply2/error\", Config)),\n\t%% @todo How to test this properly? This isn't enough.\n\t{200, _, _} = do_get(\"/resp/reply2/twice\", Config),\n\tok.\n\nreply3(Config) ->\n\tdoc(\"Response with additional headers and no body.\"),\n\t{200, Headers1, _} = do_get(\"/resp/reply3/200\", Config),\n\ttrue = lists:keymember(<<\"content-type\">>, 1, Headers1),\n\t{201, Headers2, _} = do_get(\"/resp/reply3/201\", Config),\n\ttrue = lists:keymember(<<\"content-type\">>, 1, Headers2),\n\t{404, Headers3, _} = do_get(\"/resp/reply3/404\", Config),\n\ttrue = lists:keymember(<<\"content-type\">>, 1, Headers3),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/reply3/error\", Config)),\n\t%% The set-cookie header is special. set_resp_cookie must be used.\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/reply3/set_cookie\", Config)),\n\tok.\n\nreply4(Config) ->\n\tdoc(\"Response with additional headers and body.\"),\n\t{200, _, <<\"OK\">>} = do_get(\"/resp/reply4/200\", Config),\n\t{201, _, <<\"OK\">>} = do_get(\"/resp/reply4/201\", Config),\n\t{404, _, <<\"OK\">>} = do_get(\"/resp/reply4/404\", Config),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/reply4/error\", Config)),\n\t%% The set-cookie header is special. set_resp_cookie must be used.\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/reply4/set_cookie\", Config)),\n\tok.\n\nstream_reply2(Config) ->\n\tdoc(\"Response with default headers and streamed body.\"),\n\tBody = <<0:8000000>>,\n\t{200, _, Body} = do_get(\"/resp/stream_reply2/200\", Config),\n\t{201, _, Body} = do_get(\"/resp/stream_reply2/201\", Config),\n\t{404, _, Body} = do_get(\"/resp/stream_reply2/404\", Config),\n\t{200, _, Body} = do_get(\"/resp/stream_reply2/binary\", Config),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/stream_reply2/error\", Config)),\n\tok.\n\nstream_reply2_twice(Config) ->\n\tdoc(\"Attempting to stream a response twice results in a crash.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/stream_reply2/twice\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref, infinity),\n\tProtocol = config(protocol, Config),\n\tFlavor = config(flavor, Config),\n\tcase {Protocol, Flavor, gun:await_body(ConnPid, Ref, infinity)} of\n\t\t%% In HTTP/1.1 we cannot propagate an error at that point.\n\t\t%% The response will simply not have a body.\n\t\t{http, vanilla, {ok, <<>>}} ->\n\t\t\tok;\n\t\t%% When compression was used we do get gzip headers. But\n\t\t%% we do not have any data in the zlib stream.\n\t\t{http, compress, {ok, Data}} ->\n\t\t\tZ = zlib:open(),\n\t\t\tzlib:inflateInit(Z, 31),\n\t\t\t0 = iolist_size(zlib:inflate(Z, Data)),\n\t\t\tok;\n\t\t%% In HTTP/2 and HTTP/3 the stream gets reset with an appropriate error.\n\t\t{http2, _, {error, {stream_error, {stream_error, internal_error, _}}}} ->\n\t\t\tok;\n\t\t{http3, _, {error, {stream_error, {stream_error, h3_internal_error, _}}}} ->\n\t\t\tok\n\tend,\n\tgun:close(ConnPid).\n\nstream_reply3(Config) ->\n\tdoc(\"Response with additional headers and streamed body.\"),\n\tBody = <<0:8000000>>,\n\t{200, Headers1, Body} = do_get(\"/resp/stream_reply3/200\", Config),\n\ttrue = lists:keymember(<<\"content-type\">>, 1, Headers1),\n\t{201, Headers2, Body} = do_get(\"/resp/stream_reply3/201\", Config),\n\ttrue = lists:keymember(<<\"content-type\">>, 1, Headers2),\n\t{404, Headers3, Body} = do_get(\"/resp/stream_reply3/404\", Config),\n\ttrue = lists:keymember(<<\"content-type\">>, 1, Headers3),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/stream_reply3/error\", Config)),\n\t%% The set-cookie header is special. set_resp_cookie must be used.\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/resp/stream_reply3/set_cookie\", Config)),\n\tok.\n\nstream_body_fin0(Config) ->\n\tdoc(\"Streamed body with last chunk of size 0.\"),\n\t{200, _, <<\"Hello world!\">>} = do_get(\"/resp/stream_body/fin0\", Config),\n\tok.\n\nstream_body_multiple(Config) ->\n\tdoc(\"Streamed body via multiple calls.\"),\n\t{200, _, <<\"Hello world!\">>} = do_get(\"/resp/stream_body/multiple\", Config),\n\tok.\n\nstream_body_loop(Config) ->\n\tdoc(\"Streamed body via a fast loop.\"),\n\t{200, _, <<0:32000000/unit:8>>} = do_get(\"/resp/stream_body/loop\", Config),\n\tok.\n\nstream_body_nofin(Config) ->\n\tdoc(\"Unfinished streamed body.\"),\n\t{200, _, <<\"Hello world!\">>} = do_get(\"/resp/stream_body/nofin\", Config),\n\tok.\n\nstream_body_sendfile(Config) ->\n\tdoc(\"Streamed body via multiple calls, including sendfile calls.\"),\n\t{ok, AppFile} = file:read_file(code:where_is_file(\"cowboy.app\")),\n\tExpectedBody = iolist_to_binary([\n\t\t<<\"Hello \">>,\n\t\tAppFile,\n\t\t<<\" interspersed \">>,\n\t\tAppFile,\n\t\t<<\" world!\">>\n\t]),\n\t{200, _, ExpectedBody} = do_get(\"/resp/stream_body/sendfile\", Config),\n\tok.\n\nstream_body_sendfile_fin(Config) ->\n\tdoc(\"Streamed body via multiple calls, including a sendfile final call.\"),\n\t{ok, AppFile} = file:read_file(code:where_is_file(\"cowboy.app\")),\n\tExpectedBody = iolist_to_binary([\n\t\t<<\"Hello! \">>,\n\t\tAppFile\n\t]),\n\t{200, _, ExpectedBody} = do_get(\"/resp/stream_body/sendfile_fin\", Config),\n\tok.\n\nstream_body_spawn(Config) ->\n\tdoc(\"Confirm we can use cowboy_req:stream_body/3 from another process.\"),\n\t{200, _, <<\"Hello world!\">>} = do_get(\"/resp/stream_body/spawn\", Config),\n\tok.\n\nstream_body_content_length_multiple(Config) ->\n\tdoc(\"Streamed body via multiple calls.\"),\n\t{200, _, <<\"Hello world!\">>} = do_get(\"/resp/stream_body_content_length/multiple\", Config),\n\tok.\n\nstream_body_content_length_fin0(Config) ->\n\tdoc(\"Streamed body with last chunk of size 0.\"),\n\t{200, _, <<\"Hello world!\">>} = do_get(\"/resp/stream_body_content_length/fin0\", Config),\n\tok.\n\nstream_body_content_length_nofin(Config) ->\n\tdoc(\"Unfinished streamed body.\"),\n\t{200, _, <<\"Hello world!\">>} = do_get(\"/resp/stream_body_content_length/nofin\", Config),\n\tok.\n\nstream_body_content_length_nofin_error(Config) ->\n\tdoc(\"Not all of the response body sent.\"),\n\tcase config(protocol, Config) of\n\t\thttp ->\n\t\t\tcase do_get_error(\"/resp/stream_body_content_length/nofin-error\", Config) of\n\t\t\t\t%% When compression is used content-length is not sent.\n\t\t\t\t{200, Headers, <<\"Hello\">>} ->\n\t\t\t\t\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers);\n\t\t\t\t%% The server closes the connection when the body couldn't be sent fully.\n\t\t\t\t{error, {stream_error, closed}} ->\n\t\t\t\t\treceive\n\t\t\t\t\t\t{gun_down, ConnPid, _, _, _} ->\n\t\t\t\t\t\t\tgun:close(ConnPid)\n\t\t\t\t\tafter 1000 ->\n\t\t\t\t\t\terror(timeout)\n\t\t\t\t\tend\n\t\t\tend;\n\t\thttp2 ->\n\t\t\t%% @todo HTTP/2 should have the same content-length checks.\n\t\t\t{skip, \"Implement the test for HTTP/2.\"};\n\t\thttp3 ->\n\t\t\t%% @todo HTTP/3 should have the same content-length checks.\n\t\t\t{skip, \"Implement the test for HTTP/3.\"}\n\tend.\n\nstream_body_concurrent(Config) ->\n\tConnPid = gun_open(Config),\n\tRef1 = gun:get(ConnPid, \"/resp/stream_body/loop\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\tRef2 = gun:get(ConnPid, \"/resp/stream_body/loop\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref1, infinity),\n\t{ok, _} = gun:await_body(ConnPid, Ref1, infinity),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref2, infinity),\n\t{ok, _} = gun:await_body(ConnPid, Ref2, infinity),\n\tgun:close(ConnPid).\n\n%% @todo Crash when calling stream_body after the fin flag has been set.\n%% @todo Crash when calling stream_body after calling reply.\n%% @todo Crash when calling stream_body before calling stream_reply.\n\nstream_events_single(Config) ->\n\tdoc(\"Streamed event.\"),\n\t{200, Headers, <<\n\t\t\"event: add_comment\\n\"\n\t\t\"data: Comment text.\\n\"\n\t\t\"data: With many lines.\\n\"\n\t\t\"\\n\"\n\t>>} = do_get(\"/resp/stream_events/single\", Config),\n\t{_, <<\"text/event-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nstream_events_list(Config) ->\n\tdoc(\"Streamed list of events.\"),\n\t{200, Headers, <<\n\t\t\"event: add_comment\\n\"\n\t\t\"data: Comment text.\\n\"\n\t\t\"data: With many lines.\\n\"\n\t\t\"\\n\"\n\t\t\": Set retry higher\\n\"\n\t\t\": with many lines also.\\n\"\n\t\t\"retry: 10000\\n\"\n\t\t\"\\n\"\n\t\t\"id: 123\\n\"\n\t\t\"event: add_comment\\n\"\n\t\t\"data: Closing!\\n\"\n\t\t\"\\n\"\n\t>>} = do_get(\"/resp/stream_events/list\", Config),\n\t{_, <<\"text/event-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nstream_events_multiple(Config) ->\n\tdoc(\"Streamed events via multiple calls.\"),\n\t{200, Headers, <<\n\t\t\"event: add_comment\\n\"\n\t\t\"data: Comment text.\\n\"\n\t\t\"data: With many lines.\\n\"\n\t\t\"\\n\"\n\t\t\": Set retry higher\\n\"\n\t\t\": with many lines also.\\n\"\n\t\t\"retry: 10000\\n\"\n\t\t\"\\n\"\n\t\t\"id: 123\\n\"\n\t\t\"event: add_comment\\n\"\n\t\t\"data: Closing!\\n\"\n\t\t\"\\n\"\n\t>>} = do_get(\"/resp/stream_events/multiple\", Config),\n\t{_, <<\"text/event-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nstream_trailers(Config) ->\n\tdoc(\"Stream body followed by trailer headers.\"),\n\t{200, RespHeaders, <<\"Hello world!\">>, [\n\t\t{<<\"grpc-status\">>, <<\"0\">>}\n\t]} = do_trailers(\"/resp/stream_trailers\", Config),\n\t{_, <<\"grpc-status\">>} = lists:keyfind(<<\"trailer\">>, 1, RespHeaders),\n\tok.\n\nstream_trailers_large(Config) ->\n\tdoc(\"Stream large body followed by trailer headers.\"),\n\t{200, RespHeaders, <<0:80000000>>, [\n\t\t{<<\"grpc-status\">>, <<\"0\">>}\n\t]} = do_trailers(\"/resp/stream_trailers/large\", Config),\n\t{_, <<\"grpc-status\">>} = lists:keyfind(<<\"trailer\">>, 1, RespHeaders),\n\tok.\n\nstream_trailers_no_te(Config) ->\n\tdoc(\"Stream body followed by trailer headers without a te header in the request.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/stream_trailers\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\t%% @todo Do we want to remove the trailer header automatically?\n%\tfalse = lists:keyfind(<<\"trailer\">>, 1, RespHeaders),\n\t{ok, RespBody} = gun:await_body(ConnPid, Ref, infinity),\n\t<<\"Hello world!\">> = do_decode(RespHeaders, RespBody),\n\tgun:close(ConnPid).\n\nstream_trailers_set_cookie(Config) ->\n\tdoc(\"Trying to send set-cookie in trailers should result in a crash.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/stream_trailers/set_cookie\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"te\">>, <<\"trailers\">>}\n\t]),\n\tProtocol = config(protocol, Config),\n\tcase gun:await(ConnPid, Ref, infinity) of\n\t\t{response, nofin, 200, _} when Protocol =:= http ->\n\t\t\t%% Trailers are not sent because of the stream error.\n\t\t\t{ok, _Body} = gun:await_body(ConnPid, Ref, infinity),\n\t\t\t{error, timeout} = gun:await_body(ConnPid, Ref, 1000),\n\t\t\tok;\n\t\t{response, nofin, 200, _} when Protocol =:= http2 ->\n\t\t\t{error, {stream_error, {stream_error, internal_error, _}}}\n\t\t\t\t= gun:await_body(ConnPid, Ref, infinity),\n\t\t\tok;\n\t\t{response, nofin, 200, _} when Protocol =:= http3 ->\n\t\t\t{error, {stream_error, {stream_error, h3_internal_error, _}}}\n\t\t\t\t= gun:await_body(ConnPid, Ref, infinity),\n\t\t\tok;\n\t\t%% The RST_STREAM arrived before the start of the response.\n\t\t%% See maybe_h3_error comment for details.\n\t\t{error, {stream_error, {stream_error, h3_internal_error, _}}} when Protocol =:= http3 ->\n\t\t\tok\n\tend,\n\tgun:close(ConnPid).\n\ndo_trailers(Path, Config) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, Path, [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"te\">>, <<\"trailers\">>}\n\t]),\n\t{response, nofin, Status, RespHeaders} = gun:await(ConnPid, Ref, infinity),\n\t{ok, RespBody, Trailers} = gun:await_body(ConnPid, Ref, infinity),\n\tgun:close(ConnPid),\n\t{Status, RespHeaders, do_decode(RespHeaders, RespBody), Trailers}.\n\n%% @todo Crash when calling stream_trailers twice.\n%% @todo Crash when calling stream_trailers after the fin flag has been set.\n%% @todo Crash when calling stream_trailers after calling reply.\n%% @todo Crash when calling stream_trailers before calling stream_reply.\n\n%% Tests: Push.\n\n%% @todo We want to crash when push is called after reply has been initiated.\n\npush(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_push_http(\"/resp/push\", Config);\n\t\thttp2 -> do_push_http2(Config);\n\t\thttp3 -> {skip, \"Implement server push for HTTP/3.\"}\n\tend.\n\npush_after_reply(Config) ->\n\tdoc(\"Trying to push a response after the final response results in a crash.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/push/after_reply\", []),\n\t%% With HTTP/1.1 and HTTP/2 we will not get an error.\n\t%% With HTTP/3 however the stream will occasionally\n\t%% be reset before Gun receives the response.\n\tcase gun:await(ConnPid, Ref, infinity) of\n\t\t{response, fin, 200, _} ->\n\t\t\tok;\n\t\t{error, {stream_error, {stream_error, h3_internal_error, _}}} ->\n\t\t\tok\n\tend,\n\tgun:close(ConnPid).\n\npush_method(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_push_http(\"/resp/push/method\", Config);\n\t\thttp2 -> do_push_http2_method(Config);\n\t\thttp3 -> {skip, \"Implement server push for HTTP/3.\"}\n\tend.\n\n\npush_origin(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_push_http(\"/resp/push/origin\", Config);\n\t\thttp2 -> do_push_http2_origin(Config);\n\t\thttp3 -> {skip, \"Implement server push for HTTP/3.\"}\n\tend.\n\npush_qs(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_push_http(\"/resp/push/qs\", Config);\n\t\thttp2 -> do_push_http2_qs(Config);\n\t\thttp3 -> {skip, \"Implement server push for HTTP/3.\"}\n\tend.\n\ndo_push_http(Path, Config) ->\n\tdoc(\"Ignore pushed responses when protocol is HTTP/1.1.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, Path, []),\n\t{response, fin, 200, _} = gun:await(ConnPid, Ref, infinity),\n\tgun:close(ConnPid).\n\ndo_push_http2(Config) ->\n\tdoc(\"Pushed responses.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/push\", []),\n\t%% We expect two pushed resources.\n\tOrigin = iolist_to_binary([\n\t\tcase config(type, Config) of\n\t\t\ttcp -> \"http\";\n\t\t\tssl -> \"https\"\n\t\tend,\n\t\t\"://localhost:\",\n\t\tinteger_to_binary(config(port, Config))\n\t]),\n\tOriginLen = byte_size(Origin),\n\t{push, PushCSS, <<\"GET\">>, <<Origin:OriginLen/binary, \"/static/style.css\">>,\n\t\t[{<<\"accept\">>,<<\"text/css\">>}]} = gun:await(ConnPid, Ref, infinity),\n\t{push, PushTXT, <<\"GET\">>, <<Origin:OriginLen/binary, \"/static/plain.txt\">>,\n\t\t[{<<\"accept\">>,<<\"text/plain\">>}]} = gun:await(ConnPid, Ref, infinity),\n\t%% Pushed CSS.\n\t{response, nofin, 200, HeadersCSS} = gun:await(ConnPid, PushCSS, infinity),\n\t{_, <<\"text/css\">>} = lists:keyfind(<<\"content-type\">>, 1, HeadersCSS),\n\t{ok, <<\"body{color:red}\\n\">>} = gun:await_body(ConnPid, PushCSS, infinity),\n\t%% Pushed TXT is 406 because the pushed accept header uses an undefined type.\n\t{response, fin, 406, _} = gun:await(ConnPid, PushTXT, infinity),\n\t%% Let's not forget about the response to the client's request.\n\t{response, fin, 200, _} = gun:await(ConnPid, Ref, infinity),\n\tgun:close(ConnPid).\n\ndo_push_http2_method(Config) ->\n\tdoc(\"Pushed response with non-GET method.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/push/method\", []),\n\t%% Pushed CSS.\n\t{push, PushCSS, <<\"HEAD\">>, _, [{<<\"accept\">>,<<\"text/css\">>}]} = gun:await(ConnPid, Ref, infinity),\n\t{response, fin, 200, HeadersCSS} = gun:await(ConnPid, PushCSS, infinity),\n\t{_, <<\"text/css\">>} = lists:keyfind(<<\"content-type\">>, 1, HeadersCSS),\n\t%% Let's not forget about the response to the client's request.\n\t{response, fin, 200, _} = gun:await(ConnPid, Ref, infinity),\n\tgun:close(ConnPid).\n\ndo_push_http2_origin(Config) ->\n\tdoc(\"Pushed response with custom scheme/host/port.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/push/origin\", []),\n\t%% Pushed CSS.\n\t{push, PushCSS, <<\"GET\">>, <<\"ftp://127.0.0.1:21/static/style.css\">>,\n\t\t[{<<\"accept\">>,<<\"text/css\">>}]} = gun:await(ConnPid, Ref, infinity),\n\t{response, nofin, 200, HeadersCSS} = gun:await(ConnPid, PushCSS, infinity),\n\t{_, <<\"text/css\">>} = lists:keyfind(<<\"content-type\">>, 1, HeadersCSS),\n\t{ok, <<\"body{color:red}\\n\">>} = gun:await_body(ConnPid, PushCSS, infinity),\n\t%% Let's not forget about the response to the client's request.\n\t{response, fin, 200, _} = gun:await(ConnPid, Ref, infinity),\n\tgun:close(ConnPid).\n\ndo_push_http2_qs(Config) ->\n\tdoc(\"Pushed response with query string.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/push/qs\", []),\n\t%% Pushed CSS.\n\tOrigin = iolist_to_binary([\n\t\tcase config(type, Config) of\n\t\t\ttcp -> \"http\";\n\t\t\tssl -> \"https\"\n\t\tend,\n\t\t\"://localhost:\",\n\t\tinteger_to_binary(config(port, Config))\n\t]),\n\tOriginLen = byte_size(Origin),\n\t{push, PushCSS, <<\"GET\">>, <<Origin:OriginLen/binary, \"/static/style.css?server=cowboy&version=2.0\">>,\n\t\t[{<<\"accept\">>,<<\"text/css\">>}]} = gun:await(ConnPid, Ref, infinity),\n\t{response, nofin, 200, HeadersCSS} = gun:await(ConnPid, PushCSS, infinity),\n\t{_, <<\"text/css\">>} = lists:keyfind(<<\"content-type\">>, 1, HeadersCSS),\n\t{ok, <<\"body{color:red}\\n\">>} = gun:await_body(ConnPid, PushCSS, infinity),\n\t%% Let's not forget about the response to the client's request.\n\t{response, fin, 200, _} = gun:await(ConnPid, Ref, infinity),\n\tgun:close(ConnPid).\n"
  },
  {
    "path": "test/rest_handler_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(rest_handler_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\n%% ct.\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\n%% Dispatch configuration.\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{'_', [\n\t\t{\"/\", rest_hello_h, []},\n\t\t{\"/accept_callback\", accept_callback_h, []},\n\t\t{\"/accept_callback_missing\", accept_callback_missing_h, []},\n\t\t{\"/charsets_provided\", charsets_provided_h, []},\n\t\t{\"/charsets_provided_empty\", charsets_provided_empty_h, []},\n\t\t{\"/charset_in_content_types_provided\",\n\t\t\tcharset_in_content_types_provided_h, []},\n\t\t{\"/charset_in_content_types_provided_implicit\",\n\t\t\tcharset_in_content_types_provided_implicit_h, []},\n\t\t{\"/charset_in_content_types_provided_implicit_no_callback\",\n\t\t\tcharset_in_content_types_provided_implicit_no_callback_h, []},\n\t\t{\"/content_types_accepted\", content_types_accepted_h, []},\n\t\t{\"/content_types_provided\", content_types_provided_h, []},\n\t\t{\"/delete_resource\", delete_resource_h, []},\n\t\t{\"/create_resource\", create_resource_h, []},\n\t\t{\"/expires\", expires_h, []},\n\t\t{\"/generate_etag\", generate_etag_h, []},\n\t\t{\"/if_range\", if_range_h, []},\n\t\t{\"/last_modified\", last_modified_h, []},\n\t\t{\"/provide_callback_missing\", provide_callback_missing_h, []},\n\t\t{\"/provide_range_callback\", provide_range_callback_h, []},\n\t\t{\"/range_satisfiable\", range_satisfiable_h, []},\n\t\t{\"/ranges_provided\", ranges_provided_h, []},\n\t\t{\"/ranges_provided_auto\", ranges_provided_auto_h, []},\n\t\t{\"/rate_limited\", rate_limited_h, []},\n\t\t{\"/stop_handler\", stop_handler_h, []},\n\t\t{\"/switch_handler\", switch_handler_h, run},\n\t\t{\"/switch_handler_opts\", switch_handler_h, hibernate}\n\t]}]).\n\n%% Internal.\n\ndo_decode(Headers, Body) ->\n\tcase lists:keyfind(<<\"content-encoding\">>, 1, Headers) of\n\t\t{_, <<\"gzip\">>} -> zlib:gunzip(Body);\n\t\t_ -> Body\n\tend.\n\n%% Tests.\n\naccept_callback_missing(Config) ->\n\tdoc(\"A 500 response must be sent when the AcceptCallback can't be called.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:put(ConnPid, \"/accept_callback_missing\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], <<\"Missing!\">>),\n\t{response, fin, 500, _} = do_maybe_h3_error(gun:await(ConnPid, Ref)),\n\tok.\n\naccept_callback_patch_false(Config) ->\n\tdo_accept_callback_false(Config, patch).\n\naccept_callback_patch_true(Config) ->\n\tdo_accept_callback_true(Config, patch).\n\naccept_callback_post_false(Config) ->\n\tdo_accept_callback_false(Config, post).\n\naccept_callback_post_true(Config) ->\n\tdo_accept_callback_true(Config, post).\n\naccept_callback_put_false(Config) ->\n\tdo_accept_callback_false(Config, put).\n\naccept_callback_put_true(Config) ->\n\tdo_accept_callback_true(Config, put).\n\ndo_accept_callback_false(Config, Fun) ->\n\tdoc(\"When AcceptCallback returns false a 400 response must be returned.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:Fun(ConnPid, \"/accept_callback?false\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], <<\"Request body.\">>),\n\t{response, _, 400, _} = gun:await(ConnPid, Ref),\n\tok.\n\ndo_accept_callback_true(Config, Fun) ->\n\tdoc(\"When AcceptCallback returns true a 204 response must be returned.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:Fun(ConnPid, \"/accept_callback?true\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], <<\"Request body.\">>),\n\t{response, _, 204, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncharset_in_content_types_provided(Config) ->\n\tdoc(\"When a charset is matched explicitly in content_types_provided, \"\n\t\t\"that charset is used and the charsets_provided callback is ignored.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charset_in_content_types_provided\", [\n\t\t{<<\"accept\">>, <<\"text/plain;charset=utf-8\">>},\n\t\t{<<\"accept-charset\">>, <<\"utf-16, utf-8;q=0.5\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"text/plain; charset=utf-8\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharset_in_content_types_provided_implicit_match(Config) ->\n\tdoc(\"When a charset is matched implicitly in content_types_provided, \"\n\t\t\"the charsets_provided callback is used to determine if the media \"\n\t\t\"type will match.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charset_in_content_types_provided_implicit\", [\n\t\t{<<\"accept\">>, <<\"text/plain;charset=utf-16\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"text/plain; charset=utf-16\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharset_in_content_types_provided_implicit_nomatch(Config) ->\n\tdoc(\"When a charset is matched implicitly in content_types_provided, \"\n\t\t\"the charsets_provided callback is used to determine if the media \"\n\t\t\"type will match. If it doesn't, try the next media type.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charset_in_content_types_provided_implicit\", [\n\t\t{<<\"accept\">>, <<\"text/plain;charset=utf-32, text/plain\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t%% We end up with the first charset listed in charsets_provided.\n\t{_, <<\"text/plain; charset=utf-8\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharset_in_content_types_provided_implicit_nomatch_error(Config) ->\n\tdoc(\"When a charset is matched implicitly in content_types_provided, \"\n\t\t\"the charsets_provided callback is used to determine if the media \"\n\t\t\"type will match. If it doesn't, and there's no other media type, \"\n\t\t\"a 406 is returned.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charset_in_content_types_provided_implicit\", [\n\t\t{<<\"accept\">>, <<\"text/plain;charset=utf-32\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 406, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncharset_in_content_types_provided_implicit_no_callback(Config) ->\n\tdoc(\"When a charset is matched implicitly in content_types_provided, \"\n\t\t\"and the charsets_provided callback is not exported, the media \"\n\t\t\"type will match but the charset will be ignored like all other \"\n\t\t\"parameters.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charset_in_content_types_provided_implicit_no_callback\", [\n\t\t{<<\"accept\">>, <<\"text/plain;charset=utf-32\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t%% The charset is ignored as if it was any other parameter.\n\t{_, <<\"text/plain\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharsets_provided_match_text(Config) ->\n\tdoc(\"When the media type is text and the charsets_provided callback exists \"\n\t\t\"and the accept-charset header was sent, the selected charset is sent \"\n\t\t\"back in the content-type of the response.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charsets_provided\", [\n\t\t{<<\"accept\">>, <<\"text/plain\">>},\n\t\t{<<\"accept-charset\">>, <<\"utf-8;q=0.5, utf-16\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"text/plain; charset=utf-16\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharsets_provided_match_other(Config) ->\n\tdoc(\"When the media type is not text and the charsets_provided callback exists \"\n\t\t\"and the accept-charset header was sent, the selected charset is not sent \"\n\t\t\"back in the content-type of the response.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charsets_provided\", [\n\t\t{<<\"accept\">>, <<\"application/json\">>},\n\t\t{<<\"accept-charset\">>, <<\"utf-8;q=0.5, utf-16\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"application/json\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharsets_provided_wildcard_text(Config) ->\n\tdoc(\"When the media type is text and the charsets_provided callback exists \"\n\t\t\"and a wildcard accept-charset header was sent, the selected charset is sent \"\n\t\t\"back in the content-type of the response.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charsets_provided\", [\n\t\t{<<\"accept\">>, <<\"text/plain\">>},\n\t\t{<<\"accept-charset\">>, <<\"*\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"text/plain; charset=utf-8\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharsets_provided_wildcard_other(Config) ->\n\tdoc(\"When the media type is not text and the charsets_provided callback exists \"\n\t\t\"and a wildcard accept-charset header was sent, the selected charset is not sent \"\n\t\t\"back in the content-type of the response.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charsets_provided\", [\n\t\t{<<\"accept\">>, <<\"application/json\">>},\n\t\t{<<\"accept-charset\">>, <<\"*\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"application/json\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharsets_provided_nomatch(Config) ->\n\tdoc(\"Regardless of the media type negotiated, if no charset is found in the \"\n\t\t\"accept-charset header match a charset configured in charsets_provided, \"\n\t\t\"then a 406 not acceptable response is sent back.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charsets_provided\", [\n\t\t{<<\"accept\">>, <<\"text/plain\">>},\n\t\t{<<\"accept-charset\">>, <<\"utf-8;q=0, iso-8859-1\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 406, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncharsets_provided_noheader_text(Config) ->\n\tdoc(\"When the media type is text and the charsets_provided callback exists \"\n\t\t\"but the accept-charset header was not sent, the first charset in the \"\n\t\t\"list is selected and sent back in the content-type of the response.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charsets_provided\", [\n\t\t{<<\"accept\">>, <<\"text/plain\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"text/plain; charset=utf-8\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharsets_provided_noheader_other(Config) ->\n\tdoc(\"When the media type is not text and the charsets_provided callback exists \"\n\t\t\"but the accept-charset header was not sent, the first charset in the \"\n\t\t\"list is selected but is not sent back in the content-type of the response.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charsets_provided\", [\n\t\t{<<\"accept\">>, <<\"application/json\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"application/json\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharsets_provided_empty(Config) ->\n\tdoc(\"Regardless of the media type negotiated, if the charsets_provided \"\n\t\t\"callback returns an empty list a 406 not acceptable response is sent back.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charsets_provided_empty\", [\n\t\t{<<\"accept\">>, <<\"text/plain\">>},\n\t\t{<<\"accept-charset\">>, <<\"utf-8q=0.5, utf-16\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 406, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncharsets_provided_empty_wildcard(Config) ->\n\tdoc(\"Regardless of the media type negotiated, if the charsets_provided \"\n\t\t\"callback returns an empty list a 406 not acceptable response is sent back.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charsets_provided_empty\", [\n\t\t{<<\"accept\">>, <<\"text/plain\">>},\n\t\t{<<\"accept-charset\">>, <<\"*\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 406, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncharsets_provided_empty_noheader(Config) ->\n\tdoc(\"Regardless of the media type negotiated, if the charsets_provided \"\n\t\t\"callback returns an empty list a 406 not acceptable response is sent back.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/charsets_provided_empty\", [\n\t\t{<<\"accept\">>, <<\"text/plain\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 406, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncontent_type_invalid(Config) ->\n\tdoc(\"An invalid content-type in a POST/PATCH/PUT request \"\n\t\t\"must be rejected with a 415 unsupported media type response. (RFC7231 6.5.13)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:put(ConnPid, \"/content_types_accepted?wildcard-param\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"text/plain, text/html\">>}\n\t]),\n\t{response, fin, 415, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncontent_types_accepted_ignore_multipart_boundary(Config) ->\n\tdoc(\"When a multipart content-type is provided for the request \"\n\t\t\"body, the boundary parameter is not expected to be returned \"\n\t\t\"from the content_types_accepted callback and will be \"\n\t\t\"automatically ignored.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:put(ConnPid, \"/content_types_accepted?multipart\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"multipart/mixed; boundary=abcdef; v=1\">>}\n\t], <<\"Not really multipart!\">>),\n\t{response, _, 204, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncontent_types_accepted_param(Config) ->\n\tdoc(\"When a parameter is returned from the content_types_accepted \"\n\t\t\"callback, and the same parameter is found in the content-type \"\n\t\t\"header, the negotiation succeeds and the request is processed.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:put(ConnPid, \"/content_types_accepted?param\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"text/plain;charset=UTF-8\">>}\n\t], \"12345\"),\n\t{response, fin, 204, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncontent_types_accepted_wildcard(Config) ->\n\tdoc(\"When a wildcard is returned from the content_types_accepted \"\n\t\t\"callback, any content-type must be accepted.\"),\n\tConnPid = gun_open(Config),\n\tRef1 = gun:put(ConnPid, \"/content_types_accepted?wildcard\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t]),\n\tgun:data(ConnPid, Ref1, fin, \"Hello world!\"),\n\t{response, fin, 204, _} = gun:await(ConnPid, Ref1),\n\tRef2 = gun:put(ConnPid, \"/content_types_accepted?wildcard\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"application/vnd.plain;charset=UTF-8\">>}\n\t]),\n\tgun:data(ConnPid, Ref2, fin, \"Hello world!\"),\n\t{response, fin, 204, _} = gun:await(ConnPid, Ref2),\n\tok.\n\ncontent_types_accepted_wildcard_param_no_content_type_param(Config) ->\n\tdoc(\"When a wildcard is returned for parameters from the \"\n\t\t\"content_types_accepted callback, a content-type header \"\n\t\t\"with no parameters must be accepted.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:put(ConnPid, \"/content_types_accepted?wildcard-param\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t]),\n\tgun:data(ConnPid, Ref, fin, \"Hello world!\"),\n\t{response, fin, 204, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncontent_types_accepted_wildcard_param_content_type_with_param(Config) ->\n\tdoc(\"When a wildcard is returned for parameters from the \"\n\t\t\"content_types_accepted callback, a content-type header \"\n\t\t\"with a parameter must be accepted.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:put(ConnPid, \"/content_types_accepted?wildcard-param\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"text/plain; charset=utf-8\">>}\n\t]),\n\tgun:data(ConnPid, Ref, fin, \"Hello world!\"),\n\t{response, fin, 204, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncontent_types_provided_invalid_type(Config) ->\n\tdoc(\"When an invalid type is returned from the \"\n\t\t\"content_types_provided callback, the \"\n\t\t\"resource is incorrect and a 500 response is expected.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/content_types_provided?invalid-type\", [\n\t\t{<<\"accept\">>, <<\"*/*\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 500, _} = do_maybe_h3_error(gun:await(ConnPid, Ref)),\n\tok.\n\ncontent_types_provided_wildcard_param_no_accept_param(Config) ->\n\tdoc(\"When a wildcard is returned for parameters from the \"\n\t\t\"content_types_provided callback, an accept header \"\n\t\t\"with no parameters must be accepted.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/content_types_provided?wildcard-param\", [\n\t\t{<<\"accept\">>, <<\"text/plain\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"[]\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\ncontent_types_provided_wildcard_param_accept_with_param(Config) ->\n\tdoc(\"When a wildcard is returned for parameters from the \"\n\t\t\"content_types_provided callback, an accept header \"\n\t\t\"with a parameter must be accepted.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/content_types_provided?wildcard-param\", [\n\t\t{<<\"accept\">>, <<\"text/plain;level=1\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"level=1\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\ncontent_types_provided_wildcard_param_accept_with_param_and_qvalue(Config) ->\n\tdoc(\"When a wildcard is returned for parameters from the \"\n\t\t\"content_types_provided callback, an accept header \"\n\t\t\"with two media types containing parameters including a \"\n\t\t\"q-value must be accepted. The q-value determines which.\"),\n\tConnPid = gun_open(Config),\n\tRef1 = gun:get(ConnPid, \"/content_types_provided?wildcard-param\", [\n\t\t{<<\"accept\">>, <<\"text/plain;level=1;q=0.8, text/plain;level=2;q=0.5\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref1),\n\t{ok, <<\"level=1\">>} = gun:await_body(ConnPid, Ref1),\n\tRef2 = gun:get(ConnPid, \"/content_types_provided?wildcard-param\", [\n\t\t{<<\"accept\">>, <<\"text/plain;level=1;q=0.5, text/plain;level=2;q=0.8\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref2),\n\t{ok, <<\"level=2\">>} = gun:await_body(ConnPid, Ref2),\n\tok.\n\ncontent_types_provided_wildcard_param_no_accept_header(Config) ->\n\tdoc(\"When a wildcard is returned for parameters from the \"\n\t\t\"content_types_provided callback, the lack of accept header \"\n\t\t\"results in the first media type returned being accepted. \"\n\t\t\"The wildcard must however not be present in the media_type \"\n\t\t\"value added to the Req object.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/content_types_provided?wildcard-param\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"[]\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\ndelete_resource_missing(Config) ->\n\tdoc(\"When a resource accepts the DELETE method and the \"\n\t\t\"delete_resource callback is not exported, the \"\n\t\t\"resource is incorrect and a 500 response is expected.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:delete(ConnPid, \"/delete_resource?missing\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 500, _} = do_maybe_h3_error(gun:await(ConnPid, Ref)),\n\tok.\n\ncreate_resource_created(Config) ->\n\tdoc(\"POST to an existing resource to create a new resource. \"\n\t\t\"When the accept callback returns {created, NewURI}, \"\n\t\t\"the expected reply is 201 Created.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, \"/create_resource?created\", [\n\t\t{<<\"content-type\">>, <<\"application/text\">>}\n\t], <<\"hello\">>, #{}),\n\t{response, _, 201, _} = gun:await(ConnPid, Ref),\n\tok.\n\ncreate_resource_see_other(Config) ->\n\tdoc(\"POST to an existing resource to create a new resource. \"\n\t\t\"When the accept callback returns {see_other, NewURI}, \"\n\t\t\"the expected reply is 303 See Other with a location header set.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, \"/create_resource?see_other\", [\n\t\t{<<\"content-type\">>, <<\"application/text\">>}\n\t], <<\"hello\">>, #{}),\n\t{response, _, 303, RespHeaders} = gun:await(ConnPid, Ref),\n\t{_, _} = lists:keyfind(<<\"location\">>, 1, RespHeaders),\n\tok.\n\nerror_on_malformed_accept(Config) ->\n\tdoc(\"A malformed Accept header must result in a 400 response.\"),\n\tdo_error_on_malformed_header(Config, <<\"accept\">>).\n\nerror_on_malformed_if_match(Config) ->\n\tdoc(\"A malformed If-Match header must result in a 400 response.\"),\n\tdo_error_on_malformed_header(Config, <<\"if-match\">>).\n\nerror_on_malformed_if_none_match(Config) ->\n\tdoc(\"A malformed If-None-Match header must result in a 400 response.\"),\n\tdo_error_on_malformed_header(Config, <<\"if-none-match\">>).\n\ndo_error_on_malformed_header(Config, Name) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{Name, <<\"bad\">>}\n\t]),\n\t{response, _, 400, _} = gun:await(ConnPid, Ref),\n\tok.\n\nexpires_binary(Config) ->\n\tdoc(\"The expires header can also be given as a binary \"\n\t\t\"to indicate a date in the past. (RFC7234 5.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/expires?binary\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"0\">>} = lists:keyfind(<<\"expires\">>, 1, Headers),\n\tok.\n\nexpires_missing(Config) ->\n\tdoc(\"The expires header must not be sent when the callback is not exported.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/expires?missing\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"expires\">>, 1, Headers),\n\tok.\n\nexpires_tuple(Config) ->\n\tdoc(\"The expires header can be given as a date tuple. (RFC7234 5.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/expires?tuple\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"Fri, 21 Sep 2012 22:36:14 GMT\">>} = lists:keyfind(<<\"expires\">>, 1, Headers),\n\tok.\n\nexpires_undefined(Config) ->\n\tdoc(\"The expires header must not be sent when undefined is returned.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/expires?undefined\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"expires\">>, 1, Headers),\n\tok.\n\ngenerate_etag_missing(Config) ->\n\tdoc(\"The etag header must not be sent when \"\n\t\t\"the generate_etag callback is not exported.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/generate_etag?missing\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"etag\">>, 1, Headers),\n\tok.\n\ngenerate_etag_undefined(Config) ->\n\tdoc(\"The etag header must not be sent when \"\n\t\t\"the generate_etag callback returns undefined.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/generate_etag?undefined\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"etag\">>, 1, Headers),\n\tok.\n\ngenerate_etag_binary_strong(Config) ->\n\tdoc(\"The etag header must be sent when the generate_etag \"\n\t\t\"callback returns a strong binary. (RFC7232 2.3)\"),\n\tdo_generate_etag(Config, \"binary-strong-quoted\",\n\t\t[], 200, {<<\"etag\">>, <<\"\\\"etag-header-value\\\"\">>}).\n\ngenerate_etag_binary_weak(Config) ->\n\tdoc(\"The etag header must be sent when the generate_etag \"\n\t\t\"callback returns a weak binary. (RFC7232 2.3)\"),\n\tdo_generate_etag(Config, \"binary-weak-quoted\",\n\t\t[], 200, {<<\"etag\">>, <<\"W/\\\"etag-header-value\\\"\">>}).\n\ngenerate_etag_invalid_binary_strong_unquoted(Config) ->\n\tdoc(\"When Cowboy cannot parse the generate_etag callback's \"\n\t\t\"return value, a 500 response is returned without the etag header.\"),\n\tdo_generate_etag(Config, \"binary-strong-unquoted\", [], 500, false).\n\ngenerate_etag_invalid_binary_weak_unquoted(Config) ->\n\tdoc(\"When Cowboy cannot parse the generate_etag callback's \"\n\t\t\"return value, a 500 response is returned without the etag header.\"),\n\tdo_generate_etag(Config, \"binary-weak-unquoted\", [], 500, false).\n\ngenerate_etag_tuple_strong(Config) ->\n\tdoc(\"The etag header must be sent when the generate_etag \"\n\t\t\"callback returns a strong tuple. (RFC7232 2.3)\"),\n\tdo_generate_etag(Config, \"tuple-strong\",\n\t\t[], 200, {<<\"etag\">>, <<\"\\\"etag-header-value\\\"\">>}).\n\ngenerate_etag_tuple_weak(Config) ->\n\tdoc(\"The etag header must be sent when the generate_etag \"\n\t\t\"callback returns a weak tuple. (RFC7232 2.3)\"),\n\tdo_generate_etag(Config, \"tuple-weak\",\n\t\t[], 200, {<<\"etag\">>, <<\"W/\\\"etag-header-value\\\"\">>}).\n\nif_none_match_binary_strong(Config) ->\n\tdoc(\"When the if-none-match header matches a strong etag, \"\n\t\t\"a 304 not modified response is returned. (RFC7232 3.2)\"),\n\tdo_generate_etag(Config, \"binary-strong-quoted\",\n\t\t[{<<\"if-none-match\">>, <<\"\\\"etag-header-value\\\"\">>}],\n\t\t304, {<<\"etag\">>, <<\"\\\"etag-header-value\\\"\">>}).\n\nif_none_match_binary_weak(Config) ->\n\tdoc(\"When the if-none-match header matches a weak etag, \"\n\t\t\"a 304 not modified response is returned. (RFC7232 3.2)\"),\n\tdo_generate_etag(Config, \"binary-weak-quoted\",\n\t\t[{<<\"if-none-match\">>, <<\"W/\\\"etag-header-value\\\"\">>}],\n\t\t304, {<<\"etag\">>, <<\"W/\\\"etag-header-value\\\"\">>}).\n\nif_none_match_tuple_strong(Config) ->\n\tdoc(\"When the if-none-match header matches a strong etag, \"\n\t\t\"a 304 not modified response is returned. (RFC7232 3.2)\"),\n\tdo_generate_etag(Config, \"tuple-strong\",\n\t\t[{<<\"if-none-match\">>, <<\"\\\"etag-header-value\\\"\">>}],\n\t\t304, {<<\"etag\">>, <<\"\\\"etag-header-value\\\"\">>}).\n\nif_none_match_tuple_weak(Config) ->\n\tdoc(\"When the if-none-match header matches a weak etag, \"\n\t\t\"a 304 not modified response is returned. (RFC7232 3.2)\"),\n\tdo_generate_etag(Config, \"tuple-weak\",\n\t\t[{<<\"if-none-match\">>, <<\"W/\\\"etag-header-value\\\"\">>}],\n\t\t304, {<<\"etag\">>, <<\"W/\\\"etag-header-value\\\"\">>}).\n\ndo_generate_etag(Config, Qs, ReqHeaders, Status, Etag) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/generate_etag?\" ++ Qs, [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t\t|ReqHeaders\n\t]),\n\t{response, _, Status, RespHeaders} = do_maybe_h3_error(gun:await(ConnPid, Ref)),\n\tEtag = lists:keyfind(<<\"etag\">>, 1, RespHeaders),\n\tok.\n\n%% See do_maybe_h3_error2 comment.\ndo_maybe_h3_error({error, {stream_error, {stream_error, h3_internal_error, _}}}) ->\n\t{response, fin, 500, []};\ndo_maybe_h3_error(Result) ->\n\tResult.\n\nif_range_etag_equal(Config) ->\n\tdoc(\"When the if-range header matches, a 206 partial content \"\n\t\t\"response is expected for an otherwise valid range request. (RFC7233 3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/if_range\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>},\n\t\t{<<\"if-range\">>, <<\"\\\"strong-and-match\\\"\">>}\n\t]),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, <<\"bytes 0-19/20\">>} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nif_range_etag_not_equal(Config) ->\n\tdoc(\"When the if-range header does not match, the range header \"\n\t\t\"must be ignored and a 200 OK response is expected for \"\n\t\t\"an otherwise valid range request. (RFC7233 3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/if_range\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>},\n\t\t{<<\"if-range\">>, <<\"\\\"strong-but-no-match\\\"\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nif_range_ignored_when_no_range_header(Config) ->\n\tdoc(\"When there is no range header the if-range header is ignored \"\n\t\t\"and a 200 OK response is expected (RFC7233 3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/if_range\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"if-range\">>, <<\"\\\"strong-and-match\\\"\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nif_range_ignored_when_ranges_provided_missing(Config) ->\n\tdoc(\"When the resource does not support range requests \"\n\t\t\"the range and if-range headers must be ignored\"\n\t\t\"and a 200 OK response is expected (RFC7233 3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/if_range?missing-ranges_provided\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>},\n\t\t{<<\"if-range\">>, <<\"\\\"strong-and-match\\\"\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nif_range_ignored_when_ranges_provided_empty(Config) ->\n\tdoc(\"When the resource does not support range requests \"\n\t\t\"the range and if-range headers must be ignored\"\n\t\t\"and a 200 OK response is expected (RFC7233 3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/if_range?empty-ranges_provided\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>},\n\t\t{<<\"if-range\">>, <<\"\\\"strong-and-match\\\"\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"none\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nif_range_weak_etag_not_equal(Config) ->\n\tdoc(\"The if-range header must not match weak etags; the range header \"\n\t\t\"must be ignored and a 200 OK response is expected for \"\n\t\t\"an otherwise valid range request. (RFC7233 3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/if_range?weak-etag\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>},\n\t\t{<<\"if-range\">>, <<\"W/\\\"weak-no-match\\\"\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nif_range_date_not_equal(Config) ->\n\tdoc(\"The if-range header must not match weak dates. Cowboy \"\n\t\t\"currently has no way of knowing whether a resource was \"\n\t\t\"updated twice within the same second. The range header \"\n\t\t\"must be ignored and a 200 OK response is expected for \"\n\t\t\"an otherwise valid range request. (RFC7233 3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/if_range\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>},\n\t\t{<<\"if-range\">>, <<\"Fri, 22 Feb 2222 11:11:11 GMT\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nlast_modified(Config) ->\n\tdoc(\"The last-modified header can be given as a date tuple. (RFC7232 2.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/last_modified?tuple\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"Fri, 21 Sep 2012 22:36:14 GMT\">>} = lists:keyfind(<<\"last-modified\">>, 1, Headers),\n\tok.\n\nlast_modified_missing(Config) ->\n\tdoc(\"The last-modified header must not be sent when the callback is not exported.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/last_modified?missing\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"last-modified\">>, 1, Headers),\n\tok.\n\nlast_modified_undefined(Config) ->\n\tdoc(\"The last-modified header must not be sent when the callback returns undefined.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/last_modified?undefined\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"last-modified\">>, 1, Headers),\n\tok.\n\noptions_missing(Config) ->\n\tdoc(\"A successful OPTIONS request to a simple handler results in \"\n\t\t\"a 200 OK response with the allow header set. (RFC7231 4.3.7)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:options(ConnPid, \"/\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, fin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"HEAD, GET, OPTIONS\">>} = lists:keyfind(<<\"allow\">>, 1, Headers),\n\tok.\n\nprovide_callback(Config) ->\n\tdoc(\"A successful GET request to a simple handler results in \"\n\t\t\"a 200 OK response with the content-type set. (RFC7231 4.3.1, RFC7231 6.3.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/\", [\n\t\t{<<\"accept\">>, <<\"*/*\">>},\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"text/plain\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t{_, <<\"HEAD, GET, OPTIONS\">>} = lists:keyfind(<<\"allow\">>, 1, Headers),\n\t{ok, <<\"This is REST!\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\nprovide_callback_missing(Config) ->\n\tdoc(\"A 500 response must be sent when the ProvideCallback can't be called.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/provide_callback_missing\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, fin, 500, _} = do_maybe_h3_error(gun:await(ConnPid, Ref)),\n\tok.\n\nprovide_range_callback(Config) ->\n\tdoc(\"A successful request for a single range results in a \"\n\t\t\"206 partial content response with content-range set. (RFC7233 4.1, RFC7233 4.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/provide_range_callback\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, <<\"bytes 0-19/20\">>} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\t{_, <<\"text/plain\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t{ok, <<\"This is ranged REST!\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\nprovide_range_callback_sendfile(Config) ->\n\tdoc(\"A successful request for a single range results in a \"\n\t\t\"206 partial content response with content-range set. (RFC7233 4.1, RFC7233 4.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/provide_range_callback?sendfile\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\tPath = code:lib_dir(cowboy) ++ \"/ebin/cowboy.app\",\n\tSize = filelib:file_size(Path),\n\t{ok, Body} = file:read_file(Path),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, ContentRange} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tContentRange = iolist_to_binary([\n\t\t<<\"bytes 0-\">>,\n\t\tinteger_to_binary(Size - 1),\n\t\t<<\"/\">>,\n\t\tinteger_to_binary(Size)\n\t]),\n\t{_, <<\"text/plain\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t{ok, Body} = gun:await_body(ConnPid, Ref),\n\tok.\n\nprovide_range_callback_multipart(Config) ->\n\tdoc(\"A successful request for multiple ranges results in a \"\n\t\t\"206 partial content response using the multipart/byteranges \"\n\t\t\"content-type and the content-range not being set. The real \"\n\t\t\"content-type and content-range of the parts can be found in \"\n\t\t\"the multipart headers. (RFC7233 4.1, RFC7233 A)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/provide_range_callback\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t%% This range selects everything except the space characters.\n\t\t{<<\"range\">>, <<\"bytes=0-3, 5-6, 8-13, 15-\">>}\n\t]),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\t{_, <<\"multipart/byteranges; boundary=\", Boundary/bits>>}\n\t\t= lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t{ok, Body0} = gun:await_body(ConnPid, Ref),\n\tBody = do_decode(Headers, Body0),\n\t{ContentRanges, BodyAcc} = do_provide_range_callback_multipart_body(Body, Boundary, [], <<>>),\n\t[\n\t\t{bytes, 0, 3, 20},\n\t\t{bytes, 5, 6, 20},\n\t\t{bytes, 8, 13, 20},\n\t\t{bytes, 15, 19, 20}\n\t] = ContentRanges,\n\t<<\"ThisisrangedREST!\">> = BodyAcc,\n\tok.\n\nprovide_range_callback_multipart_sendfile(Config) ->\n\tdoc(\"A successful request for multiple ranges results in a \"\n\t\t\"206 partial content response using the multipart/byteranges \"\n\t\t\"content-type and the content-range not being set. The real \"\n\t\t\"content-type and content-range of the parts can be found in \"\n\t\t\"the multipart headers. (RFC7233 4.1, RFC7233 A)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/provide_range_callback?sendfile\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t%% This range selects a few random chunks of the file.\n\t\t{<<\"range\">>, <<\"bytes=50-99, 150-199, 250-299, -99\">>}\n\t]),\n\tPath = code:lib_dir(cowboy) ++ \"/ebin/cowboy.app\",\n\tSize = filelib:file_size(Path),\n\tSkip = Size - 399,\n\t{ok, <<\n\t\t_:50/binary, Body1:50/binary,\n\t\t_:50/binary, Body2:50/binary,\n\t\t_:50/binary, Body3:50/binary,\n\t\t_:Skip/binary, Body4/bits>>} = file:read_file(Path),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\t{_, <<\"multipart/byteranges; boundary=\", Boundary/bits>>}\n\t\t= lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t{ok, Body0} = gun:await_body(ConnPid, Ref),\n\tBody = do_decode(Headers, Body0),\n\t%% We will receive the ranges in the same order as requested.\n\t{ContentRanges, BodyAcc} = do_provide_range_callback_multipart_body(Body, Boundary, [], <<>>),\n\tLastFrom = 300 + Skip,\n\tLastTo = Size - 1,\n\t[\n\t\t{bytes, 50, 99, Size},\n\t\t{bytes, 150, 199, Size},\n\t\t{bytes, 250, 299, Size},\n\t\t{bytes, LastFrom, LastTo, Size}\n\t] = ContentRanges,\n\tBodyAcc = <<Body1/binary, Body2/binary, Body3/binary, Body4/binary>>,\n\tok.\n\ndo_provide_range_callback_multipart_body(Rest, Boundary, ContentRangesAcc, BodyAcc) ->\n\tcase cow_multipart:parse_headers(Rest, Boundary) of\n\t\t{ok, Headers, Rest1} ->\n\t\t\t{_, ContentRange0} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\t\t\tContentRange = cow_http_hd:parse_content_range(ContentRange0),\n\t\t\t{_, <<\"text/plain\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t\t\tcase cow_multipart:parse_body(Rest1, Boundary) of\n\t\t\t\t{done, Body} ->\n\t\t\t\t\tdo_provide_range_callback_multipart_body(<<>>, Boundary,\n\t\t\t\t\t\t[ContentRange|ContentRangesAcc],\n\t\t\t\t\t\t<<BodyAcc/binary, Body/binary>>);\n\t\t\t\t{done, Body, Rest2} ->\n\t\t\t\t\tdo_provide_range_callback_multipart_body(Rest2, Boundary,\n\t\t\t\t\t\t[ContentRange|ContentRangesAcc],\n\t\t\t\t\t\t<<BodyAcc/binary, Body/binary>>)\n\t\t\tend;\n\t\t{done, <<>>} ->\n\t\t\t{lists:reverse(ContentRangesAcc), BodyAcc}\n\tend.\n\nprovide_range_callback_metadata(Config) ->\n\tdoc(\"A successful request for a single range results in a \"\n\t\t\"206 partial content response with the same headers that \"\n\t\t\"a normal 200 OK response would, like vary or etag. (RFC7233 4.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/provide_range_callback\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, _} = lists:keyfind(<<\"date\">>, 1, Headers),\n\t{_, _} = lists:keyfind(<<\"etag\">>, 1, Headers),\n\t{_, _} = lists:keyfind(<<\"expires\">>, 1, Headers),\n\t{_, _} = lists:keyfind(<<\"last-modified\">>, 1, Headers),\n\t{_, _} = lists:keyfind(<<\"vary\">>, 1, Headers),\n\t%% Also cache-control and content-location but we don't send those.\n\tok.\n\nprovide_range_callback_missing(Config) ->\n\tdoc(\"A 500 response must be sent when the ProvideRangeCallback can't be called.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/provide_range_callback?missing\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\t{response, fin, 500, _} = do_maybe_h3_error(gun:await(ConnPid, Ref)),\n\tok.\n\nrange_ignore_unknown_unit(Config) ->\n\tdoc(\"The range header must be ignored when the range unit \"\n\t\t\"is not found in ranges_provided. (RFC7233 3.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/if_range\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"chapters=1-\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nrange_ignore_when_not_modified(Config) ->\n\tdoc(\"The range header must be ignored when a conditional \"\n\t\t\"GET results in a 304 not modified response. (RFC7233 3.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/if_range\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>},\n\t\t{<<\"if-none-match\">>, <<\"\\\"strong-and-match\\\"\">>}\n\t]),\n\t{response, fin, 304, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nrange_satisfiable(Config) ->\n\tdoc(\"When the range_satisfiable callback returns true \"\n\t\t\"a 206 partial content response is expected for \"\n\t\t\"an otherwise valid range request. (RFC7233 4.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/range_satisfiable?true\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, <<\"bytes 0-19/20\">>} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nrange_not_satisfiable(Config) ->\n\tdoc(\"When the range_satisfiable callback returns false \"\n\t\t\"a 416 range not satisfiable response is expected for \"\n\t\t\"an otherwise valid range request. (RFC7233 4.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/range_satisfiable?false\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\t{response, fin, 416, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nrange_not_satisfiable_int(Config) ->\n\tdoc(\"When the range_satisfiable callback returns false \"\n\t\t\"a 416 range not satisfiable response is expected for \"\n\t\t\"an otherwise valid range request. If an integer is \"\n\t\t\"provided it is used to construct the content-range \"\n\t\t\"header. (RFC7233 4.2, RFC7233 4.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/range_satisfiable?false-int\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\t{response, fin, 416, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, <<\"bytes */123\">>} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nrange_not_satisfiable_bin(Config) ->\n\tdoc(\"When the range_satisfiable callback returns false \"\n\t\t\"a 416 range not satisfiable response is expected for \"\n\t\t\"an otherwise valid range request. If a binary is \"\n\t\t\"provided it is used to construct the content-range \"\n\t\t\"header. (RFC7233 4.2, RFC7233 4.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/range_satisfiable?false-bin\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\t{response, fin, 416, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, <<\"bytes */456\">>} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nrange_satisfiable_missing(Config) ->\n\tdoc(\"When the range_satisfiable callback is missing \"\n\t\t\"a 206 partial content response is expected for \"\n\t\t\"an otherwise valid range request. (RFC7233 4.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/range_satisfiable?missing\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\t{response, _, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, <<\"bytes \", _/bits>>} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nranges_provided_accept_ranges(Config) ->\n\tdoc(\"When the ranges_provided callback exists the accept-ranges header \"\n\t\t\"is sent in the response. (RFC7233 2.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/ranges_provided?list\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes, pages, chapters\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tok.\n\n%% @todo Probably should have options to do this automatically for auto at least.\n%%\n%%   A server that supports range requests MAY ignore or reject a Range\n%%   header field that consists of more than two overlapping ranges, or a\n%%   set of many small ranges that are not listed in ascending order,\n%%   since both are indications of either a broken client or a deliberate\n%%   denial-of-service attack (Section 6.1).\n\n%% @todo Probably should have options for auto as well to join ranges that\n%% are very close from each other.\n\nranges_provided_auto_data(Config) ->\n\tdoc(\"When the unit range is bytes and the callback is 'auto' \"\n\t\t\"Cowboy will call the normal ProvideCallback and perform \"\n\t\t\"the range calculations automatically.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/ranges_provided_auto?data\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=8-\">>}\n\t]),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, <<\"bytes 8-19/20\">>} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\t{_, <<\"text/plain\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t{ok, <<\"ranged REST!\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\nranges_provided_auto_sendfile(Config) ->\n\tdoc(\"When the unit range is bytes and the callback is 'auto' \"\n\t\t\"Cowboy will call the normal ProvideCallback and perform \"\n\t\t\"the range calculations automatically.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/ranges_provided_auto?sendfile\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=8-\">>}\n\t]),\n\tPath = code:lib_dir(cowboy) ++ \"/ebin/cowboy.app\",\n\tSize = filelib:file_size(Path),\n\t{ok, <<_:8/binary, Body/bits>>} = file:read_file(Path),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, ContentRange} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tContentRange = iolist_to_binary([\n\t\t<<\"bytes 8-\">>,\n\t\tinteger_to_binary(Size - 1),\n\t\t<<\"/\">>,\n\t\tinteger_to_binary(Size)\n\t]),\n\t{_, <<\"text/plain\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t{ok, Body} = gun:await_body(ConnPid, Ref),\n\tok.\n\nranges_provided_auto_multipart_data(Config) ->\n\tdoc(\"When the unit range is bytes and the callback is 'auto' \"\n\t\t\"Cowboy will call the normal ProvideCallback and perform \"\n\t\t\"the range calculations automatically.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/ranges_provided_auto?data\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t%% This range selects everything except the space characters.\n\t\t{<<\"range\">>, <<\"bytes=0-3, 5-6, 8-13, 15-\">>}\n\t]),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\t{_, <<\"multipart/byteranges; boundary=\", Boundary/bits>>}\n\t\t= lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t{ok, Body0} = gun:await_body(ConnPid, Ref),\n\tBody = do_decode(Headers, Body0),\n\t%% We will receive the ranges in the same order as requested.\n\t{ContentRanges, BodyAcc} = do_provide_range_callback_multipart_body(Body, Boundary, [], <<>>),\n\t[\n\t\t{bytes, 0, 3, 20},\n\t\t{bytes, 5, 6, 20},\n\t\t{bytes, 8, 13, 20},\n\t\t{bytes, 15, 19, 20}\n\t] = ContentRanges,\n\t<<\"ThisisrangedREST!\">> = BodyAcc,\n\tok.\n\nranges_provided_auto_multipart_sendfile(Config) ->\n\tdoc(\"When the unit range is bytes and the callback is 'auto' \"\n\t\t\"Cowboy will call the normal ProvideCallback and perform \"\n\t\t\"the range calculations automatically.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/ranges_provided_auto?sendfile\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t%% This range selects a few random chunks of the file.\n\t\t{<<\"range\">>, <<\"bytes=50-99, 150-199, 250-299, -99\">>}\n\t]),\n\tPath = code:lib_dir(cowboy) ++ \"/ebin/cowboy.app\",\n\tSize = filelib:file_size(Path),\n\tSkip = Size - 399,\n\t{ok, <<\n\t\t_:50/binary, Body1:50/binary,\n\t\t_:50/binary, Body2:50/binary,\n\t\t_:50/binary, Body3:50/binary,\n\t\t_:Skip/binary, Body4/bits>>} = file:read_file(Path),\n\t{response, nofin, 206, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tfalse = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\t{_, <<\"multipart/byteranges; boundary=\", Boundary/bits>>}\n\t\t= lists:keyfind(<<\"content-type\">>, 1, Headers),\n\t{ok, Body0} = gun:await_body(ConnPid, Ref),\n\tBody = do_decode(Headers, Body0),\n\t%% We will receive the ranges in the same order as requested.\n\t{ContentRanges, BodyAcc} = do_provide_range_callback_multipart_body(Body, Boundary, [], <<>>),\n\tLastFrom = 300 + Skip,\n\tLastTo = Size - 1,\n\t[\n\t\t{bytes, 50, 99, Size},\n\t\t{bytes, 150, 199, Size},\n\t\t{bytes, 250, 299, Size},\n\t\t{bytes, LastFrom, LastTo, Size}\n\t] = ContentRanges,\n\tBodyAcc = <<Body1/binary, Body2/binary, Body3/binary, Body4/binary>>,\n\tok.\n\nranges_provided_auto_not_satisfiable_data(Config) ->\n\tdoc(\"When the unit range is bytes and the callback is 'auto' \"\n\t\t\"Cowboy will call the normal ProvideCallback and perform \"\n\t\t\"the range calculations automatically. When the requested \"\n\t\t\"range is not satisfiable a 416 range not satisfiable response \"\n\t\t\"is expected. The content-range header will be set. (RFC7233 4.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/ranges_provided_auto?data\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=1000-\">>}\n\t]),\n\t{response, fin, 416, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, <<\"bytes */20\">>} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nranges_provided_auto_not_satisfiable_sendfile(Config) ->\n\tdoc(\"When the unit range is bytes and the callback is 'auto' \"\n\t\t\"Cowboy will call the normal ProvideCallback and perform \"\n\t\t\"the range calculations automatically. When the requested \"\n\t\t\"range is not satisfiable a 416 range not satisfiable response \"\n\t\t\"is expected. The content-range header will be set. (RFC7233 4.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/ranges_provided_auto?sendfile\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=1000-\">>}\n\t]),\n\t{response, fin, 416, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tPath = code:lib_dir(cowboy) ++ \"/ebin/cowboy.app\",\n\tSize = filelib:file_size(Path),\n\tContentRange = iolist_to_binary([<<\"bytes */\">>, integer_to_binary(Size)]),\n\t{_, ContentRange} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\tok.\n\nranges_provided_empty_accept_ranges_none(Config) ->\n\tdoc(\"When the ranges_provided callback exists but returns an empty list \"\n\t\t\"the accept-ranges header is sent in the response with the value none. (RFC7233 2.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/ranges_provided?none\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"none\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tok.\n\nranges_provided_missing_no_accept_ranges(Config) ->\n\tdoc(\"When the ranges_provided callback does not exist \"\n\t\t\"the accept-ranges header is not sent in the response.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/ranges_provided?missing\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\tok.\n\nrate_limited(Config) ->\n\tdoc(\"A 429 response must be sent when the rate_limited callback returns true. \"\n\t\t\"The retry-after header is specified as an integer. (RFC6585 4, RFC7231 7.1.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/rate_limited?true\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, fin, 429, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"3600\">>} = lists:keyfind(<<\"retry-after\">>, 1, Headers),\n\tok.\n\nrate_limited_datetime(Config) ->\n\tdoc(\"A 429 response must be sent when the rate_limited callback returns true. \"\n\t\t\"The retry-after header is specified as a date/time tuple. (RFC6585 4, RFC7231 7.1.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/rate_limited?true-date\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, fin, 429, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"Fri, 22 Feb 2222 11:11:11 GMT\">>} = lists:keyfind(<<\"retry-after\">>, 1, Headers),\n\tok.\n\nrate_not_limited(Config) ->\n\tdoc(\"A success response must be sent when the rate_limited callback returns false.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/rate_limited?false\", [{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstop_handler_allowed_methods(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_allow_missing_post(Config) ->\n\tdo_req_body_stop_handler(Config, post, ?FUNCTION_NAME).\n\nstop_handler_charsets_provided(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_content_types_accepted(Config) ->\n\tdo_req_body_stop_handler(Config, post, ?FUNCTION_NAME).\n\nstop_handler_content_types_provided(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_delete_completed(Config) ->\n\tdo_no_body_stop_handler(Config, delete, ?FUNCTION_NAME).\n\nstop_handler_delete_resource(Config) ->\n\tdo_no_body_stop_handler(Config, delete, ?FUNCTION_NAME).\n\nstop_handler_forbidden(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_is_authorized(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_is_conflict(Config) ->\n\tdo_req_body_stop_handler(Config, put, ?FUNCTION_NAME).\n\nstop_handler_known_methods(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_languages_provided(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_malformed_request(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_moved_permanently(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_moved_temporarily(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_multiple_choices(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_options(Config) ->\n\tdo_no_body_stop_handler(Config, options, ?FUNCTION_NAME).\n\nstop_handler_previously_existed(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_range_satisfiable(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_ranges_provided(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_rate_limited(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_resource_exists(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_service_available(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_uri_too_long(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_valid_content_headers(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_valid_entity_length(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_accept(Config) ->\n\tdo_req_body_stop_handler(Config, post, ?FUNCTION_NAME).\n\nstop_handler_provide(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\nstop_handler_provide_range(Config) ->\n\tdo_no_body_stop_handler(Config, get, ?FUNCTION_NAME).\n\ndo_no_body_stop_handler(Config, Method, StateName0) ->\n\tdoc(\"Send a response manually and stop the REST handler.\"),\n\tConnPid = gun_open(Config),\n\t\"stop_handler_\" ++ StateName = atom_to_list(StateName0),\n\tRef = gun:Method(ConnPid, \"/stop_handler?\" ++ StateName, [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\t{response, fin, 248, _} = gun:await(ConnPid, Ref),\n\tok.\n\ndo_req_body_stop_handler(Config, Method, StateName0) ->\n\tdoc(\"Send a response manually and stop the REST handler.\"),\n\tConnPid = gun_open(Config),\n\t\"stop_handler_\" ++ StateName = atom_to_list(StateName0),\n\tRef = gun:Method(ConnPid, \"/stop_handler?\" ++ StateName, [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], <<\"Hocus PocuSwitch!\">>),\n\t{response, fin, 248, _} = gun:await(ConnPid, Ref),\n\tok.\n\nswitch_handler_allowed_methods(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_allow_missing_post(Config) ->\n\tdo_req_body_switch_handler(Config, post, ?FUNCTION_NAME).\n\nswitch_handler_charsets_provided(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_content_types_accepted(Config) ->\n\tdo_req_body_switch_handler(Config, post, ?FUNCTION_NAME).\n\nswitch_handler_content_types_provided(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_delete_completed(Config) ->\n\tdo_no_body_switch_handler(Config, delete, ?FUNCTION_NAME).\n\nswitch_handler_delete_resource(Config) ->\n\tdo_no_body_switch_handler(Config, delete, ?FUNCTION_NAME).\n\nswitch_handler_forbidden(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_is_authorized(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_is_conflict(Config) ->\n\tdo_req_body_switch_handler(Config, put, ?FUNCTION_NAME).\n\nswitch_handler_known_methods(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_languages_provided(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_malformed_request(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_moved_permanently(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_moved_temporarily(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_multiple_choices(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_options(Config) ->\n\tdo_no_body_switch_handler(Config, options, ?FUNCTION_NAME).\n\nswitch_handler_previously_existed(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_range_satisfiable(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_ranges_provided(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_rate_limited(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_resource_exists(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_service_available(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_uri_too_long(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_valid_content_headers(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_valid_entity_length(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_accept(Config) ->\n\tdo_req_body_switch_handler(Config, post, ?FUNCTION_NAME).\n\nswitch_handler_provide(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\nswitch_handler_provide_range(Config) ->\n\tdo_no_body_switch_handler(Config, get, ?FUNCTION_NAME).\n\ndo_no_body_switch_handler(Config, Method, StateName0) ->\n\tdoc(\"Switch REST to loop handler for streaming the response body, \"\n\t\t\"with and without options.\"),\n\t\"switch_handler_\" ++ StateName = atom_to_list(StateName0),\n\tdo_no_body_switch_handler1(Config, Method, \"/switch_handler?\" ++ StateName),\n\tdo_no_body_switch_handler1(Config, Method, \"/switch_handler_opts?\" ++ StateName).\n\ndo_no_body_switch_handler1(Config, Method, Path) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:Method(ConnPid, Path, [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"range\">>, <<\"bytes=0-\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{ok, Body} = gun:await_body(ConnPid, Ref),\n\t<<\"Hello\\nstreamed\\nworld!\\n\">> = do_decode(Headers, Body),\n\tok.\n\ndo_req_body_switch_handler(Config, Method, StateName0) ->\n\tdoc(\"Switch REST to loop handler for streaming the response body, \"\n\t\t\"with and without options.\"),\n\t\"switch_handler_\" ++ StateName = atom_to_list(StateName0),\n\tdo_req_body_switch_handler1(Config, Method, \"/switch_handler?\" ++ StateName),\n\tdo_req_body_switch_handler1(Config, Method, \"/switch_handler_opts?\" ++ StateName).\n\ndo_req_body_switch_handler1(Config, Method, Path) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:Method(ConnPid, Path, [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], <<\"Hocus PocuSwitch!\">>),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{ok, Body} = gun:await_body(ConnPid, Ref),\n\t<<\"Hello\\nstreamed\\nworld!\\n\">> = do_decode(Headers, Body),\n\tok.\n"
  },
  {
    "path": "test/rfc6585_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(rfc6585_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{\"[...]\", [\n\t\t{\"/resp/:key[/:arg]\", resp_h, []}\n\t]}]).\n\nstatus_code_428(Config) ->\n\tdoc(\"The 428 Precondition Required status code can be sent. (RFC6585 3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/428\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 428, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_429(Config) ->\n\tdoc(\"The 429 Too Many Requests status code can be sent. (RFC6585 4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/429\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 429, _} = gun:await(ConnPid, Ref),\n\tok.\n\n%% @todo\n%   The (429) response MAY include a Retry-After header indicating how long\n%   to wait before making a new request. (RFC6585 4)\n\nstatus_code_431(Config) ->\n\tdoc(\"The 431 Request Header Fields Too Large status code can be sent. (RFC6585 5)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/431\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 431, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_511(Config) ->\n\tdoc(\"The 511 Network Authentication Required status code can be sent. (RFC6585 6)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/511\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 511, _} = gun:await(ConnPid, Ref),\n\tok.\n"
  },
  {
    "path": "test/rfc7230_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(rfc7230_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n-import(cowboy_test, [gun_down/1]).\n-import(cowboy_test, [raw_open/1]).\n-import(cowboy_test, [raw_send/2]).\n-import(cowboy_test, [raw_recv_head/1]).\n-import(cowboy_test, [raw_recv_rest/3]).\n-import(cowboy_test, [raw_recv/3]).\n\nsuite() ->\n\t[{timetrap, 30000}].\n\nall() -> [{group, http}].\n\ngroups() -> [{http, [parallel], ct_helper:all(?MODULE)}].\n\ninit_per_group(Name = http, Config) ->\n\tcowboy_test:init_http(Name = http, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))},\n\t\tmax_keepalive => 100\n\t}, Config).\n\nend_per_group(Name, _) ->\n\tok = cowboy:stop_listener(Name).\n\ninit_routes(_) -> [\n\t{\"localhost\", [\n\t\t{\"/\", hello_h, []},\n\t\t{\"/echo/:key[/:arg]\", echo_h, []},\n\t\t{\"/full/:key[/:arg]\", echo_h, []},\n\t\t{\"/length/echo/:key\", echo_h, []},\n\t\t{\"/resp/:key[/:arg]\", resp_h, []},\n\t\t{\"/send_message\", send_message_h, []},\n\t\t{\"*\", asterisk_h, []}\n\t]},\n\t{\"127.0.0.1\", [{\"/echo/:key\", echo_h, []}]},\n\t{\"example.org\", [{\"/echo/:key\", echo_h, []}]}\n%% @todo Add IPv6 addresses support to the router. This fails:\n%%\t{\"[2001:db8:85a3::8a2e:370:7334]\", [{\"/echo/:key\", echo_h, []}]}\n].\n\ndo_raw(Config, Data) ->\n\tClient = raw_open(Config),\n\tok = raw_send(Client, Data),\n\t{Version, Code, Reason, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{Headers, Rest2} = cow_http:parse_headers(Rest),\n\tcase lists:keyfind(<<\"content-length\">>, 1, Headers) of\n\t\t{_, LengthBin} when LengthBin =/= <<\"0\">> ->\n\t\t\tBody = raw_recv_rest(Client, binary_to_integer(LengthBin), Rest2),\n\t\t\t#{client => Client, version => Version, code => Code, reason => Reason, headers => Headers, body => Body};\n\t\t_ ->\n\t\t\t#{client => Client, version => Version, code => Code, reason => Reason, headers => Headers, body => <<>>}\n\tend.\n\n%% Listener.\n\n%% @todo Add to documentation.\n%The default port for \"http\" connections is 80. The connection\n%uses plain TCP. (RFC7230 2.7.1)\n%\n%The default port for \"https\" connections is 443. The connection\n%uses TLS. (RFC7230 2.7.2)\n%\n%Any other port may be used for either of them.\n\n%% Before the request.\n\naccept_at_least_1_empty_line(Config) ->\n\tdoc(\"A configurable number of empty lines (CRLF) preceding the request \"\n\t\t\"must be ignored. At least 1 empty line must be ignored. (RFC7230 3.5)\"),\n\t#{code := 200} = do_raw(Config,\n\t\t\"\\r\\n\"\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nreject_response(Config) ->\n\tdoc(\"When receiving a response instead of a request, identified by the \"\n\t\t\"status-line which starts with the HTTP version, the server must \"\n\t\t\"reject the message with a 400 status code and close the connection. (RFC7230 3.1)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"HTTP/1.1 200 OK\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% Request.\n\nonly_parse_necessary_elements(Config) ->\n\tdoc(\"It is only necessary to parse elements required to process the request. (RFC7230 2.5)\"),\n\t#{code := 200} = do_raw(Config,\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-type: purposefully bad header value\\r\\n\"\n\t\t\"\\r\\n\").\n\n%% @todo Add to documentation.\n%Parsed elements are subject to configurable limits. A server must\n%be able to parse elements at least as long as it generates. (RFC7230 2.5)\n\nno_empty_line_after_request_line(Config) ->\n\tdoc(\"The general format of HTTP requests is strict. No empty line is \"\n\t\t\"allowed in-between components except for the empty line \"\n\t\t\"indicating the end of the list of headers.\"),\n\t#{code := 400} = do_raw(Config,\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nno_empty_line_in_headers(Config) ->\n\tdoc(\"The general format of HTTP requests is strict. No empty line is \"\n\t\t\"allowed in-between components except for the empty line \"\n\t\t\"indicating the end of the list of headers.\"),\n\t#{code := 400} = do_raw(Config,\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"User-Agent: RFC7230\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\ntimeout_before_request_line(Config) ->\n\tdoc(\"The time the request (request line and headers) takes to be \"\n\t\t\"received by the server must be limited and subject to configuration. \"\n\t\t\"No response must be sent before closing if no request was initiated \"\n\t\t\"by the reception of a complete request-line.\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, \"GET / HTTP/1.1\\r\"),\n\t{error, closed} = raw_recv(Client, 0, 6000).\n\ntimeout_after_request_line(Config) ->\n\tdoc(\"The time the request (request line and headers) takes to be \"\n\t\t\"received by the server must be limited and subject to configuration. \"\n\t\t\"A 408 status code must be sent if the request line was received.\"),\n\t#{code := 408, client := Client1} = do_raw(Config, \"GET / HTTP/1.1\\r\\n\"),\n\t{error, closed} = raw_recv(Client1, 0, 6000).\n\ntimeout_after_request_line_host(Config) ->\n\tdoc(\"The time the request (request line and headers) takes to be \"\n\t\t\"received by the server must be limited and subject to configuration. \"\n\t\t\"A 408 status code must be sent if the request line was received.\"),\n\t#{code := 408, client := Client2} = do_raw(Config, \"GET / HTTP/1.1\\r\\nHost: localhost\"),\n\t{error, closed} = raw_recv(Client2, 0, 6000).\n\ntimeout_after_request_line_host_crlf(Config) ->\n\tdoc(\"The time the request (request line and headers) takes to be \"\n\t\t\"received by the server must be limited and subject to configuration. \"\n\t\t\"A 408 status code must be sent if the request line was received.\"),\n\t#{code := 408, client := Client3} = do_raw(Config, \"GET / HTTP/1.1\\r\\nHost: localhost\\r\\n\"),\n\t{error, closed} = raw_recv(Client3, 0, 6000).\n\ntimeout_after_request_line_host_crlfcr(Config) ->\n\tdoc(\"The time the request (request line and headers) takes to be \"\n\t\t\"received by the server must be limited and subject to configuration. \"\n\t\t\"A 408 status code must be sent if the request line was received.\"),\n\t#{code := 408, client := Client4} = do_raw(Config, \"GET / HTTP/1.1\\r\\nHost: localhost\\r\\n\\r\"),\n\t{error, closed} = raw_recv(Client4, 0, 6000).\n\n%% Request line.\n\nlimit_request_line_8000(Config) ->\n\tdoc(\"It is recommended to limit the request-line length to a configurable \"\n\t\t\"limit of at least 8000 octets.\"),\n\tLongPath = [\"/long-path\" || _ <- lists:seq(1, 799)],\n\t#{code := 200} = do_raw(Config, [\n\t\t\"GET /?qs=\", LongPath, \" HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]).\n\nlimit_request_line_9000(Config) ->\n\tdoc(\"It is recommended to limit the request-line length to a configurable \"\n\t\t\"limit of at least 8000 octets. A request line too long must be rejected \"\n\t\t\"with a 414 status code and the closing of the connection. (RFC7230 3.1.1)\"),\n\tLongPath = [\"/long-path\" || _ <- lists:seq(1, 899)],\n\t#{code := 414, client := Client} = do_raw(Config, [\n\t\t\"GET /very\", LongPath, \" HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% Method.\n\nreject_invalid_method(Config) ->\n\tdoc(\"The request method is defined as 1+ token characters. An invalid \"\n\t\t\"method must be rejected with a 400 status code and the \"\n\t\t\"closing of the connection. (RFC7230 3.1.1, RFC7230 3.2.6)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET\\0 / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nreject_empty_method(Config) ->\n\tdoc(\"The request method is defined as 1+ token characters. An empty \"\n\t\t\"method must be rejected with a 400 status code and the \"\n\t\t\"closing of the connection. (RFC7230 3.1.1, RFC7230 3.2.6)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\" / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% @todo We probably want to directly match commonly used methods.\n%In practice the only characters in use by registered methods are\n%uppercase letters [A-Z] and the dash \"-\". (IANA HTTP Method Registry)\n\nlimit_method_name(Config) ->\n\tdoc(\"The length of the method must be subject to a configurable limit. \"\n\t\t\"A method too long must be rejected with a 501 status code and the \"\n\t\t\"closing of the connection. A good default for the method length limit \"\n\t\t\"is the longest method length the server implements. (RFC7230 3.1.1)\"),\n\tLongMethod = [$G || _ <- lists:seq(1, 1000)],\n\t#{code := 501, client := Client} = do_raw(Config, [\n\t\tLongMethod, \" / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% Between method and request-target.\n\nreject_tab_between_method_and_request_target(Config) ->\n\tdoc(\"A request that uses anything other than SP as separator between \"\n\t\t\"the method and the request-target must be rejected with a 400 \"\n\t\t\"status code and the closing of the connection. (RFC7230 3.1.1, RFC7230 3.5)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET\\t/ HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nreject_two_sp_between_method_and_request_target(Config) ->\n\tdoc(\"A request that uses anything other than SP as separator between \"\n\t\t\"the method and the request-target must be rejected with a 400 \"\n\t\t\"status code and the closing of the connection. (RFC7230 3.1.1, RFC7230 3.5)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET  / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% Request target.\n\nignore_uri_fragment_after_path(Config) ->\n\tdoc(\"The fragment part of the target URI is not sent. It must be \"\n\t\t\"ignored by a server receiving it. (RFC7230 5.1)\"),\n\tEcho = <<\"http://localhost/echo/uri\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET /echo/uri#fragment HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nignore_uri_fragment_after_query(Config) ->\n\tdoc(\"The fragment part of the target URI is not sent. It must be \"\n\t\t\"ignored by a server receiving it. (RFC7230 5.1)\"),\n\tEcho = <<\"http://localhost/echo/uri?key=value\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET /echo/uri?key=value#fragment HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\n%% Request target: origin-form.\n\nmust_understand_origin_form(Config) ->\n\tdoc(\"A server must be able to handle at least origin-form and absolute-form. (RFC7230 5.3.2)\"),\n\t#{code := 200} = do_raw(Config,\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\n%% @todo Reenable this test once support for CONNECT is added.\n%origin_form_reject_if_connect(Config) ->\n%\tdoc(\"origin-form is used when the client does not connect to a proxy, \"\n%\t\t\"does not use the CONNECT method and does not issue a site-wide \"\n%\t\t\"OPTIONS request. (RFC7230 5.3.1)\"),\n%\t#{code := 400, client := Client} = do_raw(Config,\n%\t\t\"CONNECT / HTTP/1.1\\r\\n\"\n%\t\t\"Host: localhost\\r\\n\"\n%\t\t\"\\r\\n\"),\n%\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% @todo Equivalent test for https.\norigin_form_tcp_scheme(Config) ->\n\tdoc(\"The scheme is either resolved from configuration or is \\\"https\\\" \"\n\t\t\"when on a TLS connection and \\\"http\\\" otherwise. (RFC7230 5.5)\"),\n\tEcho = <<\"http://localhost/echo/uri\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET /echo/uri HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\norigin_form_path(Config) ->\n\tdoc(\"The absolute-path always starts with \\\"/\\\" and ends with either \\\"?\\\", \\\"#\\\" \"\n\t\t\"or the end of the URI. (RFC3986 3.3)\"),\n\tEcho = <<\"/echo/path\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET /echo/path HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\norigin_form_path_query(Config) ->\n\tdoc(\"The absolute-path always starts with \\\"/\\\" and ends with either \\\"?\\\", \\\"#\\\" \"\n\t\t\"or the end of the URI. (RFC3986 3.3)\"),\n\tEcho = <<\"/echo/path\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET /echo/path?key=value HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\norigin_form_path_fragment(Config) ->\n\tdoc(\"The absolute-path always starts with \\\"/\\\" and ends with either \\\"?\\\", \\\"#\\\" \"\n\t\t\"or the end of the URI. (RFC3986 3.3)\"),\n\tEcho = <<\"/echo/path\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET /echo/path#fragment HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\norigin_form_query(Config) ->\n\tdoc(\"The query starts with \\\"?\\\" and ends with \\\"#\\\" or the end of the URI. (RFC3986 3.4)\"),\n\tEcho = <<\"key=value\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET /echo/qs?key=value HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\norigin_form_query_fragment(Config) ->\n\tdoc(\"The query starts with \\\"?\\\" and ends with \\\"#\\\" or the end of the URI. (RFC3986 3.4)\"),\n\tEcho = <<\"key=value\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET /echo/qs?key=value#fragment HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\n%% @todo origin_form: reject paths with too large depth or query strings with too many keys\n\n%% Request target: absolute-form.\n\nmust_understand_absolute_form(Config) ->\n\tdoc(\"A server must be able to handle at least origin-form and absolute-form. (RFC7230 5.3.2)\"),\n\t#{code := 200} = do_raw(Config,\n\t\t\"GET http://localhost HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_case_insensitive_scheme(Config) ->\n\tdoc(\"The scheme is case insensitive and normally provided in lowercase. (RFC7230 2.7.3)\"),\n\tEcho = <<\"http://localhost/echo/uri\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET HttP://localhost/echo/uri HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_case_insensitive_host(Config) ->\n\tdoc(\"The host is case insensitive and normally provided in lowercase. (RFC7230 2.7.3)\"),\n\tEcho = <<\"http://localhost/echo/uri\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET http://LoCaLHOsT/echo/uri HTTP/1.1\\r\\n\"\n\t\t\"Host: LoCaLHOsT\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_reject_unknown_schemes(Config) ->\n\tdoc(\"Unknown schemes must be rejected with a 400 status code and the closing of the connection.\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET bad://localhost/ HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% @todo Equivalent test for https.\nabsolute_form_drop_scheme_tcp(Config) ->\n\tdoc(\"The scheme provided with the request must be dropped. The effective \"\n\t\t\"scheme is either resolved from configuration or is \\\"https\\\" when on \"\n\t\t\"a TLS connection and \\\"http\\\" otherwise. (RFC7230 5.5)\"),\n\tEcho = <<\"http://localhost/echo/uri\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET https://localhost/echo/uri HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_reject_userinfo(Config) ->\n\tdoc(\"An authority component with a userinfo component (and its \"\n\t\t\"\\\"@\\\" delimiter) is invalid. The request must be rejected with \"\n\t\t\"a 400 status code and the closing of the connection. (RFC7230 2.7.1)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET http://username:password@localhost HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nabsolute_form_reject_missing_host_without_path(Config) ->\n\tdoc(\"A URI with a missing host identifier is invalid. The request must \"\n\t\t\"be rejected with a 400 status code and the closing of the connection. (RFC7230 2.7.1)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET http:// HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nabsolute_form_reject_missing_host_with_path(Config) ->\n\tdoc(\"A URI with a missing host identifier is invalid. The request must \"\n\t\t\"be rejected with a 400 status code and the closing of the connection. (RFC7230 2.7.1)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET http:/// HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nabsolute_form_ipv4(Config) ->\n\tdoc(\"Absolute form with an IPv4 address for the host. (RFC3986 3.2.2)\"),\n\tEcho = <<\"127.0.0.1\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET http://127.0.0.1/echo/host HTTP/1.1\\r\\n\"\n\t\t\"Host: 127.0.0.1\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_ipv4_port(Config) ->\n\tdoc(\"Absolute form with an IPv4 address for the host and a port number. (RFC3986 3.2.2)\"),\n\tHost = <<\"127.0.0.1\">>,\n\t#{code := 200, body := Host} = do_raw(Config,\n\t\t\"GET http://127.0.0.1:8080/echo/host HTTP/1.1\\r\\n\"\n\t\t\"Host: 127.0.0.1:8080\\r\\n\"\n\t\t\"\\r\\n\"),\n\tPort = <<\"8080\">>,\n\t#{code := 200, body := Port} = do_raw(Config,\n\t\t\"GET http://127.0.0.1:8080/echo/port HTTP/1.1\\r\\n\"\n\t\t\"Host: 127.0.0.1:8080\\r\\n\"\n\t\t\"\\r\\n\").\n\n%% @todo We need the router to support IPv6 addresses to write proper tests for these:\n%absolute_form_ipv6(Config) ->\n%absolute_form_ipv6_ipv4(Config) ->\n%absolute_form_ipv6_zoneid(Config) ->\n\nabsolute_form_reg_name(Config) ->\n\tdoc(\"Absolute form with a regular name for the host. (RFC3986 3.2.2)\"),\n\tEcho = <<\"example.org\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET http://example.org/echo/host HTTP/1.1\\r\\n\"\n\t\t\"Host: example.org\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_reg_name_port(Config) ->\n\tdoc(\"Absolute form with an IPv4 address for the host and a port number. (RFC3986 3.2.2)\"),\n\tHost = <<\"example.org\">>,\n\t#{code := 200, body := Host} = do_raw(Config,\n\t\t\"GET http://example.org:8080/echo/host HTTP/1.1\\r\\n\"\n\t\t\"Host: example.org:8080\\r\\n\"\n\t\t\"\\r\\n\"),\n\tPort = <<\"8080\">>,\n\t#{code := 200, body := Port} = do_raw(Config,\n\t\t\"GET http://example.org:8080/echo/port HTTP/1.1\\r\\n\"\n\t\t\"Host: example.org:8080\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_limit_host(Config) ->\n\tdoc(\"The maximum length for the host component of the URI must be subject \"\n\t\t\"to a configurable limit. A good default is 255 characters. \"\n\t\t\"(RFC7230 3.1.1, RFC3986 3.2.2, RFC1034 3.1)\"),\n\tLongHost = [\"host.\" || _ <- lists:seq(1, 100)],\n\t#{code := 414, client := Client} = do_raw(Config, [\n\t\t\"GET http://\", LongHost, \"/ HTTP/1.1\\r\\n\"\n\t\t\"Host: \", LongHost, \"\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nabsolute_form_invalid_port_0(Config) ->\n\tdoc(\"Port number 0 is reserved. The request must be rejected and the connection closed.\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET http://localhost:0/ HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost:0\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nabsolute_form_invalid_port_65536(Config) ->\n\tdoc(\"Port numbers above 65535 are invalid. The request must be rejected \"\n\t\t\"and the connection closed.\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET http://localhost:65536/ HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost:65536\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% @todo The RFC says to discard the Host header if we are a proxy,\n%% and replace it with the content of absolute-form. This means\n%% that we should probably keep the absolute-form value when\n%% operating in proxy mode. Otherwise the absolute-form value\n%% is simply dropped and the Host header is used.\n\n%% @todo The authority is sent both in the URI and in the host header.\n%% The authority from the URI must be dropped, and the host header\n%% must be used instead. (RFC7230 5.5)\n%%\n%% It is not possible to test that the absolute-form value is dropped\n%% because one of the Host header test ensures that the authority\n%% is the same in both, and errors out otherwise.\n\nabsolute_form_path(Config) ->\n\tdoc(\"The path always starts with \\\"/\\\" and ends with either \\\"?\\\", \\\"#\\\" \"\n\t\t\"or the end of the URI. (RFC3986 3.3)\"),\n\tEcho = <<\"/echo/path\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET http://localhost/echo/path HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_path_query(Config) ->\n\tdoc(\"The path always starts with \\\"/\\\" and ends with either \\\"?\\\", \\\"#\\\" \"\n\t\t\"or the end of the URI. (RFC3986 3.3)\"),\n\tEcho = <<\"/echo/path\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET http://localhost/echo/path?key=value HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_path_fragment(Config) ->\n\tdoc(\"The path always starts with \\\"/\\\" and ends with either \\\"?\\\", \\\"#\\\" \"\n\t\t\"or the end of the URI. (RFC3986 3.3)\"),\n\tEcho = <<\"/echo/path\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET http://localhost/echo/path#fragment HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_no_path(Config) ->\n\tdoc(\"An empty path component is equivalent to \\\"/\\\". (RFC7230 2.7.3)\"),\n\t#{code := 200, body := <<\"Hello world!\">>} = do_raw(Config,\n\t\t\"GET http://localhost HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_no_path_then_query(Config) ->\n\tdoc(\"An empty path component is equivalent to \\\"/\\\". (RFC7230 2.7.3)\"),\n\t#{code := 200, body := <<\"Hello world!\">>} = do_raw(Config,\n\t\t\"GET http://localhost?key=value HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_no_path_then_fragment(Config) ->\n\tdoc(\"An empty path component is equivalent to \\\"/\\\". (RFC7230 2.7.3)\"),\n\t#{code := 200, body := <<\"Hello world!\">>} = do_raw(Config,\n\t\t\"GET http://localhost#fragment HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_query(Config) ->\n\tdoc(\"The query starts with \\\"?\\\" and ends with \\\"#\\\" or the end of the URI. (RFC3986 3.4)\"),\n\tEcho = <<\"key=value\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET http://localhost/echo/qs?key=value HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nabsolute_form_query_fragment(Config) ->\n\tdoc(\"The query starts with \\\"?\\\" and ends with \\\"#\\\" or the end of the URI. (RFC3986 3.4)\"),\n\tEcho = <<\"key=value\">>,\n\t#{code := 200, body := Echo} = do_raw(Config,\n\t\t\"GET http://localhost/echo/qs?key=value#fragment HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\n%% @todo absolute_form: reject paths with too large depth or query strings with too many keys\n\n%% Request-target: authority-form.\n\nauthority_form_reject_if_not_connect(Config) ->\n\tdoc(\"When the method is CONNECT, authority-form must be used. This \"\n\t\t\"form does not apply to any other methods which must reject the \"\n\t\t\"request with a 400 status code and the closing of the connection. (RFC7230 5.3.3)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET localhost:80 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% @todo Implement CONNECT.\n%authority_form_reject_userinfo(Config) ->\n%An authority component with a userinfo component (and its\n%\"@\" delimiter) is invalid. The request must be rejected with\n%a 400 status code and the closing of the connection. (RFC7230 2.7.1)\n%\n%authority_form_limit_host(Config) ->\n%authority_form_limit_port0(Config) ->\n%authority_form_limit_port65536(Config) ->\n%\n%A request with a too long component of authority-form must be rejected with\n%a 414 status code and the closing of the connection. (RFC7230 3.1.1)\n%\n%The authority is either resolved from configuration or is taken\n%directly from authority-form. (RFC7230 5.5)\n%\n%authority_form_empty_path(Config) ->\n%authority_form_empty_query(Config) ->\n%The path and query are empty when using authority-form. (RFC7230 5.5)\n\n%% Request-target: asterisk-form.\n\nasterisk_form_reject_if_not_options(Config) ->\n\tdoc(\"asterisk-form is used for server-wide OPTIONS requests. \"\n\t\t\"It is invalid with any other methods which must reject the \"\n\t\t\"request with a 400 status code and the closing of the connection. (RFC7230 5.3.4)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET * HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nasterisk_form_empty_path_query(Config) ->\n\tdoc(\"The path and query components are empty when using asterisk-form. (RFC7230 5.5)\"),\n\t#{code := 200, body := <<\"http://localhost\">>} = do_raw(Config,\n\t\t\"OPTIONS * HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"X-Echo: uri\\r\\n\"\n\t\t\"\\r\\n\").\n\n%% Invalid request-target.\n\ninvalid_request_target(Config) ->\n\tdoc(\"Any other form is invalid and must be rejected with a 400 status code \"\n\t\t\"and the closing of the connection.\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET \\0 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nmissing_request_target(Config) ->\n\tdoc(\"The lack of request target must be rejected with a 400 status code \"\n\t\t\"and the closing of the connection.\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET  HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% Between request-target and version.\n\nreject_tab_between_request_target_and_version(Config) ->\n\tdoc(\"A request that uses anything other than SP as separator between \"\n\t\t\"the request-target and the version must be rejected with a 400 \"\n\t\t\"status code and the closing of the connection. (RFC7230 3.1.1, RFC7230 3.5)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET /\\tHTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nreject_two_sp_between_request_target_and_version(Config) ->\n\tdoc(\"A request that uses anything other than SP as separator between \"\n\t\t\"the request-target and the version must be rejected with a 400 \"\n\t\t\"status code and the closing of the connection. (RFC7230 3.1.1, RFC7230 3.5)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET /  HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% Request version.\n\nreject_invalid_version_http09(Config) ->\n\tdoc(\"Any version number other than HTTP/1.0 or HTTP/1.1 must be \"\n\t\t\"rejected by a server or intermediary with a 505 status code. (RFC7230 2.6, RFC7230 A.2)\"),\n\t#{code := 505} = do_raw(Config,\n\t\t\"GET / HTTP/0.9\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nreject_invalid_version_http100(Config) ->\n\tdoc(\"Any version number other than HTTP/1.0 or HTTP/1.1 must be \"\n\t\t\"rejected by a server or intermediary with a 505 status code. (RFC7230 2.6, RFC7230 A.2)\"),\n\t#{code := 505} = do_raw(Config,\n\t\t\"GET / HTTP/1.00\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nreject_invalid_version_http111(Config) ->\n\tdoc(\"Any version number other than HTTP/1.0 or HTTP/1.1 must be \"\n\t\t\"rejected by a server or intermediary with a 505 status code. (RFC7230 2.6, RFC7230 A.2)\"),\n\t#{code := 505} = do_raw(Config,\n\t\t\"GET / HTTP/1.11\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nreject_invalid_version_http12(Config) ->\n\tdoc(\"Any version number other than HTTP/1.0 or HTTP/1.1 must be \"\n\t\t\"rejected by a server or intermediary with a 505 status code. (RFC7230 2.6, RFC7230 A.2)\"),\n\t#{code := 505} = do_raw(Config,\n\t\t\"GET / HTTP/1.2\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nreject_invalid_version_http2(Config) ->\n\tdoc(\"Any version number other than HTTP/1.0 or HTTP/1.1 must be \"\n\t\t\"rejected by a server or intermediary with a 505 status code. (RFC7230 2.6, RFC7230 A.2)\"),\n\t#{code := 505} = do_raw(Config,\n\t\t\"GET / HTTP/2\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nreject_empty_version(Config) ->\n\tdoc(\"Any version number other than HTTP/1.0 or HTTP/1.1 must be \"\n\t\t\"rejected by a server or intermediary with a 505 status code. \"\n\t\t\"(RFC7230 2.6, RFC7230 A, RFC7230 A.2)\"),\n\t#{code := 505} = do_raw(Config,\n\t\t\"GET / \\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\").\n\nreject_invalid_whitespace_after_version(Config) ->\n\tdoc(\"A request that has whitespace different than CRLF following the \"\n\t\t\"version must be rejected with a 400 status code and the closing \"\n\t\t\"of the connection. (RFC7230 3.1.1)\"),\n\t#{code := 400, client := Client} = do_raw(Config,\n\t\t\"GET / HTTP/1.1 \\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%% Request headers.\n\ninvalid_header_name(Config) ->\n\tdoc(\"Header field names are tokens. (RFC7230 3.2)\"),\n\t#{code := 400} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host\\0: localhost\\r\\n\"\n\t\t\"\\r\\n\"]).\n\ninvalid_header_value(Config) ->\n\tdoc(\"Header field values are made of printable characters, \"\n\t\t\"horizontal tab or space. (RFC7230 3.2)\"),\n\t#{code := 400} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\0rm rf the world\\r\\n\"\n\t\t\"\\r\\n\"]).\n\nlower_case_header(Config) ->\n\tdoc(\"The header field name is case insensitive. (RFC7230 3.2)\"),\n\t#{code := 200} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]).\n\nupper_case_header(Config) ->\n\tdoc(\"The header field name is case insensitive. (RFC7230 3.2)\"),\n\t#{code := 200} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"HOST: localhost\\r\\n\"\n\t\t\"\\r\\n\"]).\n\nmixed_case_header(Config) ->\n\tdoc(\"The header field name is case insensitive. (RFC7230 3.2)\"),\n\t#{code := 200} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"hOsT: localhost\\r\\n\"\n\t\t\"\\r\\n\"]).\n\nreject_whitespace_before_header_name(Config) ->\n\tdoc(\"Messages that contain whitespace before the header name must \"\n\t\t\"be rejected with a 400 status code and the closing of the \"\n\t\t\"connection. (RFC7230 3.2.4)\"),\n\t#{code := 400, client := Client1} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\" Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client1, 0, 1000),\n\t#{code := 400, client := Client2} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"\\tHost: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client2, 0, 1000).\n\nreject_whitespace_between_header_name_and_colon(Config) ->\n\tdoc(\"Messages that contain whitespace between the header name and \"\n\t\t\"colon must be rejected with a 400 status code and the closing \"\n\t\t\"of the connection. (RFC7230 3.2.4)\"),\n\t#{code := 400, client := Client1} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host : localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client1, 0, 1000),\n\t#{code := 400, client := Client2} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host\\t: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client2, 0, 1000).\n\nreject_header_name_without_colon(Config) ->\n\tdoc(\"Messages that contain a header name that is not followed by a \"\n\t\t\"colon must be rejected with a 400 status code and the closing \"\n\t\t\"of the connection. (RFC7230 3.2.4)\"),\n\t#{code := 400, client := Client1} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client1, 0, 1000),\n\t#{code := 400, client := Client2} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client2, 0, 1000),\n\t#{code := 400, client := Client3} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host\\r\\n\"\n\t\t\" : localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client3, 0, 1000).\n\nlimit_header_name(Config) ->\n\tdoc(\"The header name must be subject to a configurable limit. A \"\n\t\t\"good default is 50 characters, well above the longest registered \"\n\t\t\"header. Such a request must be rejected with a 431 status code \"\n\t\t\"and the closing of the connection. \"\n\t\t\"(RFC7230 3.2.5, RFC6585 5, IANA Message Headers registry)\"),\n\t#{code := 431, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\",\n\t\tbinary:copy(<<$a>>, 32768), \": bad\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nlimit_header_value(Config) ->\n\tdoc(\"The header value and the optional whitespace around it must be \"\n\t\t\"subject to a configurable limit. There is no recommendations \"\n\t\t\"for the default. 4096 characters is known to work well. Such \"\n\t\t\"a request must be rejected with a 431 status code and the closing \"\n\t\t\"of the connection. (RFC7230 3.2.5, RFC6585 5)\"),\n\t#{code := 431, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"bad: \", binary:copy(<<$a>>, 32768), \"\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\ndrop_whitespace_before_header_value(Config) ->\n\tdoc(\"Optional whitespace before and after the header value is not \"\n\t\t\"part of the value and must be dropped.\"),\n\t#{code := 200} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-length:      \\t     12\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Hello world!\"]).\n\ndrop_whitespace_after_header_value(Config) ->\n\tdoc(\"Optional whitespace before and after the header value is not \"\n\t\t\"part of the value and must be dropped.\"),\n\t#{code := 200} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-length: 12     \\t     \\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Hello world!\"]).\n\nreject_lf_line_breaks(Config) ->\n\tdoc(\"A server may accept header names separated by a single LF, instead of \"\n\t\t\"CRLF. Cowboy rejects all requests that use LF as separator. (RFC7230 3.5)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%@todo\n%The order of header fields with differing names is not significant. (RFC7230 3.2.2)\n%\n%@todo\n%The normal procedure for parsing headers is to read each header\n%field into a hash table by field name until the empty line. (RFC7230 3)\n\nreject_duplicate_content_length_header(Config) ->\n\tdoc(\"Requests with duplicate content-length headers must be rejected \"\n\t\t\"with a 400 status code and the closing of the connection. (RFC7230 3.3.2)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-length: 12\\r\\n\"\n\t\t\"Content-length: 12\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Hello world!\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nreject_duplicate_host_header(Config) ->\n\tdoc(\"Requests with duplicate host headers must be rejected \"\n\t\t\"with a 400 status code and the closing of the connection. (RFC7230 3.3.2)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Hello world!\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\ncombine_duplicate_headers(Config) ->\n\tdoc(\"Other duplicate header fields must be combined by inserting a comma \"\n\t\t\"between the values in the order they were received. (RFC7230 3.2.2)\"),\n\t#{code := 200, body := Body} = do_raw(Config, [\n\t\t\"GET /echo/headers HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Accept-encoding: gzip\\r\\n\"\n\t\t\"Accept-encoding: brotli\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t<<\"#{<<\\\"accept-encoding\\\">> => <<\\\"gzip, brotli\\\">>,\", _/bits>> = Body,\n\tok.\n\n%Duplicate header field names are only allowed when their value is\n%a comma-separated list. In practice there is no need to perform\n%a check while reading the headers as the value will become invalid\n%and the error can be handled while parsing the header later on. (RFC7230 3.2.2)\n%\n%wait_for_eoh_before_processing_request(Config) ->\n%The request must not be processed until all headers have arrived. (RFC7230 3.2.2)\n\nlimit_headers(Config) ->\n\tdoc(\"The number of headers allowed in a request must be subject to \"\n\t\t\"a configurable limit. There is no recommendations for the default. \"\n\t\t\"100 headers is known to work well. Such a request must be rejected \"\n\t\t\"with a 431 status code and the closing of the connection. (RFC7230 3.2.5, RFC6585 5)\"),\n\t%% 100 headers.\n\t#{code := 200} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\",\n\t\t[[\"H-\", integer_to_list(N), \": value\\r\\n\"] || N <- lists:seq(1, 99)],\n\t\t\"\\r\\n\"]),\n\t%% 101 headers.\n\t#{code := 431, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\",\n\t\t[[\"H-\", integer_to_list(N), \": value\\r\\n\"] || N <- lists:seq(1, 100)],\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%ignore_header_empty_list_elements(Config) ->\n%When parsing header field values, the server must ignore empty\n%list elements, and not count those as the count of elements present. (RFC7230 7)\n%\n%@todo\n%The information in the via header is largely unreliable. (RFC7230 5.7.1)\n\n%% Request body.\n\n%@todo\n%The message body is the octets after decoding any transfer\n%codings. (RFC7230 3.3)\n\nno_request_body(Config) ->\n\tdoc(\"A request has a message body only if it includes a transfer-encoding \"\n\t\t\"header or a non-zero content-length header. (RFC7230 3.3)\"),\n\t#{code := 200, body := <<\"false\">>} = do_raw(Config, [\n\t\t\"POST /echo/has_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t#{code := 200, body := <<>>} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\tok.\n\nno_request_body_content_length_zero(Config) ->\n\tdoc(\"A request has a message body only if it includes a transfer-encoding \"\n\t\t\"header or a non-zero content-length header. (RFC7230 3.3)\"),\n\t#{code := 200, body := <<\"false\">>} = do_raw(Config, [\n\t\t\"POST /echo/has_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-length: 0\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t#{code := 200, body := <<>>} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-length: 0\\r\\n\"\n\t\t\"\\r\\n\"]),\n\tok.\n\nrequest_body_content_length(Config) ->\n\tdoc(\"A request has a message body only if it includes a transfer-encoding \"\n\t\t\"header or a non-zero content-length header. (RFC7230 3.3)\"),\n\t#{code := 200, body := <<\"true\">>} = do_raw(Config, [\n\t\t\"POST /echo/has_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-length: 12\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Hello world!\"]),\n\t#{code := 200, body := <<\"Hello world!\">>} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-length: 12\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Hello world!\"]),\n\tok.\n\nrequest_body_transfer_encoding(Config) ->\n\tdoc(\"A request has a message body only if it includes a transfer-encoding \"\n\t\t\"header or a non-zero content-length header. (RFC7230 3.3)\"),\n\t#{code := 200, body := <<\"true\">>} = do_raw(Config, [\n\t\t\"POST /echo/has_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t#{code := 200, body := <<\"Hello world!\">>} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\tok.\n\n%```\n%Transfer-Encoding = 1#transfer-coding\n%\n%transfer-coding = \"chunked\" / \"compress\" / \"deflate\" / \"gzip\" / transfer-extension\n%transfer-extension = token *( OWS \";\" OWS transfer-parameter )\n%transfer-parameter = token BWS \"=\" BWS ( token / quoted-string )\n%```\n\ncase_insensitive_transfer_encoding(Config) ->\n\tdoc(\"The transfer-coding is case insensitive. (RFC7230 4)\"),\n\t#{code := 200, body := <<\"Hello world!\">>} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: ChUnKeD\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\tok.\n\n%@todo\n%There are no known other transfer-extension with the exception of\n%deprecated aliases \"x-compress\" and \"x-gzip\". (IANA HTTP Transfer Coding Registry,\n%RFC7230 4.2.1, RFC7230 4.2.3, RFC7230 8.4.2)\n\n%% This is the exact same test as request_body_transfer_encoding.\nmust_understand_chunked(Config) ->\n\tdoc(\"A server must be able to handle at least chunked transfer-encoding. \"\n\t\t\"This is also the only coding that sees widespread use. (RFC7230 3.3.1, RFC7230 4.1)\"),\n\t#{code := 200, body := <<\"Hello world!\">>} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\tok.\n\nreject_double_chunked_encoding(Config) ->\n\tdoc(\"Messages encoded more than once with chunked transfer-encoding \"\n\t\t\"must be rejected with a 400 status code and the closing of the \"\n\t\t\"connection. (RFC7230 3.3.1)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked, chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"20\\r\\n6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nreject_non_terminal_chunked(Config) ->\n\tdoc(\"Messages where chunked, when present, is not the last \"\n\t\t\"transfer-encoding must be rejected with a 400 status code \"\n\t\t\"and the closing of the connection. (RFC7230 3.3.3)\"),\n\t#{code := 400, client := Client1} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked, gzip\\r\\n\"\n\t\t\"\\r\\n\",\n\t\tzlib:gzip(<<\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\">>)]),\n\t{error, closed} = raw_recv(Client1, 0, 1000),\n\t#{code := 400, client := Client2} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"Transfer-encoding: gzip\\r\\n\"\n\t\t\"\\r\\n\",\n\t\tzlib:gzip(<<\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\">>)]),\n\t{error, closed} = raw_recv(Client2, 0, 1000).\n\n%@todo\n%Some non-conformant implementations send the \"deflate\" compressed\n%data without the zlib wrapper. (RFC7230 4.2.2)\n\nreject_unknown_transfer_encoding(Config) ->\n\tdoc(\"Messages encoded with a transfer-encoding the server does not \"\n\t\t\"understand must be rejected with a 501 status code and the \"\n\t\t\"closing of the connection. (RFC7230 3.3.1)\"),\n\t#{code := 400, client := Client1} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: unknown, chunked\\r\\n\"\n\t\t\"\\r\\n\",\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client1, 0, 1000),\n\t#{code := 400, client := Client2} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: unknown\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\",\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client2, 0, 1000).\n\n%@todo\n%A server may reject requests with a body and no content-length\n%header with a 411 status code. (RFC7230 3.3.3)\n\n%```\n%Content-Length = 1*DIGIT\n%```\n\nreject_invalid_content_length(Config) ->\n\tdoc(\"A request with an invalid content-length header must be rejected \"\n\t\t\"with a 400 status code and the closing of the connection. (RFC7230 3.3.3)\"),\n\t#{code := 400, client := Client1} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-length: 12,12\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Hello world!\"]),\n\t{error, closed} = raw_recv(Client1, 0, 1000),\n\t#{code := 400, client := Client2} = do_raw(Config, [\n\t\t\"POST / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-length: NaN\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Hello world!\"]),\n\t{error, closed} = raw_recv(Client2, 0, 1000).\n\n%@todo\n%The content-length header ranges from 0 to infinity. Requests\n%with a message body too large must be rejected with a 413 status\n%code and the closing of the connection. (RFC7230 3.3.2)\n\nreject_when_both_content_length_and_transfer_encoding(Config) ->\n\tdoc(\"When a message includes both transfer-encoding and content-length \"\n\t\t\"headers, the message may be an attempt at request smuggling. It \"\n\t\t\"must be rejected with a 400 status code and the closing of the \"\n\t\t\"connection. (RFC7230 3.3.3)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"Content-length: 12\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%socket_error_while_reading_body(Config) ->\n%If a socket error occurs while reading the body the server\n%must send a 400 status code response and close the connection. (RFC7230 3.3.3, RFC7230 3.4)\n%\n%timeout_while_reading_body(Config) ->\n%If a timeout occurs while reading the body the server must\n%send a 408 status code response and close the connection. (RFC7230 3.3.3, RFC7230 3.4)\n\n%% Body length.\n\nbody_length_chunked_before(Config) ->\n\tdoc(\"The length of a message with a transfer-encoding header can \"\n\t\t\"only be determined on decoding completion. (RFC7230 3.3.3)\"),\n\t#{code := 200, body := <<\"undefined\">>} = do_raw(Config, [\n\t\t\"POST /echo/body_length HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\tok.\n\nbody_length_chunked_after(Config) ->\n\tdoc(\"Upon completion of chunk decoding the server must add a content-length \"\n\t\t\"header with the value set to the total length of data read. (RFC7230 4.1.3)\"),\n\t#{code := 200, body := <<\"12\">>} = do_raw(Config, [\n\t\t\"POST /length/echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\tok.\n\nbody_length_content_length(Config) ->\n\tdoc(\"The length of a message with a content-length header is \"\n\t\t\"the numeric value in octets found in the header. (RFC7230 3.3.3)\"),\n\t#{code := 200, body := <<\"12\">>} = do_raw(Config, [\n\t\t\"POST /echo/body_length HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Content-length: 12\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"Hello world!\"]),\n\tok.\n\nbody_length_zero(Config) ->\n\tdoc(\"A message with no transfer-encoding or content-length header \"\n\t\t\"has a body length of 0. (RFC7230 3.3.3)\"),\n\t#{code := 200, body := <<\"0\">>} = do_raw(Config, [\n\t\t\"POST /echo/body_length HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\tok.\n\n%% Chunked transfer-encoding.\n\nreject_invalid_chunk_size(Config) ->\n\tdoc(\"A request with an invalid chunk size must be rejected \"\n\t\t\"with a 400 status code and the closing of the connection. (RFC7230 4.1)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r\\nFIVE\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%```\n%chunked-body = *chunk last-chunk trailer-part CRLF\n%\n%chunk = chunk-size [ chunk-ext ] CRLF chunk-data CRLF\n%chunk-size = 1*HEXDIG\n%chunk-data = 1*OCTET ; a sequence of chunk-size octets\n%\n%last-chunk = 1*(\"0\") [ chunk-ext ] CRLF\n%```\n%\n%The chunk-size field is a string of hex digits indicating the size of\n%the chunk-data in octets.\n%\n%```\n%chunk-ext = *( \";\" chunk-ext-name [ \"=\" chunk-ext-val ] )\n%chunk-ext-name = token\n%chunk-ext-val = token / quoted-string\n%```\n\nignore_unknown_chunk_extensions(Config) ->\n\tdoc(\"Unknown chunk extensions must be ignored. (RFC7230 4.1.1)\"),\n\t#{code := 200, body := <<\"Hello world!\">>} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6; hello=\\\"cool world\\\"\\r\\nHello \\r\\n\"\n\t\t\"5 ; one ; two ; three;four;five\\r\\nworld\"\n\t\t\"\\r\\n1;ok\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\tok.\n\n%% Since we skip everything right now, the only reason\n%% we might reject chunk extensions is if they are too large.\nlimit_chunk_size_line(Config) ->\n\tdoc(\"A request with chunk extensions larger than the server allows must be rejected \"\n\t\t\"with a 400 status code and the closing of the connection. (RFC7230 4.1.1)\"),\n\t#{code := 200, body := <<\"Hello world!\">>} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6; hello=\\\"cool world\\\"\\r\\nHello \\r\\n\"\n\t\t\"5;\", lists:duplicate(128, $a), \"\\r\\nworld\"\n\t\t\"\\r\\n1;ok\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6; hello=\\\"cool world\\\"\\r\\nHello \\r\\n\"\n\t\t\"5;\", lists:duplicate(129, $a), \"\\r\\nworld\"\n\t\t\"\\r\\n1;ok\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nreject_invalid_chunk_size_crlf(Config) ->\n\tdoc(\"A request with an invalid line break after the chunk size must be rejected \"\n\t\t\"with a 400 status code and the closing of the connection. (RFC7230 4.1)\"),\n\t#{code := 400, client := Client1} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\rHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client1, 0, 1000),\n\t#{code := 400, client := Client2} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client2, 0, 1000),\n\t#{code := 400, client := Client3} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6Hello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client3, 0, 1000).\n\nreject_invalid_chunk_ext_crlf(Config) ->\n\tdoc(\"A request with an invalid line break after chunk extensions must be rejected \"\n\t\t\"with a 400 status code and the closing of the connection. (RFC7230 4.1)\"),\n\t#{code := 400, client := Client1} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6; extensions\\rHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client1, 0, 1000),\n\t#{code := 400, client := Client2} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6; extensions\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client2, 0, 1000),\n\t#{code := 400, client := Client3} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6; extensionsHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client3, 0, 1000).\n\nreject_invalid_chunk_data_crlf(Config) ->\n\tdoc(\"A request with an invalid line break after the chunk data must be rejected \"\n\t\t\"with a 400 status code and the closing of the connection. (RFC7230 4.1)\"),\n\t#{code := 400, client := Client1} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client1, 0, 1000),\n\t#{code := 400, client := Client2} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client2, 0, 1000),\n\t#{code := 400, client := Client3} = do_raw(Config, [\n\t\t\"POST /echo/read_body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello 5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\t{error, closed} = raw_recv(Client3, 0, 1000).\n\n%```\n%trailer-part = *( header-field CRLF )\n%```\n%\n%%% @todo see headers above and reject the same way, space etc.\n%reject_invalid_request_trailer(Config) ->\n%\n%ignore_request_trailer_transfer_encoding(Config) ->\n%ignore_request_trailer_content_length(Config) ->\n%ignore_request_trailer_host(Config) ->\n%ignore_request_trailer_cache_control(Config) ->\n%ignore_request_trailer_expect(Config) ->\n%ignore_request_trailer_max_forwards(Config) ->\n%ignore_request_trailer_pragma(Config) ->\n%ignore_request_trailer_range(Config) ->\n%ignore_request_trailer_te(Config) ->\n%ignore_request_trailer_if_match(Config) ->\n%ignore_request_trailer_if_none_match(Config) ->\n%ignore_request_trailer_if_modified_since(Config) ->\n%ignore_request_trailer_if_unmodified_since(Config) ->\n%ignore_request_trailer_if_range(Config) ->\n%ignore_request_trailer_www_authenticate(Config) ->\n%ignore_request_trailer_authorization(Config) ->\n%ignore_request_trailer_proxy_authenticate(Config) ->\n%ignore_request_trailer_proxy_authorization(Config) ->\n%ignore_request_trailer_content_encoding(Config) ->\n%ignore_request_trailer_content_type(Config) ->\n%ignore_request_trailer_content_range(Config) ->\n%ignore_request_trailer_trailer(Config) ->\n%\n%ignore_response_trailer_header(Config, Header) ->\n%Trailing headers must not include transfer-encoding, content-length,\n%host, cache-control, expect, max-forwards, pragma, range, te,\n%if-match, if-none-match, if-modified-since, if-unmodified-since,\n%if-range, www-authenticate, authorization, proxy-authenticate,\n%proxy-authorization, age, cache-control, expires, date, location,\n%retry-after, vary, warning, content-encoding, content-type,\n%content-range, or trailer. (RFC7230 4.1.2)\n%\n%When trailer headers are processed, invalid headers must be ignored.\n%Valid headers must be added to the list of headers of the request. (RFC7230 4.1.2)\n%\n%ignore_request_trailers(Config) ->\n%Trailer headers can be ignored safely. (RFC7230 4.1.2)\n%\n%limit_request_trailer_headers(Config) ->\n%The number of trailer headers must be subject to configuration.\n%There is no known recommendations for the default. A value of 10\n%should cover most cases. Requests with too many trailer headers\n%must be rejected with a 431 status code and the closing of the\n%connection. (RFC6585 5)\n\n%% We remove the header immediately so there's no need\n%% to try to read the body before checking.\nremove_transfer_encoding_chunked_after_body_read(Config) ->\n\tdoc(\"Upon completion of chunk decoding the server must remove \\\"chunked\\\" \"\n\t\t\"from the transfer-encoding header. This header must be removed if \"\n\t\t\"it becomes empty following this removal. (RFC7230 4.1.3)\"),\n\t#{code := 200, body := <<\"undefined\">>} = do_raw(Config, [\n\t\t\"POST /echo/header/transfer-encoding HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Transfer-encoding: chunked\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"6\\r\\nHello \\r\\n5\\r\\nworld\\r\\n1\\r\\n!\\r\\n0\\r\\n\\r\\n\"]),\n\tok.\n\n%remove_trailer_after_body_read(Config) ->\n%Upon completion of chunk decoding the server must remove the trailer\n%header from the list of headers. (RFC7230 4.1.3)\n%\n%```\n%Trailer = 1#field-name\n%```\n%\n%ignore_chunked_headers_not_in_trailer(Config) ->\n%The trailer header can be used to list the headers found in the\n%trailer. A server must have the option of ignoring trailer headers\n%that were not listed in the trailer header. (RFC7230 4.4)\n%\n%ignore_chunked_headers_if_trailer_not_in_connection(Config) ->\n%The trailer header must be listed in the connection header field.\n%Trailers must be ignored otherwise.\n%\n%%% @todo Though we need a compatibility mode as some clients don't send it...\n%reject_chunked_missing_end_crlf(Config) ->\n%@todo ending CRLF\n\n%% Connection management.\n\n%@todo can probably test using auth\n%Never assume any two requests on a single connection come\n%from the same user agent. (RFC7230 2.3)\n%\n%```\n%Connection = 1#token ; case-insensitive\n%```\n%\n%The connection token is either case insensitive \"close\", \"keep-alive\"\n%or a header field name.\n%\n%There are no corresponding \"close\" or \"keep-alive\" headers. (RFC7230 8.1, RFC7230 A.2)\n%\n%The connection header is valid only for the immediate connection,\n%alongside any header field it lists. (RFC7230 6.1)\n%\n%The server must determine if the connection is persistent for\n%every message received by looking at the connection header and\n%HTTP version. (RFC7230 6.3)\n\nno_connection_header_keepalive(Config) ->\n\tdoc(\"HTTP/1.1 requests with no \\\"close\\\" option \"\n\t\t\"indicate the connection will persist. (RFC7230 6.1, RFC7230 6.3)\"),\n\t#{code := 200, headers := RespHeaders, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\tfalse = lists:keyfind(<<\"connection\">>, 1, RespHeaders),\n\t{error, timeout} = raw_recv(Client, 0, 1000).\n\nhttp10_connection_keepalive(Config) ->\n\tdoc(\"HTTP/1.0 requests with the \\\"keep-alive\\\" option \"\n\t\t\"indicate the connection will persist. \"\n\t\t\"(RFC7230 6.1, RFC7230 6.3, RFC7230 A.1.2)\"),\n\t#{code := 200, headers := RespHeaders, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.0\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: keep-alive\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, <<\"keep-alive\">>} = lists:keyfind(<<\"connection\">>, 1, RespHeaders),\n\t{error, timeout} = raw_recv(Client, 0, 1000).\n\nconnection_close(Config) ->\n\tdoc(\"HTTP/1.1 requests with the \\\"close\\\" option and HTTP/1.0 with no \"\n\t\t\"\\\"keep-alive\\\" option indicate the connection will be closed \"\n\t\t\"upon reception of the response by the client. (RFC7230 6.1, RFC7230 6.3)\"),\n\t#{code := 200, headers := RespHeaders, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: close\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, <<\"close\">>} = lists:keyfind(<<\"connection\">>, 1, RespHeaders),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nhttp10_no_connection_header_close(Config) ->\n\tdoc(\"HTTP/1.0 with no \\\"keep-alive\\\" option indicate \"\n\t\t\"the connection will be closed upon reception of \"\n\t\t\"the response by the client. (RFC7230 6.1, RFC7230 6.3, RFC7230 A.1.2)\"),\n\t#{code := 200, headers := RespHeaders, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.0\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t%% Cowboy always sends a close header back to HTTP/1.0 clients\n\t%% that support keep-alive, even though it is not required.\n\t{_, <<\"close\">>} = lists:keyfind(<<\"connection\">>, 1, RespHeaders),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nconnection_invalid(Config) ->\n\tdoc(\"HTTP/1.1 requests with an invalid Connection header \"\n\t\t\"must be rejected with a 400 status code and the closing \"\n\t\t\"of the connection. (RFC7230 6.1)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: jndi{ldap127\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nhttp10_connection_invalid(Config) ->\n\tdoc(\"HTTP/1.0 requests with an invalid Connection header \"\n\t\t\"must be rejected with a 400 status code and the closing \"\n\t\t\"of the connection. (RFC7230 6.1)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.0\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: jndi{ldap127\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nlimit_requests_keepalive(Config) ->\n\tdoc(\"The maximum number of requests sent using a persistent connection \"\n\t\t\"must be subject to configuration. The connection must be closed \"\n\t\t\"when the limit is reached. (RFC7230 6.3)\"),\n\tConnPid = gun_open(Config),\n\t_ = [begin\n\t\tRef = gun:get(ConnPid, \"/\"),\n\t\t{response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref),\n\t\t{ok, <<\"Hello world!\">>} = gun:await_body(ConnPid, Ref),\n\t\tfalse = lists:keyfind(<<\"connection\">>, 1, RespHeaders)\n\tend || _ <- lists:seq(1,99)],\n\t%% Final request closes the connection.\n\tRef = gun:get(ConnPid, \"/\"),\n\t{response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref),\n\t{ok, <<\"Hello world!\">>} = gun:await_body(ConnPid, Ref),\n\t{_, <<\"close\">>} = lists:keyfind(<<\"connection\">>, 1, RespHeaders),\n\tgun_down(ConnPid).\n\naccept_at_least_1_empty_line_keepalive(Config) ->\n\tdoc(\"A configurable number of empty lines (CRLF) preceding the request \"\n\t\t\"must be ignored. At least 1 empty line must be ignored. (RFC7230 3.5)\"),\n\t#{code := 200, client := Client} = do_raw(Config,\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t%% We send an extra CRLF that must be ignored.\n\t\t\"\\r\\n\"),\n\tok = raw_send(Client,\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{'HTTP/1.1', 200, _, _} = cow_http:parse_status_line(raw_recv_head(Client)),\n\tok.\n\n%skip_request_body_by_closing_connection(Config) ->\n%%A server that doesn't want to read the entire body of a message\n%%must close the connection, if possible after sending the \"close\"\n%%connection option in the response. (RFC7230 6.3)\n\npipeline(Config) ->\n\tdoc(\"A server can receive more than one request before any response \"\n\t\t\"is sent. This is called pipelining. Responses must be sent \"\n\t\t\"in the same order as the requests. (RFC7230 6.3.2)\"),\n\tConnPid = gun_open(Config),\n\tRefs = [{\n\t\tgun:get(ConnPid, \"/\"),\n\t\tgun:post(ConnPid, \"/full/read_body\", [], <<0:80000>>)\n\t} || _ <- lists:seq(1, 25)],\n\t_ = [begin\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, Ref1, infinity),\n\t\t{ok, <<\"Hello world!\">>} = gun:await_body(ConnPid, Ref1, infinity),\n\t\t{response, nofin, 200, _} = gun:await(ConnPid, Ref2, infinity),\n\t\t{ok, <<0:80000>>} = gun:await_body(ConnPid, Ref2, infinity)\n\tend || {Ref1, Ref2} <- Refs],\n\tok.\n\n%% @todo pipeline_parallel (safe methods can, others can't)\n%The requests can be processed in parallel if they all have safe methods.\n\n%@todo\n%A server that does parallel pipelining must send responses in the\n%same order as the requests came in. (RFC7230 5.6)\n\n%@todo\n%The server must reject abusive traffic by closing the connection.\n%Abusive traffic can come from the form of too many requests in a\n%given amount of time, or too many concurrent connections. Limits\n%must be subject to configuration. (RFC7230 6.4)\n\nclose_inactive_connections(Config) ->\n\tdoc(\"The server must close inactive connections. The timeout \"\n\t\t\"must be subject to configuration. (RFC7230 6.5)\"),\n\tClient = raw_open(Config),\n\t{error, closed} = raw_recv(Client, 0, 6000).\n\n%@todo\n%The server must monitor connections for the close signal\n%and close the socket on its end accordingly. (RFC7230 6.5)\n%\n%@todo\n%A connection close may occur at any time. (RFC7230 6.5)\n\nignore_requests_after_request_connection_close(Config) ->\n\tdoc(\"The server must not process any request after \"\n\t\t\"receiving the \\\"close\\\" connection option. (RFC7230 6.6)\"),\n\tSelf = self(),\n\t#{code := 200, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: close\\r\\n\"\n\t\t\"\\r\\n\"\n\t\t\"GET /send_message HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"x-test-pid: \", pid_to_list(Self), \"\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000),\n\t%% We receive a message if the second request is wrongly processed.\n\treceive\n\t\t{Self, _, init, Req, Opts} ->\n\t\t\terror({init, Req, Opts})\n\tafter 1000 ->\n\t\tok\n\tend.\n\nignore_requests_after_response_connection_close(Config) ->\n\tdoc(\"The server must not process any request after \"\n\t\t\"sending the \\\"close\\\" connection option. (RFC7230 6.6)\"),\n\tSelf = self(),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t[\n\t\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\t\"Host: localhost\\r\\n\"\n\t\t\t\"\\r\\n\"\n\t\t|| _ <- lists:seq(1, 100)],\n\t\t\"GET /send_message HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"x-test-pid: \", pid_to_list(Self), \"\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t%% We have a separate test for the connection close so we don't\n\t%% double check the connection gets closed here. We only need to\n\t%% know whether the 101st request was wrongly processed.\n\treceive\n\t\t{Self, _, init, Req, Opts} ->\n\t\t\terror({init, Req, Opts})\n\tafter 1000 ->\n\t\tok\n\tend.\n\n%@todo\n%The server must close the connection in stages to avoid the\n%TCP reset problem. The server starts by closing the write\n%side of the socket. The server then reads until it detects\n%the socket has been closed, until it can be certain its\n%last response has been received by the client, or until\n%a close or timeout occurs. The server then fully close the\n%connection. (6.6)\n\n%% Routing.\n\n%```\n%Host = authority ; same as authority-form\n%```\n\nreject_missing_host(Config) ->\n\tdoc(\"An HTTP/1.1 request that lacks a host header must be rejected with \"\n\t\t\"a 400 status code and the closing of the connection. (RFC7230 5.4)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nhttp10_allow_missing_host(Config0) ->\n\tdoc(\"An HTTP/1.0 request that lacks a host header may be accepted. \"\n\t\t\"(RFC7230 5.4, RFC7230 5.5, RFC7230 A.1.1)\"),\n\tRoutes = [{'_', [{\"/echo/:key[/:arg]\", echo_h, []}]}],\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(Routes)}\n\t}, Config0),\n\ttry\n\t\t#{code := 200, body := <<>>} = do_raw(Config, [\n\t\t\t\"GET /echo/host HTTP/1.0\\r\\n\"\n\t\t\t\"\\r\\n\"])\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nreject_invalid_host(Config) ->\n\tdoc(\"A request with an invalid host header must be rejected with a \"\n\t\t\"400 status code and the closing of the connection. (RFC7230 5.4)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost:port\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nreject_userinfo(Config) ->\n\tdoc(\"An authority component with a userinfo component (and its \"\n\t\t\"\\\"@\\\" delimiter) is invalid. The request must be rejected with \"\n\t\t\"a 400 status code and the closing of the connection. (RFC7230 2.7.1)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: user@localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\nreject_absolute_form_different_host(Config) ->\n\tdoc(\"When using absolute-form the URI authority component must be \"\n\t\t\"identical to the host header. Invalid requests must be rejected \"\n\t\t\"with a 400 status code and the closing of the connection. (RFC7230 5.4)\"),\n\t#{code := 400, client := Client} = do_raw(Config, [\n\t\t\"GET http://example.org/ HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n\n%reject_authority_form_different_host(Config) ->\n%When using authority-form the URI authority component must be\n%identical to the host header. Invalid requests must be rejected\n%with a 400 status code and the closing of the connection.\n\nempty_host(Config0) ->\n\tdoc(\"The host header is empty when the authority component is undefined. (RFC7230 5.4)\"),\n\tRoutes = [{'_', [{\"/echo/:key[/:arg]\", echo_h, []}]}],\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(Routes)}\n\t}, Config0),\n\ttry\n\t\t#{code := 200, body := <<>>} = do_raw(Config, [\n\t\t\t\"GET /echo/host HTTP/1.1\\r\\n\"\n\t\t\t\"Host:\\r\\n\"\n\t\t\t\"\\r\\n\"]),\n\t\t#{code := 200, body := <<>>} = do_raw(Config, [\n\t\t\t\"GET /echo/host HTTP/1.1\\r\\n\"\n\t\t\t\"Host: \\r\\n\"\n\t\t\t\"\\r\\n\"])\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\n%% The effective request URI can be rebuilt by concatenating scheme,\n%% \"://\", authority, path and query components. (RFC7230 5.5)\n%%\n%% This is covered in req_SUITE in the tests for cowboy_req:uri/1,2.\n\nreject_non_authoritative_host(Config) ->\n\tdoc(\"A request with a host header for which the origin server is \"\n\t\t\"not authoritative must be rejected with a 400 status code. \"\n\t\t\"(RFC7230 5.5, RFC7230 9.1)\"),\n\t#{code := 400} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: ninenines.eu\\r\\n\"\n\t\t\"\\r\\n\"]),\n\tok.\n\n%@todo\n%Resources with identical URI except for the scheme component\n%must be treated as different. (RFC7230 2.7.2)\n\n%% Response.\n\n%@todo\n%A server can send more than one response per request only when a\n%1xx response is sent preceding the final response. (RFC7230 5.6)\n%\n%```\n%HTTP-response = status-line *( header-field CRLF ) CRLF [ message-body ]\n%```\n%\n%@todo\n%The response format must be followed strictly.\n%\n%```\n%status-line   = HTTP-version SP status-code SP reason-phrase CRLF\n%status-code   = 3DIGIT\n%reason-phrase = *( HTAB / SP / VCHAR / obs-text )\n%```\n\nhttp10_request_http11_response(Config) ->\n\tdoc(\"A server must send its own HTTP version in responses. (RFC7230 2.6)\"),\n\t#{code := 200, version := 'HTTP/1.1'} = do_raw(Config, [\n\t\t\"GET / HTTP/1.0\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\tok.\n\n%@todo\n%An HTTP/1.1 server may send an HTTP/1.0 version for compatibility purposes. (RFC7230 2.6)\n%\n%@todo\n%RFC6585 defines additional status code a server can use to reject\n%messages. (RFC7230 9.3, RFC6585)\n\n%% Response headers.\n\n%@todo\n%In responses, OWS must be generated as SP or not generated\n%at all. RWS must be generated as SP. BWS must not be\n%generated. (RFC7230 3.2.3)\n%\n%```\n%header-field = field-name \":\" SP field-value\n%\n%field-name = token ; case-insensitive\n%field-value = *( SP / %21-7E / %80-FF )\n%```\n%\n%@todo\n%In quoted-string found in field-value, quoted-pair must only be\n%used for DQUOTE and backslash. (RFC7230 3.2.6)\n%\n%@todo\n%HTTP header values must use US-ASCII encoding and must only send\n%printable characters or SP. (RFC7230 3.2.4, RFC7230 9.4)\n%\n%@todo\n%The server must not generate empty list elements in headers. (RFC7230 7)\n%\n%@todo\n%When encoding an URI as part of a response, only characters that\n%are reserved need to be percent-encoded. (RFC7230 2.7.3)\n\nspecial_set_cookie_handling(Config) ->\n\tdoc(\"The set-cookie header must be handled as a special case. There \"\n\t\t\"must be exactly one set-cookie header field per cookie. (RFC7230 3.2.2)\"),\n\t#{code := 200, headers := RespHeaders} = do_raw(Config, [\n\t\t\"GET /resp/set_resp_cookie3/multiple HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t[_, _] = [H || H={<<\"set-cookie\">>, _} <- RespHeaders],\n\tok.\n\n%@todo\n%The server must list headers for or about the immediate connection\n%in the connection header field. (RFC7230 6.1)\n%\n%@todo\n%A server that does not support persistent connections must\n%send \"close\" in every non-1xx response. (RFC7230 6.1)\n%\n%no_close_in_100_response(Config) ->\n%no_close_in_101_response(Config) ->\n%no_close_in_102_response(Config) ->\n%A server must not send a \"close\" connection option\n%in 1xx responses. (RFC7230 6.1)\n%\n%@todo\n%The \"close\" connection must be sent in a message when the\n%sender knows it will close the connection after fully sending\n%the response. (RFC7230 6.6)\n%\n%@todo\n%A server must close the connection after sending or\n%receiving a \"close\" once the response has been sent. (RFC7230 6.6)\n\nclose_request_close_response(Config) ->\n\tdoc(\"A server must send a \\\"close\\\" in a response to a request \"\n\t\t\"containing a \\\"close\\\". (RFC7230 6.6)\"),\n\t#{code := 200, headers := RespHeaders} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: close\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, <<\"close\">>} = lists:keyfind(<<\"connection\">>, 1, RespHeaders),\n\tok.\n\n%% Response body.\n\nno_body_in_head_response(Config) ->\n\tdoc(\"Responses to HEAD requests never include a message body. (RFC7230 3.3)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"HEAD / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 200, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{Headers, <<>>} = cow_http:parse_headers(Rest),\n\t{_, LengthBin} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tLength = binary_to_integer(LengthBin),\n\t{error, timeout} = raw_recv(Client, Length, 1000),\n\tok.\n\n%% @todo test different ways to send a body in response\n\n%%% @todo Implement CONNECT\n%2xx responses to CONNECT requests never include a message\n%body. (RFC7230 3.3)\n%\n%no_body_in_100_response(Config) ->\n%no_body_in_101_response(Config) ->\n%no_body_in_102_response(Config) ->\n%1xx responses never include a message body. (RFC7230 3.3)\n\nno_body_in_204_response(Config) ->\n\tdoc(\"204 responses never include a message body. Cowboy produces \"\n\t\t\"a 500 error response when attempting to do so. (RFC7230 3.3)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"GET /resp/reply4/204body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 500, _, _} = cow_http:parse_status_line(raw_recv_head(Client)),\n\tok.\n\nno_body_in_204_response_stream(Config) ->\n\tdoc(\"204 responses never include a message body. Attempting to \"\n\t\t\"stream the body produces a crash on the server-side. (RFC7230 3.3)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"GET /resp/stream_reply2/204body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 204, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{_, <<>>} = cow_http:parse_headers(Rest),\n\t{error, timeout} = raw_recv(Client, 1, 1000),\n\tok.\n\nno_body_in_304_response(Config) ->\n\tdoc(\"304 responses never include a message body. Cowboy produces \"\n\t\t\"a 500 error response when attempting to do so. (RFC7230 3.3)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"GET /resp/reply4/304body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 500, _, _} = cow_http:parse_status_line(raw_recv_head(Client)),\n\tok.\n\nno_body_in_304_response_stream(Config) ->\n\tdoc(\"304 responses never include a message body. Attempting to \"\n\t\t\"stream the body produces a crash on the server-side. (RFC7230 3.3)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"GET /resp/stream_reply2/304body HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 304, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{_, <<>>} = cow_http:parse_headers(Rest),\n\t{error, timeout} = raw_recv(Client, 1, 1000),\n\tok.\n\nsame_content_length_as_get_in_head_response(Config) ->\n\tdoc(\"Responses to HEAD requests can include a content-length header. \"\n\t\t\"Its value must be the same as if the request was an unconditional \"\n\t\t\"GET. (RFC7230 3.3, RFC7230 3.3.1, RFC7230 3.3.2)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"HEAD / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 200, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{Headers, <<>>} = cow_http:parse_headers(Rest),\n\t{_, <<\"12\">>} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\nsame_transfer_encoding_as_get_in_head_response(Config) ->\n\tdoc(\"Responses to HEAD requests can include a transfer-encoding header. \"\n\t\t\"Its value must be the same as if the request was an unconditional \"\n\t\t\"GET. (RFC7230 3.3, RFC7230 3.3.1, RFC7230 3.3.2)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"HEAD /resp/stream_reply2/200 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 200, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{Headers, <<>>} = cow_http:parse_headers(Rest),\n\t{_, <<\"chunked\">>} = lists:keyfind(<<\"transfer-encoding\">>, 1, Headers),\n\tok.\n\n%same_content_length_as_200_in_304_response(Config) ->\n%same_transfer_encoding_as_200_in_304_response(Config) ->\n%304 responses can include a\n%content-length or transfer-encoding header. Their value must\n%be the same as if the request was an unconditional GET. (RFC7230 3.3, RFC7230 3.3.1, RFC7230 3.3.2)\n%\n%no_content_length_in_100_response(Config) ->\n%no_content_length_in_101_response(Config) ->\n%no_content_length_in_102_response(Config) ->\n%1xx, 204 responses and \"2xx responses to CONNECT requests\" must\n%not include a content-length or transfer-encoding header. (RFC7230 3.3.1, RFC7230 3.3.2)\n\nno_content_length_in_204_response(Config) ->\n\tdoc(\"204 responses must not include a content-length header. \"\n\t\t\"(RFC7230 3.3.1, RFC7230 3.3.2)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"GET /resp/reply3/204 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 204, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{Headers, <<>>} = cow_http:parse_headers(Rest),\n\tfalse = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\nno_content_length_in_empty_304_response(Config) ->\n\tdoc(\"304 responses should not include a content-length header, \"\n\t\t\"unless it matches the resource's and was therefore set \"\n\t\t\"explicitly by the user. (RFC7230 3.3.1, RFC7230 3.3.2)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"GET /resp/reply3/304 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 304, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{Headers, <<>>} = cow_http:parse_headers(Rest),\n\tfalse = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\n%%% @todo CONNECT no_content_length_in_2xx_response_to_connect_request(Config) ->\n%no_transfer_encoding_in_100_response(Config) ->\n%no_transfer_encoding_in_101_response(Config) ->\n%no_transfer_encoding_in_102_response(Config) ->\n%1xx, 204 responses and \"2xx responses to CONNECT requests\" must\n%not include a content-length or transfer-encoding header. (RFC7230 3.3.1, RFC7230 3.3.2)\n\n%% We only send transfer-encoding when streaming a response body.\n%% We therefore need a streamed response in order to see a potential bug.\nno_transfer_encoding_in_204_response(Config) ->\n\tdoc(\"204 responses must not include a transfer-encoding header. \"\n\t\t\"(RFC7230 3.3.1, RFC7230 3.3.2)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"GET /resp/stream_reply2/204 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 204, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{Headers, <<>>} = cow_http:parse_headers(Rest),\n\tfalse = lists:keyfind(<<\"transfer-encoding\">>, 1, Headers),\n\tok.\n\n%%% @todo CONNECT no_transfer_encoding_in_2xx_response_to_connect_request(Config) ->\n%1xx, 204 responses and \"2xx responses to CONNECT requests\" must\n%not include a content-length or transfer-encoding header. (RFC7230 3.3.1, RFC7230 3.3.2)\n%\n%```\n%message-body = *OCTET\n%```\n%\n%The message body is the octets after decoding any transfer\n%codings. (RFC7230 3.3)\n\ncontent_length_0_when_no_body(Config) ->\n\tdoc(\"When the length is known in advance, the server must send a \"\n\t\t\"content-length header, including if the length is 0. (RFC7230 3.3.2, RFC7230 3.3.3)\"),\n\t#{code := 200, headers := RespHeaders} = do_raw(Config, [\n\t\t\"GET /resp/reply2/200 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, <<\"0\">>} = lists:keyfind(<<\"content-length\">>, 1, RespHeaders),\n\tok.\n\ncontent_length_response(Config) ->\n\tdoc(\"When the length is known in advance, the server must send a \"\n\t\t\"content-length header. (RFC7230 3.3.2, RFC7230 3.3.3)\"),\n\t#{code := 200, headers := RespHeaders} = do_raw(Config, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, <<\"12\">>} = lists:keyfind(<<\"content-length\">>, 1, RespHeaders),\n\tok.\n\nchunked_response(Config) ->\n\tdoc(\"When the length is not known in advance, the chunked transfer-encoding \"\n\t\t\"must be used. (RFC7230 3.3.2, RFC7230 3.3.3)\"),\n\t#{code := 200, headers := RespHeaders} = do_raw(Config, [\n\t\t\"GET /resp/stream_reply2/200 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, <<\"chunked\">>} = lists:keyfind(<<\"transfer-encoding\">>, 1, RespHeaders),\n\t%% @todo We probably want to check the body received too.\n\tok.\n\n%compat_no_content_length_or_transfer_encoding_close_on_body_end(Config) ->\n%For compatibility purposes a server can send no content-length or\n%transfer-encoding header. In this case the connection must be\n%closed after the response has been sent fully. (RFC7230 3.3.2, RFC7230 3.3.3)\n\nno_content_length_if_transfer_encoding(Config) ->\n\tdoc(\"The content-length header must not be sent when a transfer-encoding \"\n\t\t\"header already exists. (RFC7230 3.3.2)\"),\n\t#{code := 200, headers := RespHeaders} = do_raw(Config, [\n\t\t\"GET /resp/stream_reply2/200 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\tfalse = lists:keyfind(<<\"content-length\">>, 1, RespHeaders),\n\tok.\n\n%@todo\n%The server must not apply the chunked transfer-encoding more than\n%once. (RFC7230 3.3.1)\n%\n%@todo\n%The server must apply the chunked transfer-encoding last. (RFC7230 3.3.1)\n\nhttp10_request_no_transfer_encoding_in_response(Config) ->\n\tdoc(\"The transfer-encoding header must not be sent in responses to \"\n\t\t\"HTTP/1.0 requests, or in responses that use the HTTP/1.0 version. \"\n\t\t\"No transfer codings must be applied in these cases. \"\n\t\t\"(RFC7230 3.3.1, RFC7230 A.1.3)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, [\n\t\t\"GET /resp/stream_reply2/200 HTTP/1.0\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, 200, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{RespHeaders, Body0} = cow_http:parse_headers(Rest),\n\tfalse = lists:keyfind(<<\"content-length\">>, 1, RespHeaders),\n\tfalse = lists:keyfind(<<\"transfer-encoding\">>, 1, RespHeaders),\n\tBody = <<0:8000000>>,\n\t{ok, Body1} = raw_recv(Client, byte_size(Body) - byte_size(Body0), 5000),\n\tBody = << Body0/binary, Body1/binary >>,\n\t%% The end of body is indicated by a connection close.\n\t{error, closed} = raw_recv(Client, 0, 1000),\n\tok.\n\nno_te_no_trailers(Config) ->\n\tdoc(\"Trailers can only be sent if the request includes a TE header \"\n\t\t\"containing \\\"trailers\\\". (RFC7230 4.1.2)\"),\n\t#{code := 200, headers := RespHeaders} = do_raw(Config, [\n\t\t\"GET /resp/stream_trailers HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, <<\"chunked\">>} = lists:keyfind(<<\"transfer-encoding\">>, 1, RespHeaders),\n\tfalse = lists:keyfind(<<\"trailer\">>, 1, RespHeaders),\n\t%% @todo We probably want to check the body received too.\n\tok.\n\nte_trailers(Config) ->\n\tdoc(\"Trailers can only be sent if the request includes a TE header \"\n\t\t\"containing \\\"trailers\\\". (RFC7230 4.1.2)\"),\n\t#{code := 200, headers := RespHeaders} = do_raw(Config, [\n\t\t\"GET /resp/stream_trailers HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"TE: trailers\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, <<\"chunked\">>} = lists:keyfind(<<\"transfer-encoding\">>, 1, RespHeaders),\n\t{_, <<\"grpc-status\">>} = lists:keyfind(<<\"trailer\">>, 1, RespHeaders),\n\t%% @todo We probably want to check the body received too.\n\tok.\n\nte_ignore_chunked(Config) ->\n\tdoc(\"The presence of \\\"chunked\\\" in a TE header must be ignored as it \"\n\t\t\"is always acceptable with HTTP/1.1. (RFC7230 4.3)\"),\n\t#{code := 200, headers := RespHeaders} = do_raw(Config, [\n\t\t\"GET /resp/stream_reply2/200 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"TE: chunked\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, <<\"chunked\">>} = lists:keyfind(<<\"transfer-encoding\">>, 1, RespHeaders),\n\t%% @todo We probably want to check the body received too.\n\tok.\n\nte_ignore_chunked_0(Config) ->\n\tdoc(\"The presence of \\\"chunked\\\" in a TE header must be ignored as it \"\n\t\t\"is always acceptable with HTTP/1.1. (RFC7230 4.3)\"),\n\t#{code := 200, headers := RespHeaders} = do_raw(Config, [\n\t\t\"GET /resp/stream_reply2/200 HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"TE: chunked;q=0\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{_, <<\"chunked\">>} = lists:keyfind(<<\"transfer-encoding\">>, 1, RespHeaders),\n\t%% @todo We probably want to check the body received too.\n\tok.\n\n%%% @todo te_not_acceptable_coding(Config) ->\n%A qvalue of 0 in the TE header means \"not acceptable\". (RFC7230 4.3)\n%\n%@todo\n%The lack of a TE header or an empty TE header means only \"chunked\"\n%(with no trailers) or no transfer-encoding is acceptable. (RFC7230 4.3)\n%\n%@todo\n%Trailer headers must be listed in the trailer header field value. (RFC7230 4.4)\n\n%% Upgrade.\n\n%```\n%Upgrade = 1#protocol\n%\n%protocol = protocol-name [\"/\" protocol-version]\n%protocol-name = token\n%protocol-version = token\n%```\n%\n%The upgrade header contains the list of protocols the\n%client wishes to upgrade to, in order of preference. (RFC7230 6.7)\n\nupgrade_safely_ignored(Config) ->\n\tdoc(\"The upgrade header can be safely ignored. (RFC7230 6.7)\"),\n\t#{code := 200} = do_raw(Config,\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: upgrade\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\").\n\n%upgrade_must_be_in_connection_header(Config) ->\n%The upgrade header must be listed under the connection header,\n%or must be ignored otherwise. (RFC7230 6.7)\n%\n%@todo\n%A server accepting an upgrade request must send a 101 status\n%code with a upgrade header listing the protocol(s) it upgrades\n%to, in layer-ascending order. In addition the upgrade header\n%must be listed in the connection header. (RFC7230 6.7)\n%\n%%A server must not switch to a protocol not listed in the\n%%request's upgrade header. (RFC7230 6.7)\n%\n%@todo\n%A server that sends a 426 status code must include a upgrade\n%header listing acceptable protocols in order of preference. (RFC7230 6.7)\n%\n%@todo\n%A server can send a upgrade header to any response to advertise\n%its support for other protocols listed in order of preference. (RFC7230 6.7)\n%\n%@todo\n%Immediately after a server responds with a 101 status code\n%it must respond to the original request using the new protocol. (RFC7230 6.7)\n%\n%@todo\n%%A server must not switch protocols unless the original message's\n%%semantics can be honored by the new protocol. OPTIONS requests\n%%can be honored by any protocol. (RFC7230 6.7)\n%\n%http10_ignore_upgrade_header(Config) ->\n%A server must ignore an upgrade header received by an HTTP/1.0\n%request. (RFC7230 6.7)\n%\n%expect_then_upgrade(Config) ->\n%A server receiving both an upgrade header and an expect header\n%containing \"100-continue\" must send a 100 response before the\n%101 response. (RFC7230 6.7)\n%\n%The upgrade header field cannot be used for switching the\n%connection protocol (e.g. TCP) or switching connections. (RFC7230 6.7)\n\n%% Compatibility.\n\n%@todo\n%A server can choose to be non-conformant to the specifications\n%for the sake of compatibility. Such behavior can be enabled\n%through configuration and/or software identification. (RFC7230 2.5)\n"
  },
  {
    "path": "test/rfc7231_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(rfc7231_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n-import(cowboy_test, [gun_open/2]).\n-import(cowboy_test, [raw_open/1]).\n-import(cowboy_test, [raw_send/2]).\n-import(cowboy_test, [raw_recv_head/1]).\n-import(cowboy_test, [raw_recv/3]).\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{\"[...]\", [\n\t\t{\"*\", asterisk_h, []},\n\t\t{\"/\", hello_h, []},\n\t\t{\"/echo/:key\", echo_h, []},\n\t\t{\"/delay/echo/:key\", echo_h, []},\n\t\t{\"/resp/:key[/:arg]\", resp_h, []},\n\t\t{\"/ws\", ws_init_h, #{}}\n\t]}]).\n\n%% @todo The documentation should list what methods, headers and status codes\n%% are handled automatically so users can know what befalls to them to implement.\n\n%% Representations.\n\n%% Cowboy has cowboy_compress_h that could be concerned with this.\n%% However Cowboy will not attempt to compress if any content-coding\n%% is already applied, regardless of what they are.\n%\n%   If one or more encodings have been applied to a representation, the\n%   sender that applied the encodings MUST generate a Content-Encoding\n%   header field that lists the content codings in the order in which\n%   they were applied.  Additional information about the encoding\n%   parameters can be provided by other header fields not defined by this\n%   specification. (RFC7231 3.1.2.2)\n\n%% Methods.\n\nmethod_get(Config) ->\n\tdoc(\"The GET method is accepted. (RFC7231 4.3.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"Hello world!\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\nmethod_head(Config) ->\n\tdoc(\"The HEAD method is accepted. (RFC7231 4.3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:head(ConnPid, \"/\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, fin, 200, _} = gun:await(ConnPid, Ref),\n\tok.\n\nmethod_head_same_resp_headers_as_get(Config) ->\n\tdoc(\"Responses to HEAD should return the same headers as GET. (RFC7231 4.3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef1 = gun:get(ConnPid, \"/\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, Headers1} = gun:await(ConnPid, Ref1),\n\t{ok, <<\"Hello world!\">>} = gun:await_body(ConnPid, Ref1),\n\tRef2 = gun:head(ConnPid, \"/\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, fin, 200, Headers2} = gun:await(ConnPid, Ref2),\n\t%% We remove the date header since the date might have changed between requests.\n\tHeaders = lists:keydelete(<<\"date\">>, 1, Headers1),\n\tHeaders = lists:keydelete(<<\"date\">>, 1, Headers2),\n\tok.\n\nmethod_head_same_resp_headers_as_get_stream_reply(Config) ->\n\tdoc(\"Responses to HEAD should return the same headers as GET. (RFC7231 4.3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef1 = gun:get(ConnPid, \"/resp/stream_reply2/200\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, Headers1} = gun:await(ConnPid, Ref1),\n\t{ok, _} = gun:await_body(ConnPid, Ref1),\n\tRef2 = gun:head(ConnPid, \"/resp/stream_reply2/200\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, fin, 200, Headers2} = gun:await(ConnPid, Ref2),\n\t%% We remove the date header since the date might have changed between requests.\n\tHeaders = lists:keydelete(<<\"date\">>, 1, Headers1),\n\tHeaders = lists:keydelete(<<\"date\">>, 1, Headers2),\n\tok.\n\nmethod_post(Config) ->\n\tdoc(\"The POST method is accepted. (RFC7231 4.3.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, \"/echo/read_body\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"application/x-www-form-urlencoded\">>}\n\t], <<\"hello=world\">>),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"hello=world\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\nmethod_put(Config) ->\n\tdoc(\"The PUT method is accepted. (RFC7231 4.3.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:put(ConnPid, \"/echo/read_body\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"application/x-www-form-urlencoded\">>}\n\t], <<\"hello=world\">>),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"hello=world\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\nmethod_delete(Config) ->\n\tdoc(\"The DELETE method is accepted. (RFC7231 4.3.5)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:delete(ConnPid, \"/echo/method\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"DELETE\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\n%% @todo This test is currently broken because Gun does not\n%% send a proper CONNECT request.\n%method_connect(Config) ->\n%\tdoc(\"The CONNECT method is currently not implemented. (RFC7231 4.3.6)\"),\n%\tConnPid = gun_open(Config),\n%\tRef = gun:request(ConnPid, <<\"CONNECT\">>, \"localhost:8080\", [\n%\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n%\t], <<>>),\n%\t{response, fin, 501, _} = gun:await(ConnPid, Ref),\n%\tok.\n\n%   A client sending a CONNECT request MUST send the authority form of\n%   request-target (Section 5.3 of [RFC7230]); i.e., the request-target\n%   consists of only the host name and port number of the tunnel\n%   destination, separated by a colon.\n%\n%   A server MUST NOT send any Transfer-Encoding or Content-Length header\n%   fields in a 2xx (Successful) response to CONNECT.  A client MUST\n%   ignore any Content-Length or Transfer-Encoding header fields received\n%   in a successful response to CONNECT.\n%\n%   A payload within a CONNECT request message has no defined semantics;\n%   sending a payload body on a CONNECT request might cause some existing\n%   implementations to reject the request.\n\nmethod_options(Config) ->\n\tdoc(\"The OPTIONS method is accepted. (RFC7231 4.3.7)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:options(ConnPid, \"/echo/method\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"OPTIONS\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\nmethod_options_asterisk(Config) ->\n\tdoc(\"The OPTIONS method is accepted with an asterisk. (RFC7231 4.3.7)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:options(ConnPid, \"*\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-echo\">>, <<\"method\">>}\n\t]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"OPTIONS\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\nmethod_options_content_length_0(Config) ->\n\tdoc(\"The OPTIONS method must set the content-length header \"\n\t\t\"to 0 when no body is returned. (RFC7231 4.3.7)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:options(ConnPid, \"*\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, fin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"0\">>} = lists:keyfind(<<\"content-length\">>, 1, Headers),\n\tok.\n\nmethod_trace(Config) ->\n\tdoc(\"The TRACE method is currently not implemented. (RFC7231 4.3.8)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:request(ConnPid, <<\"TRACE\">>, \"/\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t], <<>>),\n\t{response, fin, 501, _} = gun:await(ConnPid, Ref),\n\tok.\n\n%% Request headers.\n\n%% @todo It could be useful to check that we can parse all request headers defined in this RFC.\n%% @todo The same applies to any other RFC for which we have a test suite.\n\nexpect(Config) ->\n\tdoc(\"A server that receives a 100-continue expectation should honor it. (RFC7231 5.1.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, \"/echo/read_body\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"application/x-www-form-urlencoded\">>},\n\t\t{<<\"expect\">>, <<\"100-continue\">>}\n\t]),\n\t{inform, 100, _} = gun:await(ConnPid, Ref),\n\tgun:close(ConnPid).\n\nhttp10_expect(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp ->\n\t\t\tdo_http10_expect(Config);\n\t\thttp2 ->\n\t\t\texpect(Config);\n\t\thttp3 ->\n\t\t\texpect(Config)\n\tend.\n\ndo_http10_expect(Config) ->\n\tdoc(\"A server that receives a 100-continue expectation \"\n\t\t\"in an HTTP/1.0 request must ignore it. (RFC7231 5.1.1)\"),\n\tBody = <<\"hello=world\">>,\n\tConnPid = gun_open(Config, #{http_opts => #{version => 'HTTP/1.0'}}),\n\tRef = gun:post(ConnPid, \"/echo/read_body\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"application/x-www-form-urlencoded\">>},\n\t\t{<<\"content-length\">>, integer_to_binary(byte_size(Body))},\n\t\t{<<\"expect\">>, <<\"100-continue\">>}\n\t]),\n\ttimer:sleep(500),\n\tok = gun:data(ConnPid, Ref, fin, Body),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, Body} = gun:await_body(ConnPid, Ref),\n\tok.\n\n%% Cowboy ignores the expect header when the value is not 100-continue.\n%\n%   A server that receives an Expect field-value other than 100-continue\n%   MAY respond with a 417 (Expectation Failed) status code to indicate\n%   that the unexpected expectation cannot be met.\n\nexpect_receive_body_omit_100_continue(Config) ->\n\tdoc(\"A server may omit sending a 100 Continue response if it has \"\n\t\t\"already started receiving the request body. (RFC7231 5.1.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, \"/delay/echo/read_body\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"application/x-www-form-urlencoded\">>},\n\t\t{<<\"expect\">>, <<\"100-continue\">>}\n\t], <<\"hello=world\">>),\n\t%% We receive the response directly without a 100 Continue.\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, <<\"hello=world\">>} = gun:await_body(ConnPid, Ref),\n\tok.\n\nexpect_discard_body_skip(Config) ->\n\tdoc(\"A server that responds with a final status code before reading \"\n\t\t\"the entire message body should keep the connection open and skip \"\n\t\t\"the body when appropriate. (RFC7231 5.1.1)\"),\n\tConnPid = gun_open(Config),\n\tRef1 = gun:post(ConnPid, \"/echo/method\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"application/x-www-form-urlencoded\">>},\n\t\t{<<\"expect\">>, <<\"100-continue\">>}\n\t], <<\"hello=world\">>),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref1),\n\t{ok, <<\"POST\">>} = gun:await_body(ConnPid, Ref1),\n\tRef2 = gun:get(ConnPid, \"/echo/method\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-type\">>, <<\"application/x-www-form-urlencoded\">>}\n\t]),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref2),\n\t{ok, <<\"GET\">>} = gun:await_body(ConnPid, Ref2),\n\tok.\n\nexpect_discard_body_close(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp ->\n\t\t\tdo_expect_discard_body_close(Config);\n\t\thttp2 ->\n\t\t\tdoc(\"There's no reason to close the connection when using HTTP/2, \"\n\t\t\t\t\"even if a stream body is too large. We just cancel the stream.\");\n\t\thttp3 ->\n\t\t\tdoc(\"There's no reason to close the connection when using HTTP/3, \"\n\t\t\t\t\"even if a stream body is too large. We just cancel the stream.\")\n\tend.\n\ndo_expect_discard_body_close(Config) ->\n\tdoc(\"A server that responds with a final status code before reading \"\n\t\t\"the entire message body may close the connection to avoid \"\n\t\t\"reading a potentially large request body. (RFC7231 5.1.1, RFC7230 6.6)\"),\n\tConnPid = gun_open(Config),\n\tRef1 = gun:post(ConnPid, \"/echo/method\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-length\">>, <<\"10000000\">>},\n\t\t{<<\"content-type\">>, <<\"application/x-www-form-urlencoded\">>},\n\t\t{<<\"expect\">>, <<\"100-continue\">>}\n\t]),\n\t{response, nofin, 200, _Headers} = gun:await(ConnPid, Ref1),\n\t%% Ideally we would send a connection: close. Cowboy however\n\t%% cannot know the intent of the application until after we\n\t%% sent the response.\n%\t{_, <<\"close\">>} = lists:keyfind(<<\"connection\">>, 1, Headers),\n\t{ok, <<\"POST\">>} = gun:await_body(ConnPid, Ref1),\n\t%% The connection is gone.\n\treceive\n\t\t{gun_down, ConnPid, _, closed, _} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\nno_accept_encoding(Config) ->\n\tdoc(\"While a request with no accept-encoding header implies the \"\n\t\t\"user agent has no preferences and any would be acceptable, \"\n\t\t\"Cowboy will not serve content-codings by defaults to ensure \"\n\t\t\"the content can safely be read. (RFC7231 5.3.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/stream_reply2/200\"),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\tok.\n\n%% Cowboy currently ignores any information about the identity content-coding\n%% and instead considers it always acceptable.\n%\n%   2.  If the representation has no content-coding, then it is\n%       acceptable by default unless specifically excluded by the\n%       Accept-Encoding field stating either \"identity;q=0\" or \"*;q=0\"\n%       without a more specific entry for \"identity\".\n\naccept_encoding_gzip(Config) ->\n\tdoc(\"No qvalue means the content-coding is acceptable. (RFC7231 5.3.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/stream_reply2/200\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t_ = case config(flavor, Config) of\n\t\tcompress ->\n\t\t\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers);\n\t\t_ ->\n\t\t\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers)\n\tend,\n\tok.\n\naccept_encoding_gzip_1(Config) ->\n\tdoc(\"A qvalue different than 0 means the content-coding is acceptable. (RFC7231 5.3.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/stream_reply2/200\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip;q=1.0\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t_ = case config(flavor, Config) of\n\t\tcompress ->\n\t\t\t{_, <<\"gzip\">>} = lists:keyfind(<<\"content-encoding\">>, 1, Headers);\n\t\t_ ->\n\t\t\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers)\n\tend,\n\tok.\n\naccept_encoding_gzip_0(Config) ->\n\tdoc(\"A qvalue of 0 means the content-coding is not acceptable. (RFC7231 5.3.1, RFC7231 5.3.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/stream_reply2/200\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip;q=0\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\tok.\n\n%% Cowboy currently only supports gzip automatically via cowboy_compress_h.\n%\n%   4.  If multiple content-codings are acceptable, then the acceptable\n%       content-coding with the highest non-zero qvalue is preferred.\n\naccept_encoding_empty(Config) ->\n\tdoc(\"An empty content-coding means that the user agent does not \"\n\t\t\"want any content-coding applied to the response. (RFC7231 5.3.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/stream_reply2/200\", [\n\t\t{<<\"accept-encoding\">>, <<>>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\tok.\n\naccept_encoding_unknown(Config) ->\n\tdoc(\"An accept-encoding header only containing unknown content-codings \"\n\t\t\"should result in no content-coding being applied. (RFC7231 5.3.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/stream_reply2/200\", [\n\t\t{<<\"accept-encoding\">>, <<\"deflate\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"content-encoding\">>, 1, Headers),\n\tok.\n\n%% Status codes.\n\nhttp10_status_code_100(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp ->\n\t\t\tdoc(\"The 100 Continue status code must not \"\n\t\t\t\t\"be sent to HTTP/1.0 endpoints. (RFC7231 6.2)\"),\n\t\t\tdo_unsupported_status_code_1xx(100, Config);\n\t\thttp2 ->\n\t\t\tstatus_code_100(Config);\n\t\thttp3 ->\n\t\t\tstatus_code_100(Config)\n\tend.\n\nhttp10_status_code_101(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp ->\n\t\t\tdoc(\"The 101 Switching Protocols status code must not \"\n\t\t\t\t\"be sent to HTTP/1.0 endpoints. (RFC7231 6.2)\"),\n\t\t\tdo_unsupported_status_code_1xx(101, Config);\n\t\thttp2 ->\n\t\t\tstatus_code_101(Config);\n\t\thttp3 ->\n\t\t\t%% While 101 is not supported by HTTP/3, there is no\n\t\t\t%% wording in RFC9114 that forbids sending it.\n\t\t\tstatus_code_101(Config)\n\tend.\n\ndo_unsupported_status_code_1xx(StatusCode, Config) ->\n\tConnPid = gun_open(Config, #{http_opts => #{version => 'HTTP/1.0'}}),\n\tRef = gun:get(ConnPid, \"/resp/inform2/\" ++ integer_to_list(StatusCode), [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_100(Config) ->\n\tdoc(\"The 100 Continue status code can be sent. (RFC7231 6.2.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/inform2/100\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{inform, 100, []} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_101(Config) ->\n\tdoc(\"The 101 Switching Protocols status code can be sent. (RFC7231 6.2.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/inform2/101\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{inform, 101, []} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_200(Config) ->\n\tdoc(\"The 200 OK status code can be sent. (RFC7231 6.3.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/200\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_201(Config) ->\n\tdoc(\"The 201 Created status code can be sent. (RFC7231 6.3.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/201\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 201, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_202(Config) ->\n\tdoc(\"The 202 Accepted status code can be sent. (RFC7231 6.3.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/202\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 202, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_203(Config) ->\n\tdoc(\"The 203 Non-Authoritative Information status code can be sent. (RFC7231 6.3.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/203\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 203, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_204(Config) ->\n\tdoc(\"The 204 No Content status code can be sent. (RFC7231 6.3.5)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/204\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 204, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_205(Config) ->\n\tdoc(\"The 205 Reset Content status code can be sent. (RFC7231 6.3.6)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/205\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 205, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_300(Config) ->\n\tdoc(\"The 300 Multiple Choices status code can be sent. (RFC7231 6.4.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/300\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 300, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_301(Config) ->\n\tdoc(\"The 301 Moved Permanently status code can be sent. (RFC7231 6.4.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/301\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 301, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_302(Config) ->\n\tdoc(\"The 302 Found status code can be sent. (RFC7231 6.4.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/302\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 302, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_303(Config) ->\n\tdoc(\"The 303 See Other status code can be sent. (RFC7231 6.4.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/303\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 303, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_305(Config) ->\n\tdoc(\"The 305 Use Proxy status code can be sent. (RFC7231 6.4.5)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/305\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 305, _} = gun:await(ConnPid, Ref),\n\tok.\n\n%% The status code 306 is no longer used. (RFC7231 6.4.6)\n\nstatus_code_307(Config) ->\n\tdoc(\"The 307 Temporary Redirect status code can be sent. (RFC7231 6.4.7)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/307\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 307, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_400(Config) ->\n\tdoc(\"The 400 Bad Request status code can be sent. (RFC7231 6.5.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/400\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 400, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_402(Config) ->\n\tdoc(\"The 402 Payment Required status code can be sent. (RFC7231 6.5.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/402\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 402, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_403(Config) ->\n\tdoc(\"The 403 Forbidden status code can be sent. (RFC7231 6.5.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/403\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 403, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_404(Config) ->\n\tdoc(\"The 404 Not Found status code can be sent. (RFC7231 6.5.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/404\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 404, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_404_not_found(Config) ->\n\tdoc(\"The 404 Not Found status code is sent when the target \"\n\t\t\"resource does not exist. (RFC7231 6.5.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/not/found\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 404, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_405(Config) ->\n\tdoc(\"The 405 Method Not Allowed status code can be sent. (RFC7231 6.5.5)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/405\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 405, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_406(Config) ->\n\tdoc(\"The 406 Not Acceptable status code can be sent. (RFC7231 6.5.6)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/406\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 406, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_408(Config) ->\n\tdoc(\"The 408 Request Timeout status code can be sent. (RFC7231 6.5.7)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/408\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 408, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_408_connection_close(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp ->\n\t\t\tdo_http11_status_code_408_connection_close(Config);\n\t\thttp2 ->\n\t\t\tdoc(\"HTTP/2 connections are not closed on 408 responses.\");\n\t\thttp3 ->\n\t\t\tdoc(\"HTTP/3 connections are not closed on 408 responses.\")\n\tend.\n\ndo_http11_status_code_408_connection_close(Config) ->\n\tdoc(\"A 408 response should result in a connection close \"\n\t\t\"for HTTP/1.1 connections. (RFC7231 6.5.7)\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, \"GET / HTTP/1.1\\r\\n\"),\n\t{_, 408, _, Rest} = cow_http:parse_status_line(raw_recv_head(Client)),\n\t{Headers, <<>>} = cow_http:parse_headers(Rest),\n\t{_, <<\"close\">>} = lists:keyfind(<<\"connection\">>, 1, Headers),\n\t{error, closed} = raw_recv(Client, 0, 1000),\n\tok.\n\nstatus_code_409(Config) ->\n\tdoc(\"The 409 Conflict status code can be sent. (RFC7231 6.5.8)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/409\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 409, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_410(Config) ->\n\tdoc(\"The 410 Gone status code can be sent. (RFC7231 6.5.9)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/410\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 410, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_411(Config) ->\n\tdoc(\"The 411 Length Required status code can be sent. (RFC7231 6.5.10)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/411\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 411, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_413(Config) ->\n\tdoc(\"The 413 Payload Too Large status code can be sent. (RFC7231 6.5.11)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/413\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 413, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_414(Config) ->\n\tdoc(\"The 414 URI Too Long status code can be sent. (RFC7231 6.5.12)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/414\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 414, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_415(Config) ->\n\tdoc(\"The 415 Unsupported Media Type status code can be sent. (RFC7231 6.5.13)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/415\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 415, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_417(Config) ->\n\tdoc(\"The 417 Expectation Failed status code can be sent. (RFC7231 6.5.14)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/417\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 417, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_426(Config) ->\n\tdoc(\"The 426 Upgrade Required status code can be sent. (RFC7231 6.5.15)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/426\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 426, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_426_upgrade_header(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp ->\n\t\t\tdo_status_code_426_upgrade_header(Config);\n\t\thttp2 ->\n\t\t\tdoc(\"HTTP/2 does not support the HTTP/1.1 Upgrade mechanism.\");\n\t\thttp3 ->\n\t\t\tdoc(\"HTTP/3 does not support the HTTP/1.1 Upgrade mechanism.\")\n\tend.\n\ndo_status_code_426_upgrade_header(Config) ->\n\tdoc(\"A 426 response must include a upgrade header. (RFC7231 6.5.15)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/ws?ok\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 426, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"upgrade\">>} = lists:keyfind(<<\"connection\">>, 1, Headers),\n\t{_, <<\"websocket\">>} = lists:keyfind(<<\"upgrade\">>, 1, Headers),\n\tok.\n\nstatus_code_500(Config) ->\n\tdoc(\"The 500 Internal Server Error status code can be sent. (RFC7231 6.6.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/500\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 500, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_501(Config) ->\n\tdoc(\"The 501 Not Implemented status code can be sent. (RFC7231 6.6.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/501\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 501, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_502(Config) ->\n\tdoc(\"The 502 Bad Gateway status code can be sent. (RFC7231 6.6.3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/502\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 502, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_503(Config) ->\n\tdoc(\"The 503 Service Unavailable status code can be sent. (RFC7231 6.6.4)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/503\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 503, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_504(Config) ->\n\tdoc(\"The 504 Gateway Timeout status code can be sent. (RFC7231 6.6.5)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/504\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 504, _} = gun:await(ConnPid, Ref),\n\tok.\n\nstatus_code_505(Config) ->\n\tdoc(\"The 505 HTTP Version Not Supported status code can be sent. (RFC7231 6.6.6)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/505\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 505, _} = gun:await(ConnPid, Ref),\n\tok.\n\n%% The 505 response code is supposed to be about the major HTTP version.\n%% Cowboy instead rejects any version that isn't HTTP/1.0 or HTTP/1.1\n%% when expecting an h1 request. While this is not correct in theory\n%% it works in practice because there are no other minor versions.\n%%\n%% Cowboy does not do version checking for HTTP/2 since the protocol\n%% does not include a version number in the messages.\n\n%% Response headers.\n\n%% @todo No such header in this suite, but some in other suites (if-(un)modified-since).\n%   A recipient that parses a timestamp value in an HTTP header field\n%   MUST accept all three HTTP-date formats. (RFC7231 7.1.1.1)\n\ndate_imf_fixdate(Config) ->\n\tdoc(\"The date header uses the IMF-fixdate format. (RFC7231 7.1.1.1, RFC7231 7.1.1.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<_,_,_,\", \",_,_,\" \",_,_,_,\" \",_,_,_,_,\" \",_,_,\":\",_,_,\":\",_,_,\" GMT\">>}\n\t\t= lists:keyfind(<<\"date\">>, 1, Headers),\n\tok.\n\n%% @todo Applies to both date and other headers (if-(un)modified-since).\n%   HTTP-date is case sensitive.  A sender MUST NOT generate additional\n%   whitespace in an HTTP-date beyond that specifically included as SP in\n%   the grammar.  The semantics of day-name, day, month, year, and\n%   time-of-day are the same as those defined for the Internet Message\n%   Format constructs with the corresponding name ([RFC5322], Section\n%   3.3). (RFC7231 7.1.1.1)\n\n%% @todo No such header in this suite, but some in other suites (if-(un)modified-since).\n%   Recipients of a timestamp value in rfc850-date format, which uses a\n%   two-digit year, MUST interpret a timestamp that appears to be more\n%   than 50 years in the future as representing the most recent year in\n%   the past that had the same last two digits. (RFC7231 7.1.1.1)\n\n%% @todo Add an option to disable sending the date header.\n%   An origin server MUST NOT send a Date header field if it does not\n%   have a clock capable of providing a reasonable approximation of the\n%   current instance in Coordinated Universal Time. (RFC7231 7.1.1.2)\n\nno_date_1xx(Config) ->\n\tdoc(\"The date header is optional for 1xx responses. \"\n\t\t\"Cowboy does not send it with those responses. (RFC7231 7.1.1.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/inform2/100\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{inform, 100, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(<<\"date\">>, 1, Headers),\n\tok.\n\ndate_2xx(Config) ->\n\tdoc(\"A date header must be sent for 2xx status codes. (RFC7231 7.1.1.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/200\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, _} = lists:keyfind(<<\"date\">>, 1, Headers),\n\tok.\n\ndate_3xx(Config) ->\n\tdoc(\"A date header must be sent for 3xx status codes. (RFC7231 7.1.1.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/300\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 300, Headers} = gun:await(ConnPid, Ref),\n\t{_, _} = lists:keyfind(<<\"date\">>, 1, Headers),\n\tok.\n\ndate_4xx(Config) ->\n\tdoc(\"A date header must be sent for 4xx status codes. (RFC7231 7.1.1.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/400\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 400, Headers} = gun:await(ConnPid, Ref),\n\t{_, _} = lists:keyfind(<<\"date\">>, 1, Headers),\n\tok.\n\ndate_5xx(Config) ->\n\tdoc(\"The date header is optional for 5xx status codes. \"\n\t\t\"Cowboy however does send it with those responses. (RFC7231 7.1.1.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/500\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 500, Headers} = gun:await(ConnPid, Ref),\n\t{_, _} = lists:keyfind(<<\"date\">>, 1, Headers),\n\tok.\n\nserver_header(Config) ->\n\tdoc(\"An origin server may generate a server header field. \"\n\t\t\"Cowboy generates a small one by default. (RFC7231 7.4.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/\"),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"Cowboy\">>} = lists:keyfind(<<\"server\">>, 1, Headers),\n\tok.\n\nserver_header_override(Config) ->\n\tdoc(\"An origin server may generate a server header field. \"\n\t\t\"Cowboy allows the user to override the default. (RFC7231 7.4.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/set_resp_header_server\"),\n\t{response, _, 200, Headers} = gun:await(ConnPid, Ref),\n\t{_, <<\"nginx\">>} = lists:keyfind(<<\"server\">>, 1, Headers),\n\tok.\n\n%% @todo It's worth revisiting this RFC in the context of cowboy_rest\n%% to ensure the state machine is doing what's expected by the RFC.\n"
  },
  {
    "path": "test/rfc7538_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(rfc7538_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{\"[...]\", [\n\t\t{\"/resp/:key[/:arg]\", resp_h, []}\n\t]}]).\n\nstatus_code_308(Config) ->\n\tdoc(\"The 308 Permanent Redirect status code can be sent. (RFC7538 3)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/308\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{response, _, 308, _} = gun:await(ConnPid, Ref),\n\tok.\n\n%% @todo\n%   The server SHOULD generate a Location header field ([RFC7231],\n%   Section 7.1.2) in the response containing a preferred URI reference\n%   for the new permanent URI.\n"
  },
  {
    "path": "test/rfc7540_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n%% Note that Cowboy does not implement the PRIORITY mechanism.\n%% Everyone has been moving away from it and it is widely seen\n%% as a failure. Setting priorities has been counter productive\n%% with regards to performance. Clients have been moving away\n%% from the mechanism.\n\n-module(rfc7540_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(ct_helper, [get_remote_pid_tcp/1]).\n-import(cowboy_test, [gun_open/1]).\n-import(cowboy_test, [raw_open/1]).\n-import(cowboy_test, [raw_send/2]).\n-import(cowboy_test, [raw_recv_head/1]).\n-import(cowboy_test, [raw_recv/3]).\n\nall() -> [{group, clear}, {group, tls}].\n\ngroups() ->\n\tTests = ct_helper:all(?MODULE),\n\tRejectTLS = [http_upgrade_reject_tls, prior_knowledge_reject_tls],\n\tClear = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =/= \"alpn\"] -- RejectTLS,\n\tTLS = [T || T <- Tests, lists:sublist(atom_to_list(T), 4) =:= \"alpn\"] ++ RejectTLS,\n\t[{clear, [parallel], Clear}, {tls, [parallel], TLS}].\n\ninit_per_group(Name = clear, Config) ->\n\t[{protocol, http2}|cowboy_test:init_http(Name, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))},\n\t\t%% Disable the DATA threshold for this test suite.\n\t\tstream_window_data_threshold => 0\n\t}, Config)];\ninit_per_group(Name = tls, Config) ->\n\tcowboy_test:init_http2(Name, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))},\n\t\t%% Disable the DATA threshold for this test suite.\n\t\tstream_window_data_threshold => 0\n\t}, Config).\n\nend_per_group(Name, _) ->\n\tok = cowboy:stop_listener(Name).\n\ninit_routes(_) -> [\n\t{\"localhost\", [\n\t\t{\"/\", hello_h, []},\n\t\t{\"/echo/:key\", echo_h, []},\n\t\t{\"/delay_hello\", delay_hello_h, 1200},\n\t\t{\"/long_polling\", long_polling_h, []},\n\t\t{\"/loop_handler_abort\", loop_handler_abort_h, []},\n\t\t{\"/resp/:key[/:arg]\", resp_h, []}\n\t]}\n].\n\n%% Starting HTTP/2 for \"http\" URIs.\n\nhttp_upgrade_reject_tls(Config) ->\n\tdoc(\"Implementations that support HTTP/2 over TLS must use ALPN. (RFC7540 3.4)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}|TlsOpts]),\n\t%% Send a valid preface.\n\tok = ssl:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\t%% We expect the server to send an HTTP 400 error\n\t%% when trying to use HTTP/2 without going through ALPN negotiation.\n\t{ok, <<\"HTTP/1.1 400\">>} = ssl:recv(Socket, 12, 1000),\n\tok.\n\nhttp_upgrade_ignore_h2(Config) ->\n\tdoc(\"An h2 token in an Upgrade field must be ignored. (RFC7540 3.2)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\t{ok, <<\"HTTP/1.1 200\">>} = gen_tcp:recv(Socket, 12, 1000),\n\tok.\n\nhttp_upgrade_ignore_if_http_10(Config) ->\n\tdoc(\"The Upgrade header must be ignored if part of an HTTP/1.0 request. (RFC7230 6.7)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.0\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\t{ok, <<\"HTTP/1.1 200\">>} = gen_tcp:recv(Socket, 12, 1000),\n\tok.\n\nhttp_upgrade_ignore_missing_upgrade_in_connection(Config) ->\n\tdoc(\"The Upgrade header must be listed in the \"\n\t\t\"Connection header field. (RFC7230 6.7)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\t{ok, <<\"HTTP/1.1 200\">>} = gen_tcp:recv(Socket, 12, 1000),\n\tok.\n\nhttp_upgrade_ignore_missing_http2_settings_in_connection(Config) ->\n\tdoc(\"The HTTP2-Settings header must be listed in the \"\n\t\t\"Connection header field. (RFC7540 3.2.1, RFC7230 6.7)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\t{ok, <<\"HTTP/1.1 200\">>} = gen_tcp:recv(Socket, 12, 1000),\n\tok.\n\nhttp_upgrade_ignore_zero_http2_settings_header(Config) ->\n\tdoc(\"The HTTP Upgrade request must include \"\n\t\t\"exactly one HTTP2-Settings header field (RFC7540 3.2, RFC7540 3.2.1)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{ok, <<\"HTTP/1.1 200\">>} = gen_tcp:recv(Socket, 12, 1000),\n\tok.\n\nhttp_upgrade_reject_two_http2_settings_header(Config) ->\n\tdoc(\"The HTTP Upgrade request must include \"\n\t\t\"exactly one HTTP2-Settings header field (RFC7540 3.2, RFC7540 3.2.1)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\t{ok, <<\"HTTP/1.1 400\">>} = gen_tcp:recv(Socket, 12, 1000),\n\tok.\n\nhttp_upgrade_reject_bad_http2_settings_header(Config) ->\n\tdoc(\"The HTTP Upgrade request must include \"\n\t\t\"a valid HTTP2-Settings header field (RFC7540 3.2, RFC7540 3.2.1)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t%% We send a full SETTINGS frame on purpose.\n\t\t\"HTTP2-Settings: \", base64:encode(iolist_to_binary(cow_http2:settings(#{}))), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\t{ok, <<\"HTTP/1.1 400\">>} = gen_tcp:recv(Socket, 12, 1000),\n\tok.\n\n%% Match directly for now.\ndo_recv_101(Socket) ->\n\t{ok, <<\n\t\t\"HTTP/1.1 101 Switching Protocols\\r\\n\"\n\t\t\"connection: Upgrade\\r\\n\"\n\t\t\"upgrade: h2c\\r\\n\"\n\t\t\"\\r\\n\"\n\t>>} = gen_tcp:recv(Socket, 71, 1000),\n\tok.\n\nhttp_upgrade_101(Config) ->\n\tdoc(\"A 101 response must be sent on successful upgrade \"\n\t\t\"to HTTP/2 when using the HTTP Upgrade mechanism. (RFC7540 3.2, RFC7230 6.7)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\tok.\n\nhttp_upgrade_server_preface(Config) ->\n\tdoc(\"The first frame after the upgrade must be a \"\n\t\t\"SETTINGS frame for the server connection preface. (RFC7540 3.2, RFC7540 3.5, RFC7540 6.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Receive the server preface.\n\t{ok, << _:24, 4:8, 0:40 >>} = gen_tcp:recv(Socket, 9, 1000),\n\tok.\n\nhttp_upgrade_client_preface_timeout(Config) ->\n\tdoc(\"Clients negotiating HTTP/2 and not sending a preface in \"\n\t\t\"a timely manner must be disconnected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Receive the response to the initial HTTP/1.1 request.\n\t{ok, << HeadersSkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, HeadersSkipLen, 1000),\n\t{ok, << DataSkipLen:24, 0:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, DataSkipLen, 1000),\n\t%% Do not send the preface. Wait for the server to disconnect us.\n\t{error, closed} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\nhttp_upgrade_reject_missing_client_preface(Config) ->\n\tdoc(\"Servers must treat an invalid connection preface as a \"\n\t\t\"connection error of type PROTOCOL_ERROR. (RFC7540 3.2, RFC7540 3.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Send a SETTINGS frame directly instead of the proper preface.\n\tok = gen_tcp:send(Socket, cow_http2:settings(#{})),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t%% The server may however have already started sending the response to the\n\t%% initial HTTP/1.1 request.\n\tReceived = lists:reverse(lists:foldl(fun(_, Acc) ->\n\t\tcase gen_tcp:recv(Socket, 9, 1000) of\n\t\t\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[headers|Acc];\n\t\t\t{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[data|Acc];\n\t\t\t{error, _} ->\n\t\t\t\t[closed|Acc]\n\t\tend\n\tend, [], [1, 2, 3])),\n\tcase Received of\n\t\t[closed|_] -> ok;\n\t\t[headers, closed|_] -> ok;\n\t\t[headers, data, closed] -> ok\n\tend.\n\nhttp_upgrade_reject_invalid_client_preface(Config) ->\n\tdoc(\"Servers must treat an invalid connection preface as a \"\n\t\t\"connection error of type PROTOCOL_ERROR. (RFC7540 3.2, RFC7540 3.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Send a slightly incorrect preface.\n\tok = gen_tcp:send(Socket, \"PRI * HTTP/2.0\\r\\n\\r\\nSM: Value\\r\\n\\r\\n\"),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t%% The server may however have already started sending the response to the\n\t%% initial HTTP/1.1 request.\n\tReceived = lists:reverse(lists:foldl(fun(_, Acc) ->\n\t\tcase gen_tcp:recv(Socket, 9, 1000) of\n\t\t\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[headers|Acc];\n\t\t\t{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[data|Acc];\n\t\t\t{error, _} ->\n\t\t\t\t[closed|Acc]\n\t\tend\n\tend, [], [1, 2, 3])),\n\tcase Received of\n\t\t[closed|_] -> ok;\n\t\t[headers, closed|_] -> ok;\n\t\t[headers, data, closed] -> ok\n\tend.\n\nhttp_upgrade_reject_missing_client_preface_settings(Config) ->\n\tdoc(\"Servers must treat an invalid connection preface as a \"\n\t\t\"connection error of type PROTOCOL_ERROR. (RFC7540 3.2, RFC7540 3.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Send a valid preface sequence except followed by a PING instead of a SETTINGS frame.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:ping(0)]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t%% The server may however have already started sending the response to the\n\t%% initial HTTP/1.1 request.\n\tReceived = lists:reverse(lists:foldl(fun(_, Acc) ->\n\t\tcase gen_tcp:recv(Socket, 9, 1000) of\n\t\t\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[headers|Acc];\n\t\t\t{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[data|Acc];\n\t\t\t{error, _} ->\n\t\t\t\t[closed|Acc]\n\t\tend\n\tend, [], [1, 2, 3])),\n\tcase Received of\n\t\t[closed|_] -> ok;\n\t\t[headers, closed|_] -> ok;\n\t\t[headers, data, closed] -> ok\n\tend.\n\nhttp_upgrade_reject_invalid_client_preface_settings(Config) ->\n\tdoc(\"Servers must treat an invalid connection preface as a \"\n\t\t\"connection error of type PROTOCOL_ERROR. (RFC7540 3.2, RFC7540 3.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Send a valid preface sequence except followed by a badly formed SETTINGS frame.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", << 0:24, 4:8, 0:9, 1:31 >>]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t%% The server may however have already started sending the response to the\n\t%% initial HTTP/1.1 request.\n\tReceived = lists:reverse(lists:foldl(fun(_, Acc) ->\n\t\tcase gen_tcp:recv(Socket, 9, 1000) of\n\t\t\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[headers|Acc];\n\t\t\t{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[data|Acc];\n\t\t\t{error, _} ->\n\t\t\t\t[closed|Acc]\n\t\tend\n\tend, [], [1, 2, 3])),\n\tcase Received of\n\t\t[closed|_] -> ok;\n\t\t[headers, closed|_] -> ok;\n\t\t[headers, data, closed] -> ok\n\tend.\n\nhttp_upgrade_accept_client_preface_empty_settings(Config) ->\n\tdoc(\"The SETTINGS frame in the client preface may be empty. (RFC7540 3.2, RFC7540 3.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Send a valid preface sequence except followed by an empty SETTINGS frame.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Receive the SETTINGS ack. The response might arrive beforehand.\n\tReceived = lists:reverse(lists:foldl(fun(_, Acc) ->\n\t\tcase gen_tcp:recv(Socket, 9, 1000) of\n\t\t\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[headers|Acc];\n\t\t\t{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[data|Acc];\n\t\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} ->\n\t\t\t\t[settings_ack|Acc]\n\t\tend\n\tend, [], [1, 2, 3])),\n\tcase Received of\n\t\t[settings_ack|_] -> ok;\n\t\t[headers, settings_ack|_] -> ok;\n\t\t[headers, data, settings_ack] -> ok\n\tend.\n\nhttp_upgrade_client_preface_settings_ack_timeout(Config) ->\n\tdoc(\"The SETTINGS frames sent by the client must be acknowledged. (RFC7540 3.5, RFC7540 6.5.3)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Send a valid preface.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Skip the SETTINGS ack. Receive a GOAWAY with reason SETTINGS_TIMEOUT,\n\t%% possibly following a HEADERS or HEADERS and DATA frames.\n\tReceived = lists:reverse(lists:foldl(fun(_, Acc) ->\n\t\tcase gen_tcp:recv(Socket, 9, 6000) of\n\t\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} ->\n\t\t\t\tAcc;\n\t\t\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[headers|Acc];\n\t\t\t{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[data|Acc];\n\t\t\t{ok, << 8:24, 7:8, 0:40 >>} ->\n\t\t\t\t%% We expect a SETTINGS_TIMEOUT reason.\n\t\t\t\t{ok, << 1:32, 4:32 >>} = gen_tcp:recv(Socket, 8, 1000),\n\t\t\t\t[goaway|Acc];\n\t\t\t{error, _} ->\n\t\t\t\t%% Can be timeouts, ignore them.\n\t\t\t\tAcc\n\t\tend\n\tend, [], [1, 2, 3, 4])),\n\tcase Received of\n\t\t[goaway] -> ok;\n\t\t[headers, goaway] -> ok;\n\t\t[headers, data, goaway] -> ok\n\tend.\n\n%% @todo We need a successful test with actual options in HTTP2-Settings.\n%% SETTINGS_MAX_FRAME_SIZE is probably the easiest to test. The relevant\n%% RFC quote is:\n%%\n%% 3.2.1\n%%   A server decodes and interprets these values as it would any other\n%%   SETTINGS frame. Explicit acknowledgement of these settings\n%%   (Section 6.5.3) is not necessary, since a 101 response serves as\n%%   implicit acknowledgement.\n\n%% @todo We need to test an upgrade with a request body. It is probably\n%% worth having a configuration value for how much we accept while still\n%% upgrading (if too big, we would just stay on HTTP/1.1). We therefore\n%% needs a test for when the body is small enough, and one for when the\n%% body is larger than we accept. The relevant RFC quote is:\n%%\n%% 3.2\n%%   Requests that contain a payload body MUST be sent in their entirety\n%%   before the client can send HTTP/2 frames.  This means that a large\n%%   request can block the use of the connection until it is completely\n%%   sent.\n\n%% @todo We should definitely have a test with OPTIONS. The relevant\n%% RFC quote is:\n%%\n%% 3.2\n%%   If concurrency of an initial request with subsequent requests is\n%%   important, an OPTIONS request can be used to perform the upgrade to\n%%   HTTP/2, at the cost of an additional round trip.\n\nhttp_upgrade_response(Config) ->\n\tdoc(\"A response must be sent to the initial HTTP/1.1 request \"\n\t\t\"after switching to HTTP/2. The response must use \"\n\t\t\"the stream identifier 1. (RFC7540 3.2)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Send a valid preface.\n\t%% @todo Use non-empty SETTINGS here. Just because.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Send the SETTINGS ack.\n\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t%% Receive the SETTINGS ack, and the response HEADERS and DATA (Stream ID 1).\n\tReceived = lists:reverse(lists:foldl(fun(_, Acc) ->\n\t\tcase gen_tcp:recv(Socket, 9, 1000) of\n\t\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} ->\n\t\t\t\t[settings_ack|Acc];\n\t\t\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[headers|Acc];\n\t\t\t{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[data|Acc]\n\t\tend\n\tend, [], [1, 2, 3])),\n\tcase Received of\n\t\t[settings_ack, headers, data] -> ok;\n\t\t[headers, settings_ack, data] -> ok;\n\t\t[headers, data, settings_ack] -> ok\n\tend.\n\nhttp_upgrade_response_half_closed(Config) ->\n\tdoc(\"The stream for the initial HTTP/1.1 request is half-closed. (RFC7540 3.2)\"),\n\t%% Try sending more data after the upgrade and get an error.\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET /long_polling HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Send a valid preface followed by an unexpected DATA frame.\n\tok = gen_tcp:send(Socket, [\n\t\t\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\",\n\t\tcow_http2:settings(#{}),\n\t\tcow_http2:data(1, fin, <<\"Unexpected DATA frame.\">>)\n\t]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Skip the SETTINGS ack. Receive an RST_STREAM possibly following by\n\t%% a HEADERS frame, or a GOAWAY following HEADERS and DATA. This\n\t%% corresponds to the stream being in half-closed and closed states.\n\t%% The reason must be STREAM_CLOSED.\n\tReceived = lists:reverse(lists:foldl(fun(_, Acc) ->\n\t\tcase gen_tcp:recv(Socket, 9, 1000) of\n\t\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} ->\n\t\t\t\tAcc;\n\t\t\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[headers|Acc];\n\t\t\t{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[data|Acc];\n\t\t\t{ok, << 4:24, 3:8, 0:8, 1:32 >>} ->\n\t\t\t\t%% We expect a STREAM_CLOSED reason.\n\t\t\t\t{ok, << 5:32 >>} = gen_tcp:recv(Socket, 4, 1000),\n\t\t\t\t[rst_stream|Acc];\n\t\t\t{ok, << 8:24, 7:8, 0:40 >>} ->\n\t\t\t\t%% We expect a STREAM_CLOSED reason.\n\t\t\t\t{ok, << 1:32, 5:32 >>} = gen_tcp:recv(Socket, 8, 1000),\n\t\t\t\t[goaway|Acc];\n\t\t\t{error, _} ->\n\t\t\t\t%% Can be timeouts, ignore them.\n\t\t\t\tAcc\n\t\tend\n\tend, [], [1, 2, 3, 4])),\n\tcase Received of\n\t\t[rst_stream] -> ok;\n\t\t[headers, rst_stream] -> ok;\n\t\t[headers, data, goaway] -> ok\n\tend.\n\n%% Starting HTTP/2 for \"https\" URIs.\n\nalpn_ignore_h2c(Config) ->\n\tdoc(\"An h2c ALPN protocol identifier must be ignored. (RFC7540 3.3)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2c\">>, <<\"http/1.1\">>]},\n\t\t\tbinary, {active, false}|TlsOpts]),\n\t{ok, <<\"http/1.1\">>} = ssl:negotiated_protocol(Socket),\n\tok.\n\nalpn_server_preface(Config) ->\n\tdoc(\"The first frame must be a SETTINGS frame \"\n\t\t\"for the server connection preface. (RFC7540 3.3, RFC7540 3.5, RFC7540 6.5)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\tbinary, {active, false}|TlsOpts]),\n\t{ok, <<\"h2\">>} = ssl:negotiated_protocol(Socket),\n\t%% Receive the server preface.\n\t{ok, << _:24, 4:8, 0:40 >>} = ssl:recv(Socket, 9, 1000),\n\tok.\n\nalpn_client_preface_timeout(Config) ->\n\tdoc(\"Clients negotiating HTTP/2 and not sending a preface in \"\n\t\t\"a timely manner must be disconnected.\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\tbinary, {active, false}|TlsOpts]),\n\t{ok, <<\"h2\">>} = ssl:negotiated_protocol(Socket),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),\n\t%% Do not send the preface. Wait for the server to disconnect us.\n\t{error, closed} = ssl:recv(Socket, 3, 6000),\n\tok.\n\nalpn_reject_missing_client_preface(Config) ->\n\tdoc(\"Servers must treat an invalid connection preface as a \"\n\t\t\"connection error of type PROTOCOL_ERROR. (RFC7540 3.3, RFC7540 3.5)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\tbinary, {active, false}|TlsOpts]),\n\t{ok, <<\"h2\">>} = ssl:negotiated_protocol(Socket),\n\t%% Send a SETTINGS frame directly instead of the proper preface.\n\tok = ssl:send(Socket, cow_http2:settings(#{})),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t{error, closed} = ssl:recv(Socket, 3, 1000),\n\tok.\n\nalpn_reject_invalid_client_preface(Config) ->\n\tdoc(\"Servers must treat an invalid connection preface as a \"\n\t\t\"connection error of type PROTOCOL_ERROR. (RFC7540 3.3, RFC7540 3.5)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\tbinary, {active, false}|TlsOpts]),\n\t{ok, <<\"h2\">>} = ssl:negotiated_protocol(Socket),\n\t%% Send a slightly incorrect preface.\n\tok = ssl:send(Socket, \"PRI * HTTP/2.0\\r\\n\\r\\nSM: Value\\r\\n\\r\\n\"),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t{error, closed} = ssl:recv(Socket, 3, 1000),\n\tok.\n\nalpn_reject_missing_client_preface_settings(Config) ->\n\tdoc(\"Servers must treat an invalid connection preface as a \"\n\t\t\"connection error of type PROTOCOL_ERROR. (RFC7540 3.3, RFC7540 3.5)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\tbinary, {active, false}|TlsOpts]),\n\t{ok, <<\"h2\">>} = ssl:negotiated_protocol(Socket),\n\t%% Send a valid preface sequence except followed by a PING instead of a SETTINGS frame.\n\tok = ssl:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:ping(0)]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t{error, closed} = ssl:recv(Socket, 3, 1000),\n\tok.\n\nalpn_reject_invalid_client_preface_settings(Config) ->\n\tdoc(\"Servers must treat an invalid connection preface as a \"\n\t\t\"connection error of type PROTOCOL_ERROR. (RFC7540 3.3, RFC7540 3.5)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\tbinary, {active, false}|TlsOpts]),\n\t{ok, <<\"h2\">>} = ssl:negotiated_protocol(Socket),\n\t%% Send a valid preface sequence except followed by a badly formed SETTINGS frame.\n\tok = ssl:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", << 0:24, 4:8, 0:9, 1:31 >>]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t{error, closed} = ssl:recv(Socket, 3, 1000),\n\tok.\n\nalpn_accept_client_preface_empty_settings(Config) ->\n\tdoc(\"The SETTINGS frame in the client preface may be empty. (RFC7540 3.3, RFC7540 3.5)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\tbinary, {active, false}|TlsOpts]),\n\t{ok, <<\"h2\">>} = ssl:negotiated_protocol(Socket),\n\t%% Send a valid preface sequence except followed by an empty SETTINGS frame.\n\tok = ssl:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = ssl:recv(Socket, 9, 1000),\n\tok.\n\nalpn_client_preface_settings_ack_timeout(Config) ->\n\tdoc(\"Failure to acknowledge the server's SETTINGS frame \"\n\t\t\"results in a SETTINGS_TIMEOUT connection error. (RFC7540 3.5, RFC7540 6.5.3)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\tbinary, {active, false}|TlsOpts]),\n\t{ok, <<\"h2\">>} = ssl:negotiated_protocol(Socket),\n\t%% Send a valid preface.\n\tok = ssl:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = ssl:recv(Socket, 9, 1000),\n\t%% Do not ack the server preface. Expect a GOAWAY with reason SETTINGS_TIMEOUT.\n\t{ok, << _:24, 7:8, _:72, 4:32 >>} = ssl:recv(Socket, 17, 6000),\n\tok.\n\nalpn(Config) ->\n\tdoc(\"Successful ALPN negotiation. (RFC7540 3.3)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\tbinary, {active, false}|TlsOpts]),\n\t{ok, <<\"h2\">>} = ssl:negotiated_protocol(Socket),\n\t%% Send a valid preface.\n\t%% @todo Use non-empty SETTINGS here. Just because.\n\tok = ssl:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),\n\t%% Send the SETTINGS ack.\n\tok = ssl:send(Socket, cow_http2:settings_ack()),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = ssl:recv(Socket, 9, 1000),\n\t%% Wait until after the SETTINGS ack timeout was supposed to trigger.\n\treceive after 6000 -> ok end,\n\t%% Send a PING.\n\tok = ssl:send(Socket, cow_http2:ping(0)),\n\t%% Receive a PING ack back, indicating the connection is still up.\n\t{ok, << 8:24, 6:8, 0:7, 1:1, 0:96 >>} = ssl:recv(Socket, 17, 1000),\n\tok.\n\n%% Starting HTTP/2 with prior knowledge.\n\nprior_knowledge_reject_tls(Config) ->\n\tdoc(\"Implementations that support HTTP/2 over TLS must use ALPN. (RFC7540 3.4)\"),\n\tTlsOpts = ct_helper:get_certs_from_ets(),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}|TlsOpts]),\n\t%% Send a valid preface.\n\tok = ssl:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% We expect the server to send an HTTP 400 error\n\t%% when trying to use HTTP/2 without going through ALPN negotiation.\n\t{ok, <<\"HTTP/1.1 400\">>} = ssl:recv(Socket, 12, 1000),\n\tok.\n\nprior_knowledge_server_preface(Config) ->\n\tdoc(\"The first frame must be a SETTINGS frame \"\n\t\t\"for the server connection preface. (RFC7540 3.4, RFC7540 3.5, RFC7540 6.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a valid preface.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << _:24, 4:8, 0:40 >>} = gen_tcp:recv(Socket, 9, 1000),\n\tok.\n\n%% Note: the client preface timeout doesn't apply in this case,\n%% so we don't test it. An HTTP/1.1 client that does not send\n%% a request in a timely manner will get disconnected by the\n%% HTTP protocol code, not by HTTP/2's.\n\n%% Note: the test that starts by sending a SETTINGS frame is\n%% redundant with tests sending garbage on the connection.\n%% From the point of view of an HTTP/1.1 connection, a\n%% SETTINGS frame is indistinguishable from garbage.\n\nprior_knowledge_reject_invalid_client_preface(Config) ->\n\tdoc(\"An incorrect preface is an invalid HTTP/1.1 request. (RFC7540 3.4)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a slightly incorrect preface.\n\tok = gen_tcp:send(Socket, \"PRI * HTTP/2.0\\r\\n\\r\\nSM: Value\\r\\n\\r\\n\"),\n\t%% We propagate to HTTP/2 after checking only the request-line.\n\t%% The server then sends its preface before checking the full client preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t{error, closed} = gen_tcp:recv(Socket, 9, 1000),\n\tok.\n\nprior_knowledge_reject_missing_client_preface_settings(Config) ->\n\tdoc(\"Servers must treat an invalid connection preface as a \"\n\t\t\"connection error of type PROTOCOL_ERROR. (RFC7540 3.4, RFC7540 3.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a valid preface sequence except followed by a PING instead of a SETTINGS frame.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:ping(0)]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t{error, closed} = gen_tcp:recv(Socket, 9, 1000),\n\tok.\n\nprior_knowledge_reject_invalid_client_preface_settings(Config) ->\n\tdoc(\"Servers must treat an invalid connection preface as a \"\n\t\t\"connection error of type PROTOCOL_ERROR. (RFC7540 3.4, RFC7540 3.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a valid preface sequence except followed by a badly formed SETTINGS frame.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", << 0:24, 4:8, 0:9, 1:31 >>]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% We expect the server to close the connection when it receives a bad preface.\n\t{error, closed} = gen_tcp:recv(Socket, 9, 1000),\n\tok.\n\nprior_knowledge_accept_client_preface_empty_settings(Config) ->\n\tdoc(\"The SETTINGS frame in the client preface may be empty. (RFC7540 3.4, RFC7540 3.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a valid preface sequence except followed by an empty SETTINGS frame.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\tok.\n\nprior_knowledge_client_preface_settings_ack_timeout(Config) ->\n\tdoc(\"The SETTINGS frames sent by the client must be acknowledged. (RFC7540 3.5, RFC7540 6.5.3)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a valid preface.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Do not ack the server preface. Expect a GOAWAY with reason SETTINGS_TIMEOUT.\n\t{ok, << _:24, 7:8, _:72, 4:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% Do a prior knowledge handshake.\ndo_handshake(Config) ->\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a valid preface.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Send the SETTINGS ack.\n\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, Socket}.\n\nprior_knowledge(Config) ->\n\tdoc(\"Streams can be initiated after a successful HTTP/2 connection \"\n\t\t\"with prior knowledge of server capabilities. (RFC7540 3.4)\"),\n\t%% @todo Use non-empty SETTINGS here. Just because.\n\t{ok, Socket} = do_handshake(Config),\n\t%% Wait until after the SETTINGS ack timeout was supposed to trigger.\n\treceive after 6000 -> ok end,\n\t%% Send a PING.\n\tok = gen_tcp:send(Socket, cow_http2:ping(0)),\n\t%% Receive a PING ack back, indicating the connection is still up.\n\t{ok, << 8:24, 6:8, 0:7, 1:1, 0:96 >>} = gen_tcp:recv(Socket, 17, 1000),\n\tok.\n\n%% @todo If we ever add an option to disable HTTP/2, we need to check\n%% the following things:\n%% * HTTP/1.1 Upgrade returns an HTTP/1.1 response (3.2)\n%% * HTTP/1.1 Upgrade errors out if the client sends HTTP/2 frames\n%%   without waiting for the 101 response (3.2, 3.5)\n%% * Prior knowledge handshake fails (3.4)\n%% * ALPN selects HTTP/1.1 (3.3)\n\n%% Frame format.\n\nignore_unknown_frames(Config) ->\n\tdoc(\"Frames of unknown type must be ignored and discarded. (RFC7540 4.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with a single DATA frame,\n\t%% and an unknown frame type interleaved.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t<< 10:24, 99:8, 0:40, 0:80 >>,\n\t\tcow_http2:data(1, fin, << 0:100/unit:8 >>)\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\nignore_data_unknown_flags(Config) ->\n\tdoc(\"Undefined DATA frame flags must be ignored. (RFC7540 4.1, RFC7540 6.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with a DATA frame with unknown flags.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t<< 100:24, 0:8,\n\t\t\t1:1, 1:1, 1:1, 1:1, %% Undefined.\n\t\t\t0:1, %% PADDED.\n\t\t\t1:1, 1:1, %% Undefined.\n\t\t\t1:1, %% END_STREAM.\n\t\t\t0:1, 1:31, 0:100/unit:8 >>\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\nignore_headers_unknown_flags(Config) ->\n\tdoc(\"Undefined HEADERS frame flags must be ignored. (RFC7540 4.1, RFC7540 6.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with a HEADERS frame with unknown flags.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tLen = iolist_size(HeadersBlock),\n\tok = gen_tcp:send(Socket, [\n\t\t<< Len:24, 1:8,\n\t\t\t1:1, 1:1, %% Undefined.\n\t\t\t0:1, %% PRIORITY.\n\t\t\t1:1, %% Undefined.\n\t\t\t0:1, %% PADDED.\n\t\t\t1:1, %% END_HEADERS.\n\t\t\t1:1, %% Undefined.\n\t\t\t0:1, %% END_STREAM.\n\t\t\t0:1, 1:31 >>,\n\t\tHeadersBlock,\n\t\tcow_http2:data(1, fin, << 0:100/unit:8 >>)\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\nignore_priority_unknown_flags(Config) ->\n\tdoc(\"Undefined PRIORITY frame flags must be ignored. (RFC7540 4.1, RFC7540 6.3)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with an interleaved PRIORITY frame with unknown flags.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t<< 5:24, 2:8,\n\t\t\t1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.\n\t\t\t0:1, 1:31, 0:1, 3:31, 0:8 >>,\n\t\tcow_http2:data(1, fin, << 0:100/unit:8 >>)\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\nignore_rst_stream_unknown_flags(Config) ->\n\tdoc(\"Undefined RST_STREAM frame flags must be ignored. (RFC7540 4.1, RFC7540 6.4)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request then cancel it with an RST_STREAM frame with unknown flags.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t<< 4:24, 3:8,\n\t\t\t1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.\n\t\t\t0:1, 1:31, 8:32 >>,\n\t\tcow_http2:headers(3, nofin, HeadersBlock),\n\t\tcow_http2:data(3, fin, << 0:100/unit:8 >>)\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 3:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 3:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\nignore_settings_unknown_flags(Config) ->\n\tdoc(\"Undefined SETTINGS frame flags must be ignored. (RFC7540 4.1, RFC7540 6.5)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a SETTINGS frame with unknown flags.\n\tok = gen_tcp:send(Socket, << 6:24, 4:8,\n\t\t1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.\n\t\t0:1, %% ACK.\n\t\t0:32, 2:16, 0:32 >>),\n\t%% Receive a SETTINGS ack.\n\t{ok, << 0:24, 4:8, 0:7, 1:1, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\tok.\n\nignore_push_promise_unknown_flags(Config) ->\n\tdoc(\"Undefined PUSH_PROMISE frame flags must be ignored. (RFC7540 4.1, RFC7540 6.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PUSH_PROMISE frame with unknown flags.\n\tok = gen_tcp:send(Socket, << 4:24, 5:8,\n\t\t1:1, 1:1, 1:1, 1:1, %% Undefined.\n\t\t0:1, %% PADDED.\n\t\t1:1, %% END_HEADERS.\n\t\t1:1, 1:1, %% Undefined.\n\t\t0:1, 1:31, 0:1, 3:31 >>\n\t),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t%%\n\t%% Note that it is not possible to distinguish between the expected\n\t%% result and the server rejecting PUSH_PROMISE frames.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nignore_ping_unknown_flags(Config) ->\n\tdoc(\"Undefined PING frame flags must be ignored. (RFC7540 4.1, RFC7540 6.7)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PING frame with unknown flags.\n\tok = gen_tcp:send(Socket, << 8:24, 6:8,\n\t\t1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.\n\t\t0:1, %% ACK.\n\t\t0:32, 0:64 >>),\n\t%% Receive a PING ACK in return.\n\t{ok, << 8:24, 6:8, _:7, 1:1, _:32, 0:64 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nignore_goaway_unknown_flags(Config) ->\n\tdoc(\"Undefined GOAWAY frame flags must be ignored. (RFC7540 4.1, RFC7540 6.8)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a GOAWAY frame with unknown flags.\n\tok = gen_tcp:send(Socket, << 8:24, 7:8,\n\t\t1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.\n\t\t0:32, 0:64 >>),\n\t%% Receive a GOAWAY frame back.\n\t{ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nignore_window_update_unknown_flags(Config) ->\n\tdoc(\"Undefined WINDOW_UPDATE frame flags must be ignored. (RFC7540 4.1, RFC7540 6.9)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a WINDOW_UPDATE frame with unknown flags.\n\tok = gen_tcp:send(Socket, << 4:24, 8:8,\n\t\t1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.\n\t\t0:32, 1000:32 >>),\n\t%% We expect no errors or replies, therefore we send a PING frame.\n\tok = gen_tcp:send(Socket, cow_http2:ping(0)),\n\t%% And receive a PING ACK in return.\n\t{ok, << 8:24, 6:8, _:7, 1:1, _:32, 0:64 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nignore_continuation_unknown_flags(Config) ->\n\tdoc(\"Undefined CONTINUATION frame flags must be ignored. (RFC7540 4.1, RFC7540 6.10)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with a CONTINUATION frame with unknown flags.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tLen = iolist_size(HeadersBlock),\n\tok = gen_tcp:send(Socket, [\n\t\t<< 0:24, 1:8, 0:8, 0:1, 1:31 >>,\n\t\t<< Len:24, 9:8,\n\t\t\t1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.\n\t\t\t1:1, %% END_HEADERS.\n\t\t\t1:1, 1:1, %% Undefined.\n\t\t\t0:1, 1:31 >>,\n\t\tHeadersBlock,\n\t\tcow_http2:data(1, fin, << 0:100/unit:8 >>)\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\n%% @todo Flags that have no defined semantics for\n%% a particular frame type MUST be left unset (0x0) when sending. (RFC7540 4.1)\n\nignore_data_reserved_bit(Config) ->\n\tdoc(\"Reserved 1-bit field of DATA frame must be ignored. (RFC7540 4.1, RFC7540 6.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with a DATA frame with the reserved bit set.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t<< 100:24, 0:8, 0:7, 1:1,\n\t\t\t1:1, %% Reserved bit.\n\t\t\t1:31, 0:100/unit:8 >>\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\nignore_headers_reserved_bit(Config) ->\n\tdoc(\"Reserved 1-bit field of HEADERS frame must be ignored. (RFC7540 4.1, RFC7540 6.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with a HEADERS frame with the reserved bit set.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tLen = iolist_size(HeadersBlock),\n\tok = gen_tcp:send(Socket, [\n\t\t<< Len:24, 1:8, 0:5, 1:1, 0:2,\n\t\t\t1:1, %% Reserved bit.\n\t\t\t1:31 >>,\n\t\tHeadersBlock,\n\t\tcow_http2:data(1, fin, << 0:100/unit:8 >>)\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\nignore_priority_reserved_bit(Config) ->\n\tdoc(\"Reserved 1-bit field of PRIORITY frame must be ignored. (RFC7540 4.1, RFC7540 6.3)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with an interleaved PRIORITY frame with the reserved bit set.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t<< 5:24, 2:8, 0:8,\n\t\t\t1:1, %% Reserved bit.\n\t\t\t1:31, 0:1, 3:31, 0:8 >>,\n\t\tcow_http2:data(1, fin, << 0:100/unit:8 >>)\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\nignore_rst_stream_reserved_bit(Config) ->\n\tdoc(\"Reserved 1-bit field of RST_STREAM frame must be ignored. (RFC7540 4.1, RFC7540 6.4)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request then cancel it with an RST_STREAM frame with the reserved bit set.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t<< 4:24, 3:8, 0:8,\n\t\t\t1:1, %% Reserved bit.\n\t\t\t1:31, 8:32 >>,\n\t\tcow_http2:headers(3, nofin, HeadersBlock),\n\t\tcow_http2:data(3, fin, << 0:100/unit:8 >>)\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 3:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 3:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\nignore_settings_reserved_bit(Config) ->\n\tdoc(\"Reserved 1-bit field of SETTINGS frame must be ignored. (RFC7540 4.1, RFC7540 6.5)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a SETTINGS frame with the reserved bit set.\n\tok = gen_tcp:send(Socket, << 6:24, 4:8, 0:8,\n\t\t1:1, %% Reserved bit.\n\t\t0:31, 2:16, 0:32 >>),\n\t%% Receive a SETTINGS ack.\n\t{ok, << 0:24, 4:8, 0:7, 1:1, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\tok.\n\nignore_push_promise_reserved_bit(Config) ->\n\tdoc(\"Reserved 1-bit field of PUSH_PROMISE frame must be ignored. (RFC7540 4.1, RFC7540 6.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PUSH_PROMISE frame with the reserved bit set.\n\tok = gen_tcp:send(Socket, << 4:24, 5:8, 0:5, 1:1, 0:2,\n\t\t1:1, %% Reserved bit.\n\t\t1:31, 0:1, 3:31 >>\n\t),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t%%\n\t%% Note that it is not possible to distinguish between the expected\n\t%% result and the server rejecting PUSH_PROMISE frames.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nignore_ping_reserved_bit(Config) ->\n\tdoc(\"Reserved 1-bit field of PING frame must be ignored. (RFC7540 4.1, RFC7540 6.7)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PING frame with the reserved bit set.\n\tok = gen_tcp:send(Socket, << 8:24, 6:8, 0:8,\n\t\t1:1, %% Reserved bit.\n\t\t0:31, 0:64 >>),\n\t%% Receive a PING ACK in return.\n\t{ok, << 8:24, 6:8, _:7, 1:1, _:32, 0:64 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nignore_goaway_reserved_bit(Config) ->\n\tdoc(\"Reserved 1-bit field of GOAWAY frame must be ignored. (RFC7540 4.1, RFC7540 6.8)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a GOAWAY frame with the reserved bit set.\n\tok = gen_tcp:send(Socket, << 8:24, 7:8, 0:8,\n\t\t1:1, %% Reserved bit.\n\t\t0:31, 0:64 >>),\n\t%% Receive a GOAWAY frame back.\n\t{ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nignore_window_update_reserved_bit(Config) ->\n\tdoc(\"Reserved 1-bit field of WINDOW_UPDATE frame must be ignored. (RFC7540 4.1, RFC7540 6.9)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a WINDOW_UPDATE frame with the reserved bit set.\n\tok = gen_tcp:send(Socket, << 4:24, 8:8, 0:8,\n\t\t1:1, %% Reserved bit.\n\t\t0:31, 1000:32 >>),\n\t%% We expect no errors or replies, therefore we send a PING frame.\n\tok = gen_tcp:send(Socket, cow_http2:ping(0)),\n\t%% And receive a PING ACK in return.\n\t{ok, << 8:24, 6:8, _:7, 1:1, _:32, 0:64 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nignore_continuation_reserved_bit(Config) ->\n\tdoc(\"Reserved 1-bit field of CONTINUATION frame must be ignored. (RFC7540 4.1, RFC7540 6.10)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with a CONTINUATION frame with the reserved bit set.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tLen = iolist_size(HeadersBlock),\n\tok = gen_tcp:send(Socket, [\n\t\t<< 0:24, 1:8, 0:8, 0:1, 1:31 >>,\n\t\t<< Len:24, 9:8, 0:5, 1:1, 0:2,\n\t\t\t1:1, %% Reserved bit.\n\t\t\t1:31 >>,\n\t\tHeadersBlock,\n\t\tcow_http2:data(1, fin, << 0:100/unit:8 >>)\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),\n\tok.\n\n%% @todo The reserved 1-bit field MUST remain unset (0x0) when sending. (RFC7540 4.1)\n\n%% Frame size.\n\nmax_frame_size_allow_exactly_default(Config) ->\n\tdoc(\"All implementations must allow frame sizes of at least 16384. (RFC7540 4.1, RFC7540 4.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with a DATA frame of exactly 16384 bytes.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\tcow_http2:data(1, fin, << 0:16384/unit:8 >>)\n\t]),\n\t%% Receive a response with the same DATA frame.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = case gen_tcp:recv(Socket, 9, 1000) of\n\t\t%% We received a WINDOW_UPDATE first. Skip it and the next.\n\t\t{ok, <<4:24, 8:8, 0:40>>} ->\n\t\t\t{ok, _} = gen_tcp:recv(Socket, 4 + 13, 1000),\n\t\t\tgen_tcp:recv(Socket, 9, 1000);\n\t\tRes ->\n\t\t\tRes\n\tend,\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 16384:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, << 0:16384/unit:8 >>} = gen_tcp:recv(Socket, 16384, 1000),\n\tok.\n\nmax_frame_size_reject_larger_than_default(Config) ->\n\tdoc(\"A FRAME_SIZE_ERROR connection error must be sent when receiving \"\n\t\t\"frames larger than the default 16384 length. (RFC7540 4.1, RFC7540 4.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with a DATA frame larger than 16384 bytes.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\tcow_http2:data(1, fin, << 0:16385/unit:8 >>)\n\t]),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nmax_frame_size_allow_exactly_custom(Config0) ->\n\tdoc(\"An endpoint that sets SETTINGS_MAX_FRAME_SIZE must allow frames \"\n\t\t\"of up to that size. (RFC7540 4.2, RFC7540 6.5.2)\"),\n\t%% Create a new listener that sets the maximum frame size to 30000.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tmax_frame_size_received => 30000\n\t}, Config0),\n\ttry\n\t\t%% Do the handshake.\n\t\t{ok, Socket} = do_handshake(Config),\n\t\t%% Send a HEADERS frame initiating a stream followed by\n\t\t%% a single 30000 bytes DATA frame.\n\t\tHeaders = [\n\t\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t\t],\n\t\t{HeadersBlock, _} = cow_hpack:encode(Headers),\n\t\tok = gen_tcp:send(Socket, [\n\t\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t\tcow_http2:data(1, fin, <<0:30000/unit:8>>)\n\t\t]),\n\t\t%% Receive a proper response.\n\t\t{ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t\t{ok, _} = gen_tcp:recv(Socket, Len2, 6000),\n\t\t%% No errors follow due to our sending of a 25000 bytes frame.\n\t\t{error, timeout} = gen_tcp:recv(Socket, 0, 1000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nmax_frame_size_reject_larger_than_custom(Config0) ->\n\tdoc(\"An endpoint that sets SETTINGS_MAX_FRAME_SIZE must reject frames \"\n\t\t\"of up to that size with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.5.2)\"),\n\t%% Create a new listener that sets the maximum frame size to 30000.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tmax_frame_size_received => 30000\n\t}, Config0),\n\ttry\n\t\t%% Do the handshake.\n\t\t{ok, Socket} = do_handshake(Config),\n\t\t%% Send a HEADERS frame initiating a stream followed by\n\t\t%% a single DATA frame larger than 30000 bytes.\n\t\tHeaders = [\n\t\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t\t],\n\t\t{HeadersBlock, _} = cow_hpack:encode(Headers),\n\t\tok = gen_tcp:send(Socket, [\n\t\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t\tcow_http2:data(1, fin, <<0:30001/unit:8>>)\n\t\t]),\n\t\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\n%% I am using FRAME_SIZE_ERROR here because the information in the\n%% frame header tells us this frame is at least 1 byte long, while\n%% the given length is smaller; i.e. it is too small to contain\n%% mandatory frame data (the pad length).\n\ndata_reject_frame_size_0_padded_flag(Config) ->\n\tdoc(\"DATA frames of size 0 with the PADDED flag set must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with an incorrect padded DATA frame size.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t<< 0:24, 0:8, 0:4, 1:1, 0:2, 1:1, 0:1, 1:31 >>\n\t]),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% This case on the other hand is noted specifically in the RFC\n%% as being a PROTOCOL_ERROR. It can be thought of as the Pad Length\n%% being incorrect, rather than the frame size.\n\ndata_reject_frame_size_too_small_padded_flag(Config) ->\n\tdoc(\"DATA frames with Pad Length >= Length must be rejected \"\n\t\t\"with a PROTOCOL_ERROR connection error. (RFC7540 6.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a POST request with an incorrect padded DATA frame size.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t<< 10:24, 0:8, 0:4, 1:1, 0:2, 1:1, 0:1, 1:31, 10:8, 0:80  >>\n\t]),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nheaders_reject_frame_size_0_padded_flag(Config) ->\n\tdoc(\"HEADERS frames of size 0 with the PADDED flag set must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a padded HEADERS frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 0:24, 1:8, 0:4, 1:1, 0:2, 1:1, 0:1, 1:31 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nheaders_reject_frame_size_too_small_padded_flag(Config) ->\n\tdoc(\"HEADERS frames with no priority flag and Pad Length >= Length \"\n\t\t\"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 6.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a padded HEADERS frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 10:24, 1:8, 0:4, 1:1, 0:2, 1:1, 0:1, 1:31, 10:8, 0:80 >>),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nheaders_reject_frame_size_too_small_priority_flag(Config) ->\n\tdoc(\"HEADERS frames of size smaller than 5 with the PRIORITY flag set must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with priority set and an incorrect size.\n\tok = gen_tcp:send(Socket, << 4:24, 1:8,\n\t\t0:2, 1:1, 0:4, 1:1, 0:1, 1:31, 0:1, 3:31, 0:8 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nheaders_reject_frame_size_5_padded_and_priority_flags(Config) ->\n\tdoc(\"HEADERS frames of size smaller than 6 with the PADDED \"\n\t\t\"and PRIORITY flags set must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a padded HEADERS frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 5:24, 1:8,\n\t\t0:2, 1:1, 0:1, 1:1, 0:2, 1:1, 0:1, 1:31, 0:8, 0:1, 3:31, 0:8 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nheaders_reject_frame_size_too_small_padded_and_priority_flags(Config) ->\n\tdoc(\"HEADERS frames of size smaller than Length+6 with the PADDED and PRIORITY flags set \"\n\t\t\"must be rejected with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a padded HEADERS frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 15:24, 1:8,\n\t\t0:2, 1:1, 0:1, 1:1, 0:2, 1:1, 0:1, 1:31, 10:8, 0:1, 3:31, 0:8, 0:80 >>),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\npriority_reject_frame_size_too_small(Config) ->\n\tdoc(\"PRIORITY frames of size smaller than 5 must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.3)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PRIORITY frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 4:24, 2:8, 0:9, 1:31, 0:1, 3:31, 0:8 >>),\n\t%% Receive a FRAME_SIZE_ERROR stream error.\n\t{ok, << _:24, 3:8, _:40, 6:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\npriority_reject_frame_size_too_large(Config) ->\n\tdoc(\"PRIORITY frames of size larger than 5 must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.3)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PRIORITY frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 6:24, 2:8, 0:9, 1:31, 0:1, 3:31, 0:16 >>),\n\t%% Receive a FRAME_SIZE_ERROR stream error.\n\t{ok, << _:24, 3:8, _:40, 6:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nrst_stream_reject_frame_size_too_small(Config) ->\n\tdoc(\"RST_STREAM frames of size smaller than 4 must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.4)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a request and reset it immediately.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, fin, HeadersBlock),\n\t\t<< 3:24, 3:8, 0:9, 1:31, 8:32 >>\n\t]),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nrst_stream_reject_frame_size_too_large(Config) ->\n\tdoc(\"RST_STREAM frames of size larger than 4 must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.4)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a request and reset it immediately.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, fin, HeadersBlock),\n\t\t<< 5:24, 3:8, 0:9, 1:31, 8:32 >>\n\t]),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nsettings_reject_bad_frame_size(Config) ->\n\tdoc(\"SETTINGS frames must have a size multiple of 6 or be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.5)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a SETTINGS frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 5:24, 4:8, 0:40, 1:16, 4096:32 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nsettings_ack_reject_non_empty_frame_size(Config) ->\n\tdoc(\"SETTINGS frames with the ACK flag set and a non-empty payload \"\n\t\t\"must be rejected with a FRAME_SIZE_ERROR connection error (RFC7540 4.2, RFC7540 6.5)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a valid preface.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Send a SETTINGS ack with a payload.\n\tok = gen_tcp:send(Socket, << 6:24, 4:8, 0:7, 1:1, 0:32, 1:16, 4096:32 >>),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% Note that clients are not supposed to send PUSH_PROMISE frames.\n%% However when they do, we need to be able to parse it in order\n%% to reject it, and so these errors may still occur.\n\npush_promise_reject_frame_size_too_small(Config) ->\n\tdoc(\"PUSH_PROMISE frames of size smaller than 4 must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PUSH_PROMISE frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 3:24, 5:8, 0:5, 1:1, 0:3, 1:31, 0:1, 3:31 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\npush_promise_reject_frame_size_4_padded_flag(Config) ->\n\tdoc(\"PUSH_PROMISE frames of size smaller than 5 with the PADDED flag set must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PUSH_PROMISE frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 4:24, 5:8, 0:4, 1:1, 1:1, 0:3, 1:31, 0:1, 0:8, 3:31 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\npush_promise_reject_frame_size_too_small_padded_flag(Config) ->\n\tdoc(\"PUSH_PROMISE frames of size smaller than Length+5 with the PADDED flag set \"\n\t\t\"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 4.2, RFC7540 6.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PUSH_PROMISE frame with an incorrect size.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tLen = 14 + iolist_size(HeadersBlock),\n\tok = gen_tcp:send(Socket, [\n\t\t<< Len:24, 5:8, 0:4, 1:1, 1:1, 0:3, 1:31, 10:8, 0:1, 3:31 >>,\n\t\tHeadersBlock,\n\t\t<< 0:80 >>\n\t]),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t%%\n\t%% Note that it is not possible to distinguish between a Pad Length\n\t%% error and the server rejecting PUSH_PROMISE frames.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nping_reject_frame_size_too_small(Config) ->\n\tdoc(\"PING frames of size smaller than 8 must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.7)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PING frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 7:24, 6:8, 0:40, 0:56 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nping_reject_frame_size_too_large(Config) ->\n\tdoc(\"PING frames of size larger than 8 must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.7)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PING frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 9:24, 6:8, 0:40, 0:72 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\ngoaway_reject_frame_size_too_small(Config) ->\n\tdoc(\"GOAWAY frames of size smaller than 8 must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.8)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a GOAWAY frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 7:24, 7:8, 0:40, 0:56 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\ngoaway_allow_frame_size_too_large(Config) ->\n\tdoc(\"GOAWAY frames of size larger than 8 must be allowed. (RFC7540 6.8)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a GOAWAY frame with debug data.\n\tok = gen_tcp:send(Socket, << 12:24, 7:8, 0:40, 0:64, 99999:32 >>),\n\t%% Receive a GOAWAY frame back.\n\t{ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nwindow_update_reject_frame_size_too_small(Config) ->\n\tdoc(\"WINDOW_UPDATE frames of size smaller than 4 must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.9)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a WINDOW_UPDATE frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 3:24, 8:8, 0:40, 1000:24 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nwindow_update_reject_frame_size_too_large(Config) ->\n\tdoc(\"WINDOW_UPDATE frames of size larger than 4 must be rejected \"\n\t\t\"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.9)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a WINDOW_UPDATE frame with an incorrect size.\n\tok = gen_tcp:send(Socket, << 5:24, 8:8, 0:40, 1000:40 >>),\n\t%% Receive a FRAME_SIZE_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% Note: There is no particular limits on the size of CONTINUATION frames,\n%% they can go from 0 to SETTINGS_MAX_FRAME_SIZE.\n\n%% Header compression and decompression.\n\nheaders_compression_error(Config) ->\n\tdoc(\"A decoding error in a HEADERS frame's header block must be rejected \"\n\t\t\"with a COMPRESSION_ERROR connection error. (RFC7540 4.3, RFC7540 6.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with an invalid header block.\n\tok = gen_tcp:send(Socket, << 10:24, 1:8, 0:5, 1:1, 0:1, 1:1, 0:1, 1:31, 0:10/unit:8 >>),\n\t%% Receive a COMPRESSION_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 9:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\ncontinuation_compression_error(Config) ->\n\tdoc(\"A decoding error in a CONTINUATION frame's header block must be rejected \"\n\t\t\"with a COMPRESSION_ERROR connection error. (RFC7540 4.3, RFC7540 6.10)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a CONTINUATION frame with an invalid header block.\n\tok = gen_tcp:send(Socket, [\n\t\t<< 0:24, 1:8, 0:7, 1:1, 0:1, 1:31 >>,\n\t\t<< 10:24, 9:8, 0:5, 1:1, 0:3, 1:31, 0:10/unit:8 >>\n\t]),\n\t%% Receive a COMPRESSION_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 9:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\ncontinuation_with_frame_interleaved_error(Config) ->\n\tdoc(\"Frames interleaved in a header block must be rejected \"\n\t\t\"with a PROTOCOL_ERROR connection error. (RFC7540 4.3, RFC7540 6.2, RFC7540 6.10)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send an unterminated HEADERS frame followed by a PING frame.\n\tok = gen_tcp:send(Socket, [\n\t\t<< 0:24, 1:8, 0:7, 1:1, 0:1, 1:31 >>,\n\t\tcow_http2:ping(0)\n\t]),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\ncontinuation_wrong_stream_error(Config) ->\n\tdoc(\"CONTINUATION frames with an incorrect stream identifier must be rejected \"\n\t\t\"with a PROTOCOL_ERROR connection error. (RFC7540 4.3, RFC7540 6.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send an unterminated HEADERS frame followed by a CONTINUATION frame for another stream.\n\tok = gen_tcp:send(Socket, [\n\t\t<< 0:24, 1:8, 0:7, 1:1, 0:1, 1:31 >>,\n\t\t<< 0:24, 9:8, 0:9, 3:31 >>\n\t]),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% Stream states.\n\nidle_stream_reject_data(Config) ->\n\tdoc(\"DATA frames received on an idle stream must be rejected \"\n\t\t\"with a PROTOCOL_ERROR connection error. (RFC7540 5.1, RFC7540 6.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a DATA frame on an idle stream.\n\tok = gen_tcp:send(Socket, cow_http2:data(1, fin, <<\"Unexpected DATA frame.\">>)),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nidle_stream_accept_headers(Config) ->\n\tdoc(\"HEADERS frames received on an idle stream must be accepted. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame on an idle stream.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a HEADERS frame as a response.\n\t{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\nidle_stream_accept_priority(Config) ->\n\tdoc(\"PRIORITY frames received on an idle stream must be accepted. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PRIORITY frame on an idle stream.\n\tok = gen_tcp:send(Socket, cow_http2:priority(1, shared, 3, 123)),\n\t%% Receive no error.\n\t{error, timeout} = gen_tcp:recv(Socket, 7, 1000),\n\t%% Send a HEADERS frame on the same stream.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a HEADERS frame as a response.\n\t{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\nidle_stream_reject_rst_stream(Config) ->\n\tdoc(\"RST_STREAM frames received on an idle stream must be rejected \"\n\t\t\"with a PROTOCOL_ERROR connection error. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send an RST_STREAM frame on an idle stream.\n\tok = gen_tcp:send(Socket, cow_http2:rst_stream(1, no_error)),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nidle_stream_reject_push_promise(Config) ->\n\tdoc(\"PUSH_PROMISE frames received on an idle stream must be rejected \"\n\t\t\"with a PROTOCOL_ERROR connection error. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PUSH_PROMISE frame on an idle stream.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:push_promise(1, 3, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nidle_stream_reject_window_update(Config) ->\n\tdoc(\"WINDOW_UPDATE frames received on an idle stream must be rejected \"\n\t\t\"with a PROTOCOL_ERROR connection error. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a WINDOW_UPDATE frame on an idle stream.\n\tok = gen_tcp:send(Socket, cow_http2:window_update(1, 12345)),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%reserved (local) - after sending PUSH_PROMISE:\n%      An endpoint MUST NOT send any type of frame other than HEADERS,\n%      RST_STREAM, or PRIORITY in this state.\n%%% how to test this?\n%\n%      A PRIORITY or WINDOW_UPDATE frame MAY be received in this state.\n%      Receiving any type of frame other than RST_STREAM, PRIORITY, or\n%      WINDOW_UPDATE on a stream in this state MUST be treated as a\n%      connection error (Section 5.4.1) of type PROTOCOL_ERROR.\n%%% we need to use a large enough file for this\n%\n%reserved_local_reject_data\n%reserved_local_reject_headers\n%reserved_local_accept_priority\n%reserved_local_accept_rst_stream\n%reserved_local_reject_push_promise %% do we even care? we reject it always\n%reserved_local_accept_window_update\n%\n%half-closed (remote):\n%      If an endpoint receives additional frames, other than\n%      WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a stream that is in\n%      this state, it MUST respond with a stream error (Section 5.4.2) of\n%      type STREAM_CLOSED.\n\nhalf_closed_remote_reject_data(Config) ->\n\tdoc(\"DATA frames received on a half-closed (remote) stream must be rejected \"\n\t\t\"with a STREAM_CLOSED stream error. (RFC7540 5.1, RFC7540 6.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with the FIN flag set.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Send a DATA frame on that now half-closed (remote) stream.\n\tok = gen_tcp:send(Socket, cow_http2:data(1, fin, <<\"Unexpected DATA frame.\">>)),\n\t%% Receive a STREAM_CLOSED stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 5:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n%% We reject all invalid HEADERS with a connection error because\n%% we do not want to waste resources decoding them.\nhalf_closed_remote_reject_headers(Config) ->\n\tdoc(\"HEADERS frames received on a half-closed (remote) stream must be rejected \"\n\t\t\"with a STREAM_CLOSED connection error. (RFC7540 4.3, RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with the FIN flag set.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Send a HEADERS frame on that now half-closed (remote) stream.\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a STREAM_CLOSED connection error.\n\t{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nhalf_closed_remote_accept_priority(Config) ->\n\tdoc(\"PRIORITY frames received on a half-closed stream must be accepted. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with the FIN flag set.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Send a PRIORITY frame on that now half-closed (remote) stream.\n\tok = gen_tcp:send(Socket, cow_http2:priority(1, shared, 3, 123)),\n\t%% Receive a HEADERS frame as a response.\n\t{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\nhalf_closed_remote_accept_rst_stream(Config) ->\n\tdoc(\"RST_STREAM frames received on a half-closed stream must be accepted. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with the FIN flag set.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Send an RST_STREAM frame on that now half-closed (remote) stream.\n\tok = gen_tcp:send(Socket, cow_http2:rst_stream(1, no_error)),\n\t%% Receive nothing back.\n\t{error, timeout} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\n%% half_closed_remote_reject_push_promise\n%%\n%% We respond to all PUSH_PROMISE frames with a PROTOCOL_ERROR connection error\n%% because PUSH is disabled in that direction. We therefore cannot test other\n%% error conditions.\n\nhalf_closed_remote_accept_window_update(Config) ->\n\tdoc(\"WINDOW_UPDATE frames received on a half-closed stream must be accepted. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with the FIN flag set.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Send a WINDOW_UPDATE frame on that now half-closed (remote) stream.\n\tok = gen_tcp:send(Socket, cow_http2:window_update(1, 12345)),\n\t%% Receive a HEADERS frame as a response.\n\t{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\n%% We reject DATA frames sent on closed streams with a STREAM_CLOSED\n%% connection error regardless of how the stream was closed to simplify\n%% the implementation. This excludes the few frames we ignore from\n%% lingering streams that we canceled.\nrst_stream_closed_reject_data(Config) ->\n\tdoc(\"DATA frames received on a stream closed via RST_STREAM must be rejected \"\n\t\t\"with a STREAM_CLOSED connection error. (RFC7540 5.1, RFC7540 6.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),\n\t%% Send an RST_STREAM frame to close the stream.\n\tok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),\n\t%% Send a DATA frame on the now RST_STREAM closed stream.\n\tok = gen_tcp:send(Socket, cow_http2:data(1, fin, <<\"Unexpected DATA frame.\">>)),\n\t%% Receive a STREAM_CLOSED connection error.\n\t{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% We reject all invalid HEADERS with a connection error because\n%% we do not want to waste resources decoding them.\nrst_stream_closed_reject_headers(Config) ->\n\tdoc(\"HEADERS frames received on a stream closed via RST_STREAM must be rejected \"\n\t\t\"with a STREAM_CLOSED connection error. (RFC7540 4.3, RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),\n\t%% Send an RST_STREAM frame to close the stream.\n\tok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),\n\t%% Send a HEADERS frame on the now RST_STREAM closed stream.\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),\n\t%% Receive a STREAM_CLOSED connection error.\n\t{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nrst_stream_closed_accept_priority(Config) ->\n\tdoc(\"PRIORITY frames received on a stream closed via RST_STREAM \"\n\t\t\"must be accepted. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),\n\t%% Send an RST_STREAM frame to close the stream.\n\tok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),\n\t%% Send a PRIORITY frame on that now RST_STREAM closed stream.\n\tok = gen_tcp:send(Socket, cow_http2:priority(1, shared, 3, 123)),\n\t%% Receive nothing back.\n\t{error, timeout} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\nrst_stream_closed_ignore_rst_stream(Config) ->\n\tdoc(\"RST_STREAM frames received on a stream closed via RST_STREAM \"\n\t\t\"must be ignored to avoid looping. (RFC7540 5.1, RFC7540 5.4.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),\n\t%% Send an RST_STREAM frame to close the stream.\n\tok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),\n\t%% Send an extra RST_STREAM.\n\tok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),\n\t%% Receive nothing back.\n\t{error, timeout} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\n%% rst_stream_closed_reject_push_promise\n%%\n%% We respond to all PUSH_PROMISE frames with a PROTOCOL_ERROR connection error\n%% because PUSH is disabled in that direction. We therefore cannot test other\n%% error conditions.\n\nrst_stream_closed_reject_window_update(Config) ->\n\tdoc(\"WINDOW_UPDATE frames received on a stream closed via RST_STREAM \"\n\t\t\"must be rejected with a STREAM_CLOSED stream error. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),\n\t%% Send an RST_STREAM frame to close the stream.\n\tok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),\n\t%% Send a WINDOW_UPDATE frame on the now RST_STREAM closed stream.\n\tok = gen_tcp:send(Socket, cow_http2:window_update(1, 12345)),\n\t%% Receive a STREAM_CLOSED stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 5:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nstream_closed_reject_data(Config) ->\n\tdoc(\"DATA frames received on a stream closed normally must be rejected \"\n\t\t\"with a STREAM_CLOSED connection error. (RFC7540 5.1, RFC7540 6.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive the response.\n\t{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length1, 6000),\n\t{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length2, 6000),\n\t%% Send a DATA frame on the now closed stream.\n\tok = gen_tcp:send(Socket, cow_http2:data(1, fin, <<\"Unexpected DATA frame.\">>)),\n\t%% Receive a STREAM_CLOSED connection error.\n\t{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nstream_closed_reject_headers(Config) ->\n\tdoc(\"HEADERS frames received on a stream closed normally must be rejected \"\n\t\t\"with a STREAM_CLOSED connection error. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive the response.\n\t{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length1, 6000),\n\t{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length2, 6000),\n\t%% Send a HEADERS frame on the now closed stream.\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a STREAM_CLOSED connection error.\n\t{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nstream_closed_accept_priority(Config) ->\n\tdoc(\"PRIORITY frames received on a stream closed normally must be accepted. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive the response.\n\t{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length1, 6000),\n\t{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length2, 6000),\n\t%% Send a PRIORITY frame on the now closed stream.\n\tok = gen_tcp:send(Socket, cow_http2:priority(1, shared, 3, 123)),\n\t%% Receive nothing back.\n\t{error, timeout} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\nstream_closed_accept_rst_stream(Config) ->\n\tdoc(\"RST_STREAM frames received on a stream closed normally \"\n\t\t\"must be accepted for a short period. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive the response.\n\t{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length1, 6000),\n\t{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length2, 6000),\n\t%% Send an RST_STREAM frame on the now closed stream.\n\tok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),\n\t%% Receive nothing back.\n\t{error, timeout} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\n%% stream_closed_reject_push_promise\n%%\n%% We respond to all PUSH_PROMISE frames with a PROTOCOL_ERROR connection error\n%% because PUSH is disabled in that direction. We therefore cannot test other\n%% error conditions.\n\nstream_closed_accept_window_update(Config) ->\n\tdoc(\"WINDOW_UPDATE frames received on a stream closed normally \"\n\t\t\"must be accepted for a short period. (RFC7540 5.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive the response.\n\t{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length1, 6000),\n\t{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length2, 6000),\n\t%% Send a WINDOW_UPDATE frame on the now closed stream.\n\tok = gen_tcp:send(Socket, cow_http2:window_update(1, 12345)),\n\t%% Receive nothing back.\n\t{error, timeout} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\n%% @todo While we accept RST_STREAM and WINDOW_UPDATE for a short period\n%% after the stream closed normally, we may want to reject the ones coming\n%% a significant amount of time after that.\n\n%% @todo Frames may arrive on a stream after we send an RST_STREAM for it.\n%% They must be ignored for a short period of time:\n%\n%      If this state is reached as a result of sending a RST_STREAM\n%      frame, the peer that receives the RST_STREAM might have already\n%      sent -- or enqueued for sending -- frames on the stream that\n%      cannot be withdrawn.  An endpoint MUST ignore frames that it\n%      receives on closed streams after it has sent a RST_STREAM frame.\n%      An endpoint MAY choose to limit the period over which it ignores\n%      frames and treat frames that arrive after this time as being in\n%      error.\n\n%% @todo Ensure that rejected DATA frames result in the connection\n%% flow-control window being updated. How to test this?\n%\n%      Flow-controlled frames (i.e., DATA) received after sending\n%      RST_STREAM are counted toward the connection flow-control window.\n%      Even though these frames might be ignored, because they are sent\n%      before the sender receives the RST_STREAM, the sender will\n%      consider the frames to count against the flow-control window.\n\n%% Stream identifiers.\n\nreject_streamid_even(Config) ->\n\tdoc(\"HEADERS frames received with an even-numbered streamid \"\n\t\t\"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 5.1.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with an even-numbered streamid.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(2, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nreject_streamid_0(Config) ->\n\tdoc(\"HEADERS frames received with streamid 0 (zero) \"\n\t\t\"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 5.1.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with an streamid 0.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(0, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% See the comment for reject_streamid_lower for the rationale behind\n%% having a STREAM_CLOSED connection error.\nhttp_upgrade_reject_reuse_streamid_1(Config) ->\n\tdoc(\"Attempts to reuse streamid 1 after upgrading to HTTP/2 \"\n\t\t\"must be rejected with a STREAM_CLOSED connection error. (RFC7540 5.1.1)\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade, HTTP2-Settings\\r\\n\"\n\t\t\"Upgrade: h2c\\r\\n\"\n\t\t\"HTTP2-Settings: \", base64:encode(cow_http2:settings_payload(#{})), \"\\r\\n\",\n\t\t\"\\r\\n\"]),\n\tok = do_recv_101(Socket),\n\t%% Send a valid preface.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Send the SETTINGS ack.\n\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t%% Receive the SETTINGS ack, and the response HEADERS and DATA (Stream ID 1).\n\tReceived = lists:reverse(lists:foldl(fun(_, Acc) ->\n\t\tcase gen_tcp:recv(Socket, 9, 1000) of\n\t\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} ->\n\t\t\t\t[settings_ack|Acc];\n\t\t\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[headers|Acc];\n\t\t\t{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->\n\t\t\t\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t\t\t\t[data|Acc]\n\t\tend\n\tend, [], [1, 2, 3])),\n\tcase Received of\n\t\t[settings_ack, headers, data] -> ok;\n\t\t[headers, settings_ack, data] -> ok;\n\t\t[headers, data, settings_ack] -> ok\n\tend,\n\t%% Send a HEADERS frame with streamid 1.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a STREAM_CLOSED connection error.\n\t{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% The RFC gives us various error codes to return for this case,\n%% depending on whether the stream existed previously and how it\n%% ended up being (half-)closed. Cowboy rejects all these HEADERS\n%% frames the same way: with a STREAM_CLOSED connection error.\n%% Making it a connection error is particularly important in the\n%% cases where a stream error would be allowed because we avoid\n%% having to decode the headers and save up resources.\nreject_streamid_lower(Config) ->\n\tdoc(\"HEADERS frames received with streamid lower than the previous stream \"\n\t\t\"must be rejected with a STREAM_CLOSED connection error. (RFC7540 5.1.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with streamid 5.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(5, fin, HeadersBlock)),\n\t%% Receive the response.\n\t{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length1, 6000),\n\t{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, _} = gen_tcp:recv(Socket, Length2, 6000),\n\t%% Send a HEADERS frame with streamid 3.\n\tok = gen_tcp:send(Socket, cow_http2:headers(3, fin, HeadersBlock)),\n\t%% Receive a STREAM_CLOSED connection error.\n\t{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% @todo We need an option to limit the number of streams one can open\n%% on a connection. And we need to enforce it. (RFC7540 5.1.1)\n%\n%   Stream identifiers cannot be reused.  Long-lived connections can\n%   result in an endpoint exhausting the available range of stream\n%   identifiers.  A server\n%   that is unable to establish a new stream identifier can send a GOAWAY\n%   frame so that the client is forced to open a new connection for new\n%   streams.\n\n%% (RFC7540 5.2.1)\n%   3.  Flow control is directional with overall control provided by the\n%       receiver.  A receiver MAY choose to set any window size that it\n%       desires for each stream and for the entire connection.  A sender\n%       MUST respect flow-control limits imposed by a receiver.  Clients,\n%       servers, and intermediaries all independently advertise their\n%       flow-control window as a receiver and abide by the flow-control\n%       limits set by their peer when sending.\n%\n%   4.  The initial value for the flow-control window is 65,535 octets\n%       for both new streams and the overall connection.\n%\n%   5.  The frame type determines whether flow control applies to a\n%       frame.  Of the frames specified in this document, only DATA\n%       frames are subject to flow control; all other frame types do not\n%       consume space in the advertised flow-control window.  This\n%       ensures that important control frames are not blocked by flow\n%       control.\n%\n%   6.  Flow control cannot be disabled.\n\n%% (RFC7540 5.2.2)\n%   Even with full awareness of the current bandwidth-delay product,\n%   implementation of flow control can be difficult.  When using flow\n%   control, the receiver MUST read from the TCP receive buffer in a\n%   timely fashion.  Failure to do so could lead to a deadlock when\n%   critical frames, such as WINDOW_UPDATE, are not read and acted upon.\n\n%% (RFC7540 5.3.1)\n%   Inside the dependency tree, a dependent stream SHOULD only be\n%   allocated resources if either all of the streams that it depends on\n%   (the chain of parent streams up to 0x0) are closed or it is not\n%   possible to make progress on them.\n\n%% We reject all invalid HEADERS with a connection error because\n%% we do not want to waste resources decoding them.\nreject_self_dependent_stream_headers(Config) ->\n\tdoc(\"HEADERS frames opening a stream that depends on itself \"\n\t\t\"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 5.3.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with priority set to depend on itself.\n\tok = gen_tcp:send(Socket, << 5:24, 1:8,\n\t\t0:2, 1:1, 0:4, 1:1, 0:1, 1:31, 0:1, 1:31, 0:8 >>),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% We reject all invalid HEADERS with a connection error because\n%% we do not want to waste resources decoding them.\nreject_self_dependent_stream_headers_with_padding(Config) ->\n\tdoc(\"HEADERS frames opening a stream that depends on itself \"\n\t\t\"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 5.3.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with priority set to depend on itself.\n\tok = gen_tcp:send(Socket, << 6:24, 1:8,\n\t\t0:2, 1:1, 0:1, 1:1, 0:2, 1:1, 0:1, 1:31, 0:8, 0:1, 1:31, 0:8 >>),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nreject_self_dependent_stream_priority(Config) ->\n\tdoc(\"PRIORITY frames making a stream depend on itself \"\n\t\t\"must be rejected with a PROTOCOL_ERROR stream error. (RFC7540 5.3.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a PRIORITY frame making a stream depend on itself.\n\tok = gen_tcp:send(Socket, cow_http2:priority(1, shared, 1, 123)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n%% @todo Stream priorities. (RFC7540 5.3.2 5.3.3 5.3.4 5.3.5)\n\n%% (RFC7540 5.4.1)\n%   An endpoint that encounters a connection error SHOULD first send a\n%   GOAWAY frame (Section 6.8) with the stream identifier of the last\n%   stream that it successfully received from its peer.  The GOAWAY frame\n%   includes an error code that indicates why the connection is\n%   terminating.  After sending the GOAWAY frame for an error condition,\n%   the endpoint MUST close the TCP connection.\n%\n%   An endpoint can end a connection at any time.  In particular, an\n%   endpoint MAY choose to treat a stream error as a connection error.\n%   Endpoints SHOULD send a GOAWAY frame when ending a connection,\n%   providing that circumstances permit it.\n\n%% (RFC7540 5.4.2)\n%   A RST_STREAM is the last frame that an endpoint can send on a stream.\n%   The peer that sends the RST_STREAM frame MUST be prepared to receive\n%   any frames that were sent or enqueued for sending by the remote peer.\n%   These frames can be ignored, except where they modify connection\n%   state (such as the state maintained for header compression\n%   (Section 4.3) or flow control).\n%\n%   Normally, an endpoint SHOULD NOT send more than one RST_STREAM frame\n%   for any stream.  However, an endpoint MAY send additional RST_STREAM\n%   frames if it receives frames on a closed stream after more than a\n%   round-trip time.  This behavior is permitted to deal with misbehaving\n%   implementations.\n%\n%   To avoid looping, an endpoint MUST NOT send a RST_STREAM in response\n%   to a RST_STREAM frame.\n\n%% (RFC7540 5.5)\n%   Extensions are permitted to use new frame types (Section 4.1), new\n%   settings (Section 6.5.2), or new error codes (Section 7).  Registries\n%   are established for managing these extension points: frame types\n%   (Section 11.2), settings (Section 11.3), and error codes\n%   (Section 11.4).\n%\n%   Implementations MUST ignore unknown or unsupported values in all\n%   extensible protocol elements.  Implementations MUST discard frames\n%   that have unknown or unsupported types.  This means that any of these\n%   extension points can be safely used by extensions without prior\n%   arrangement or negotiation.  However, extension frames that appear in\n%   the middle of a header block (Section 4.3) are not permitted; these\n%   MUST be treated as a connection error (Section 5.4.1) of type\n%   PROTOCOL_ERROR.\n\ncontinuation_with_extension_frame_interleaved_error(Config) ->\n\tdoc(\"Extension frames interleaved in a header block must be rejected \"\n\t\t\"with a PROTOCOL_ERROR connection error. \"\n\t\t\"(RFC7540 4.3, RFC7540 5.5, RFC7540 6.2, RFC7540 6.10)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send an unterminated HEADERS frame followed by an extension frame.\n\tok = gen_tcp:send(Socket, [\n\t\t<< 0:24, 1:8, 0:7, 1:1, 0:1, 1:31 >>,\n\t\t<< 0:24, 128:8, 0:8, 0:32 >>\n\t]),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% (RFC7540 6.1) DATA\n%   Padding:  Padding octets that contain no application semantic value.\n%      Padding octets MUST be set to zero when sending.  A receiver is\n%      not obligated to verify padding but MAY treat non-zero padding as\n%      a connection error (Section 5.4.1) of type PROTOCOL_ERROR.\n%\n%   DATA frames MUST be associated with a stream.  If a DATA frame is\n%   received whose stream identifier field is 0x0, the recipient MUST\n%   respond with a connection error (Section 5.4.1) of type\n%   PROTOCOL_ERROR.\n\n%% (RFC7540 6.2) HEADERS\n%   Padding:  Padding octets that contain no application semantic value.\n%      Padding octets MUST be set to zero when sending.  A receiver is\n%      not obligated to verify padding but MAY treat non-zero padding as\n%      a connection error (Section 5.4.1) of type PROTOCOL_ERROR.\n%\n%      A HEADERS frame carries the END_STREAM flag that signals the end\n%      of a stream.  However, a HEADERS frame with the END_STREAM flag\n%      set can be followed by CONTINUATION frames on the same stream.\n%      Logically, the CONTINUATION frames are part of the HEADERS frame.\n%\n%% @todo We probably need a test for the server sending HEADERS too large.\n%   The payload of a HEADERS frame contains a header block fragment\n%   (Section 4.3).  A header block that does not fit within a HEADERS\n%   frame is continued in a CONTINUATION frame (Section 6.10).\n%\n%   HEADERS frames MUST be associated with a stream.  If a HEADERS frame\n%   is received whose stream identifier field is 0x0, the recipient MUST\n%   respond with a connection error (Section 5.4.1) of type\n%   PROTOCOL_ERROR.\n\n%% (RFC7540 6.3) PRIORITY\n%   The PRIORITY frame always identifies a stream.  If a PRIORITY frame\n%   is received with a stream identifier of 0x0, the recipient MUST\n%   respond with a connection error (Section 5.4.1) of type\n%   PROTOCOL_ERROR.\n\n%% (RFC7540 6.4) RST_STREAM\n%   The RST_STREAM frame fully terminates the referenced stream and\n%   causes it to enter the \"closed\" state.  After receiving a RST_STREAM\n%   on a stream, the receiver MUST NOT send additional frames for that\n%   stream, with the exception of PRIORITY.  However, after sending the\n%   RST_STREAM, the sending endpoint MUST be prepared to receive and\n%   process additional frames sent on the stream that might have been\n%   sent by the peer prior to the arrival of the RST_STREAM.\n%\n%   RST_STREAM frames MUST be associated with a stream.  If a RST_STREAM\n%   frame is received with a stream identifier of 0x0, the recipient MUST\n%   treat this as a connection error (Section 5.4.1) of type\n%   PROTOCOL_ERROR.\n\n%% (RFC7540 6.5) SETTINGS\n%   A SETTINGS frame MUST be sent by both endpoints at the start of a\n%   connection and MAY be sent at any other time by either endpoint over\n%   the lifetime of the connection.  Implementations MUST support all of\n%   the parameters defined by this specification.\n%\n%   SETTINGS frames always apply to a connection, never a single stream.\n%   The stream identifier for a SETTINGS frame MUST be zero (0x0).  If an\n%   endpoint receives a SETTINGS frame whose stream identifier field is\n%   anything other than 0x0, the endpoint MUST respond with a connection\n%   error (Section 5.4.1) of type PROTOCOL_ERROR.\n%\n%   The SETTINGS frame affects connection state.  A badly formed or\n%   incomplete SETTINGS frame MUST be treated as a connection error\n%   (Section 5.4.1) of type PROTOCOL_ERROR.\n\n%% Settings.\n\nsettings_header_table_size_client(Config) ->\n\tdoc(\"The SETTINGS_HEADER_TABLE_SIZE setting can be used to \"\n\t\t\"inform the server of the maximum header table size \"\n\t\t\"used by the client to decode header blocks. (RFC7540 6.5.2)\"),\n\tHeaderTableSize = 128,\n\t%% Do the handhsake.\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a valid preface.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\",\n\t\tcow_http2:settings(#{header_table_size => HeaderTableSize})]),\n\t%% Receive the server preface.\n\t{ok, << Len0:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len0/binary >>} = gen_tcp:recv(Socket, 6 + Len0, 1000),\n\t%% Send the SETTINGS ack.\n\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Initialize decoding/encoding states.\n\tDecodeState = cow_hpack:set_max_size(HeaderTableSize, cow_hpack:init()),\n\tEncodeState = cow_hpack:init(),\n\t%% Send a HEADERS frame as a request.\n\t{ReqHeadersBlock1, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t], EncodeState),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, ReqHeadersBlock1)),\n\t%% Receive a HEADERS frame as a response.\n\t{ok, << Len1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, RespHeadersBlock1} = gen_tcp:recv(Socket, Len1, 6000),\n\t{RespHeaders, _} = cow_hpack:decode(RespHeadersBlock1, DecodeState),\n\t{_, <<\"200\">>} = lists:keyfind(<<\":status\">>, 1, RespHeaders),\n\t%% The decoding succeeded, confirming that the table size is\n\t%% lower than or equal to HeaderTableSize.\n\tok.\n\nsettings_header_table_size_server(Config0) ->\n\tdoc(\"The SETTINGS_HEADER_TABLE_SIZE setting can be used to \"\n\t\t\"inform the client of the maximum header table size \"\n\t\t\"used by the server to decode header blocks. (RFC7540 6.5.2)\"),\n\tHeaderTableSize = 128,\n\t%% Create a new listener that allows larger header table sizes.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tmax_decode_table_size => HeaderTableSize\n\t}, Config0),\n\ttry\n\t\t%% Do the handhsake.\n\t\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t\t%% Send a valid preface.\n\t\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\",\n\t\t\tcow_http2:settings(#{header_table_size => HeaderTableSize})]),\n\t\t%% Receive the server preface.\n\t\t{ok, << Len0:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t\t{ok, Data = <<_:48, _:Len0/binary>>} = gen_tcp:recv(Socket, 6 + Len0, 1000),\n\t\t%% Confirm the server's SETTINGS_HEADERS_TABLE_SIZE uses HeaderTableSize.\n\t\t{ok, {settings, #{header_table_size := HeaderTableSize}}, <<>>}\n\t\t\t= cow_http2:parse(<<Len0:24, Data/binary>>),\n\t\t%% Send the SETTINGS ack.\n\t\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t\t%% Receive the SETTINGS ack.\n\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t\t%% Initialize decoding/encoding states.\n\t\tDecodeState = cow_hpack:init(),\n\t\tEncodeState = cow_hpack:set_max_size(HeaderTableSize, cow_hpack:init()),\n\t\t%% Send a HEADERS frame as a request.\n\t\t{ReqHeadersBlock1, _} = cow_hpack:encode([\n\t\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/\">>}\n\t\t], EncodeState),\n\t\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, ReqHeadersBlock1)),\n\t\t%% Receive a HEADERS frame as a response.\n\t\t{ok, << Len1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t\t{ok, RespHeadersBlock1} = gen_tcp:recv(Socket, Len1, 6000),\n\t\t{RespHeaders, _} = cow_hpack:decode(RespHeadersBlock1, DecodeState),\n\t\t{_, <<\"200\">>} = lists:keyfind(<<\":status\">>, 1, RespHeaders),\n\t\t%% The decoding succeeded on the server, confirming that\n\t\t%% the table size was updated to HeaderTableSize.\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nsettings_max_concurrent_streams(Config0) ->\n\tdoc(\"The SETTINGS_MAX_CONCURRENT_STREAMS setting can be used to \"\n\t\t\"restrict the number of concurrent streams. (RFC7540 5.1.2, RFC7540 6.5.2)\"),\n\t%% Create a new listener that allows only a single concurrent stream.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tmax_concurrent_streams => 1\n\t}, Config0),\n\ttry\n\t\t{ok, Socket} = do_handshake(Config),\n\t\t%% Send two HEADERS frames as two separate streams.\n\t\tHeaders = [\n\t\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t\t],\n\t\t{ReqHeadersBlock1, EncodeState} = cow_hpack:encode(Headers),\n\t\t{ReqHeadersBlock2, _} = cow_hpack:encode(Headers, EncodeState),\n\t\tok = gen_tcp:send(Socket, [\n\t\t\tcow_http2:headers(1, fin, ReqHeadersBlock1),\n\t\t\tcow_http2:headers(3, fin, ReqHeadersBlock2)\n\t\t]),\n\t\t%% Receive a REFUSED_STREAM stream error.\n\t\t{ok, << _:24, 3:8, _:8, 3:32, 7:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nsettings_max_concurrent_streams_0(Config0) ->\n\tdoc(\"The SETTINGS_MAX_CONCURRENT_STREAMS setting can be set to \"\n\t\t\"0 to refuse all incoming streams. (RFC7540 5.1.2, RFC7540 6.5.2)\"),\n\t%% Create a new listener that allows only a single concurrent stream.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tmax_concurrent_streams => 0\n\t}, Config0),\n\ttry\n\t\t{ok, Socket} = do_handshake(Config),\n\t\t%% Send a HEADERS frame.\n\t\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t\t]),\n\t\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t\t%% Receive a REFUSED_STREAM stream error.\n\t\t{ok, << _:24, 3:8, _:8, 1:32, 7:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\n%% @todo The client can limit the number of concurrent streams too. (RFC7540 5.1.2)\n%\n%   A peer can limit the number of concurrently active streams using the\n%   SETTINGS_MAX_CONCURRENT_STREAMS parameter (see Section 6.5.2) within\n%   a SETTINGS frame.  The maximum concurrent streams setting is specific\n%   to each endpoint and applies only to the peer that receives the\n%   setting.  That is, clients specify the maximum number of concurrent\n%   streams the server can initiate, and servers specify the maximum\n%   number of concurrent streams the client can initiate.\n%\n%   Endpoints MUST NOT exceed the limit set by their peer.  An endpoint\n%   that receives a HEADERS frame that causes its advertised concurrent\n%   stream limit to be exceeded MUST treat this as a stream error\n%   (Section 5.4.2) of type PROTOCOL_ERROR or REFUSED_STREAM.  The choice\n%   of error code determines whether the endpoint wishes to enable\n%   automatic retry (see Section 8.1.4) for details).\n\nsettings_initial_window_size(Config0) ->\n\tdoc(\"The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to \"\n\t\t\"change the initial window size of streams. (RFC7540 6.5.2)\"),\n\t%% Create a new listener that sets initial window sizes to 100000.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tinitial_connection_window_size => 100000,\n\t\tinitial_stream_window_size => 100000\n\t}, Config0),\n\ttry\n\t\t%% We need to do the handshake manually because a WINDOW_UPDATE\n\t\t%% frame will be sent to update the connection window.\n\t\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t\t%% Send a valid preface.\n\t\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t\t%% Receive the server preface.\n\t\t{ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t\t{ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000),\n\t\t%% Send the SETTINGS ack.\n\t\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t\t%% Receive the WINDOW_UPDATE for the connection.\n\t\t{ok, << 4:24, 8:8, 0:40, _:32 >>} = gen_tcp:recv(Socket, 13, 1000),\n\t\t%% Receive the SETTINGS ack.\n\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t\t%% Send a HEADERS frame initiating a stream followed by\n\t\t%% DATA frames totaling 90000 bytes of body.\n\t\tHeaders = [\n\t\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t\t],\n\t\t{HeadersBlock, _} = cow_hpack:encode(Headers),\n\t\tok = gen_tcp:send(Socket, [\n\t\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, fin, <<0:15000/unit:8>>)\n\t\t]),\n\t\t%% Receive a proper response.\n\t\t{ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t\t{ok, _} = gen_tcp:recv(Socket, Len2, 6000),\n\t\t%% No errors follow due to our sending of more than 65535 bytes of data.\n\t\t{error, timeout} = gen_tcp:recv(Socket, 0, 1000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nsettings_initial_window_size_after_ack(Config0) ->\n\tdoc(\"The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to \"\n\t\t\"change the initial window size of streams. It is applied \"\n\t\t\"to all existing streams upon receipt of the SETTINGS ack. (RFC7540 6.5.2)\"),\n\t%% Create a new listener that sets the initial stream window sizes to 0.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tinitial_stream_window_size => 0\n\t}, Config0),\n\ttry\n\t\t%% We need to do the handshake manually because we don't\n\t\t%% want to send the SETTINGS ack immediately.\n\t\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t\t%% Send a valid preface.\n\t\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t\t%% Receive the server preface.\n\t\t{ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t\t{ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000),\n\t\t%%\n\t\t%% Don't send the SETTINGS ack yet! We want to create a stream first.\n\t\t%%\n\t\t%% Receive the SETTINGS ack.\n\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t\t%% Send a HEADERS frame initiating a stream, a SETTINGS ack\n\t\t%% and a small DATA frame despite no window available in the stream.\n\t\tHeaders = [\n\t\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t\t],\n\t\t{HeadersBlock, _} = cow_hpack:encode(Headers),\n\t\tok = gen_tcp:send(Socket, [\n\t\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t\tcow_http2:settings_ack(),\n\t\t\tcow_http2:data(1, fin, <<0:32/unit:8>>)\n\t\t]),\n\t\t%% Receive a FLOW_CONTROL_ERROR stream error.\n\t\t{ok, << _:24, 3:8, _:8, 1:32, 3:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nsettings_initial_window_size_before_ack(Config0) ->\n\tdoc(\"The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to \"\n\t\t\"change the initial window size of streams. It is only \"\n\t\t\"applied upon receipt of the SETTINGS ack. (RFC7540 6.5.2)\"),\n\t%% Create a new listener that sets the initial stream window sizes to 0.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tinitial_stream_window_size => 0\n\t}, Config0),\n\ttry\n\t\t%% We need to do the handshake manually because we don't\n\t\t%% want to send the SETTINGS ack.\n\t\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t\t%% Send a valid preface.\n\t\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t\t%% Receive the server preface.\n\t\t{ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t\t{ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000),\n\t\t%%\n\t\t%% Don't send the SETTINGS ack! We want the server to keep the original settings.\n\t\t%%\n\t\t%% Receive the SETTINGS ack.\n\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t\t%% Send a HEADERS frame initiating a stream followed by\n\t\t%% DATA frames totaling 60000 bytes of body.\n\t\tHeaders = [\n\t\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t\t],\n\t\t{HeadersBlock, _} = cow_hpack:encode(Headers),\n\t\tok = gen_tcp:send(Socket, [\n\t\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, fin, <<0:15000/unit:8>>)\n\t\t]),\n\t\t%% Receive a proper response.\n\t\t{ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t\t{ok, _} = gen_tcp:recv(Socket, Len2, 6000),\n\t\t%% No errors follow due to our sending of more than 0 bytes of data.\n\t\t{error, timeout} = gen_tcp:recv(Socket, 0, 1000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nsettings_max_frame_size(Config0) ->\n\tdoc(\"The SETTINGS_MAX_FRAME_SIZE setting can be used to \"\n\t\t\"change the maximum frame size allowed. (RFC7540 6.5.2)\"),\n\t%% Create a new listener that sets the maximum frame size to 30000.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tmax_frame_size_received => 30000\n\t}, Config0),\n\ttry\n\t\t%% Do the handshake.\n\t\t{ok, Socket} = do_handshake(Config),\n\t\t%% Send a HEADERS frame initiating a stream followed by\n\t\t%% a single 25000 bytes DATA frame.\n\t\tHeaders = [\n\t\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t\t],\n\t\t{HeadersBlock, _} = cow_hpack:encode(Headers),\n\t\tok = gen_tcp:send(Socket, [\n\t\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t\tcow_http2:data(1, fin, <<0:25000/unit:8>>)\n\t\t]),\n\t\t%% Receive a proper response.\n\t\t{ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t\t{ok, _} = gen_tcp:recv(Socket, Len2, 6000),\n\t\t%% No errors follow due to our sending of a 25000 bytes frame.\n\t\t{error, timeout} = gen_tcp:recv(Socket, 0, 1000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\nsettings_max_frame_size_reject_too_small(Config) ->\n\tdoc(\"A SETTINGS_MAX_FRAME_SIZE smaller than 16384 must be rejected \"\n\t\t\"with a PROTOCOL_ERROR connection error. (RFC7540 6.5.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a SETTINGS frame with a SETTINGS_MAX_FRAME_SIZE lower than 16384.\n\tok = gen_tcp:send(Socket, << 6:24, 4:8, 0:40, 5:16, 16383:32 >>),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nsettings_max_frame_size_reject_too_large(Config) ->\n\tdoc(\"A SETTINGS_MAX_FRAME_SIZE larger than 16777215 must be rejected \"\n\t\t\"with a PROTOCOL_ERROR connection error. (RFC7540 6.5.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a SETTINGS frame with a SETTINGS_MAX_FRAME_SIZE larger than 16777215.\n\tok = gen_tcp:send(Socket, << 6:24, 4:8, 0:40, 5:16, 16777216:32 >>),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%   SETTINGS_MAX_HEADER_LIST_SIZE (0x6):  This advisory setting informs a\n%      peer of the maximum size of header list that the sender is\n%      prepared to accept, in octets.  The value is based on the\n%      uncompressed size of header fields, including the length of the\n%      name and value in octets plus an overhead of 32 octets for each\n%      header field.\n%\n%      For any given request, a lower limit than what is advertised MAY\n%      be enforced.  The initial value of this setting is unlimited.\n%\n%   An endpoint that receives a SETTINGS frame with any unknown or\n%   unsupported identifier MUST ignore that setting. (6.5.2 and 6.5.3)\n\n%% (RFC7540 6.5.3)\n%   Upon receiving a SETTINGS frame with the ACK flag set, the\n%   sender of the altered parameters can rely on the setting having been\n%   applied.\n%\n%   If the sender of a SETTINGS frame does not receive an acknowledgement\n%   within a reasonable amount of time, it MAY issue a connection error\n%   (Section 5.4.1) of type SETTINGS_TIMEOUT.\n\n%% (RFC7540 6.6) PUSH_PROMISE\n% @todo PUSH_PROMISE frames have a reserved bit in the payload that must be ignored.\n%\n%   Padding:  Padding octets that contain no application semantic value.\n%      Padding octets MUST be set to zero when sending.  A receiver is\n%      not obligated to verify padding but MAY treat non-zero padding as\n%      a connection error (Section 5.4.1) of type PROTOCOL_ERROR.\n%\n%   PUSH_PROMISE frames MUST only be sent on a peer-initiated stream that\n%   is in either the \"open\" or \"half-closed (remote)\" state.  The stream\n%   identifier of a PUSH_PROMISE frame indicates the stream it is\n%   associated with.  If the stream identifier field specifies the value\n%   0x0, a recipient MUST respond with a connection error (Section 5.4.1)\n%   of type PROTOCOL_ERROR.\n\nclient_settings_disable_push(Config) ->\n\tdoc(\"PUSH_PROMISE frames must not be sent when the setting \"\n\t\t\"SETTINGS_ENABLE_PUSH is disabled. (RFC7540 6.5.2, RFC7540 6.6, RFC7540 8.2)\"),\n\t%% Do a prior knowledge handshake.\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a valid preface.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{\n\t\tenable_push => false\n\t})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t%% Send the SETTINGS ack.\n\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Send a HEADERS frame on a resource that sends PUSH_PROMISE frames.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/resp/push\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a HEADERS frame as a response, no PUSH_PROMISE frames.\n\t{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\n%   Since PUSH_PROMISE reserves a stream, ignoring a PUSH_PROMISE frame\n%   causes the stream state to become indeterminate.  A receiver MUST\n%   treat the receipt of a PUSH_PROMISE on a stream that is neither\n%   \"open\" nor \"half-closed (local)\" as a connection error\n%   (Section 5.4.1) of type PROTOCOL_ERROR.\n%\n%   A receiver MUST treat the receipt of a PUSH_PROMISE that promises an\n%   illegal stream identifier (Section 5.1.1) as a connection error\n%   (Section 5.4.1) of type PROTOCOL_ERROR.  Note that an illegal stream\n%   identifier is an identifier for a stream that is not currently in the\n%   \"idle\" state.\n\n%% (RFC7540 6.7) PING\n%   PING frames are not associated with any individual stream.  If a PING\n%   frame is received with a stream identifier field value other than\n%   0x0, the recipient MUST respond with a connection error\n%   (Section 5.4.1) of type PROTOCOL_ERROR.\n\n%% (RFC7540 6.8) GOAWAY\n% @todo GOAWAY frames have a reserved bit in the payload that must be ignored.\n%\n%   A GOAWAY frame might not immediately precede closing of the\n%   connection; a receiver of a GOAWAY that has no more use for the\n%   connection SHOULD still send a GOAWAY frame before terminating the\n%   connection.\n\ngraceful_shutdown_client_stays(Config) ->\n\tdoc(\"A server gracefully shutting down must send a GOAWAY frame with the \"\n\t\t\"last stream identifier set to 2^31-1 and a NO_ERROR code. After allowing \"\n\t\t\"time for any in-flight stream creation the server can send another GOAWAY \"\n\t\t\"frame with an updated last stream identifier. (RFC7540 6.8)\"),\n\t{ok, Socket} = do_handshake(Config),\n\tServerConnPid = get_remote_pid_tcp(Socket),\n\tok = sys:terminate(ServerConnPid, whatever),\n\t%% First GOAWAY frame.\n\t{ok, <<_:24, 7:8, 0:8, 0:1, 0:31, 0:1, 16#7fffffff:31, 0:32>>} = gen_tcp:recv(Socket, 17, 500),\n\t%% Second GOAWAY frame.\n\t{ok, <<_:24, 7:8, 0:8, 0:1, 0:31, 0:1, 0:31, 0:32>>} = gen_tcp:recv(Socket, 17, 1500),\n\t{error, closed} = gen_tcp:recv(Socket, 3, 1000),\n\tok.\n\n%% @todo We should add this test also for discarded DATA and CONTINUATION frames.\n%% The test can be the same for CONTINUATION (just send headers differently) but\n%% the DATA test should make sure the global window is not corrupted.\n%%\n%% @todo We should extend this test to have two requests: one initiated before\n%% the second GOAWAY, but not terminated; another initiated after the GOAWAY, terminated.\n%% Finally the first request is terminated by sending a body and a trailing\n%% HEADERS frame. This way we know for sure that the connection state is not corrupt.\ngraceful_shutdown_race_condition(Config) ->\n\tdoc(\"A server in the process of gracefully shutting down must discard frames \"\n\t\t\"for streams initiated by the receiver with identifiers higher than the \"\n\t\t\"identified last stream. This may include frames that alter connection \"\n\t\t\"state such as HEADERS frames. (RFC7540 6.8)\"),\n\t{ok, Socket} = do_handshake(Config),\n\tServerConnPid = get_remote_pid_tcp(Socket),\n\tok = sys:terminate(ServerConnPid, whatever),\n\t%% First GOAWAY frame.\n\t{ok, <<_:24, 7:8, 0:8, 0:1, 0:31, 0:1, 16#7fffffff:31, 0:32>>} = gen_tcp:recv(Socket, 17, 500),\n\t%% Simulate an in-flight request, sent by the client before the\n\t%% GOAWAY frame arrived to the client.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/delay_hello\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Second GOAWAY frame.\n\t{ok, <<_:24, 7:8, 0:8, 0:1, 0:31, 0:1, 1:31, 0:32>>} = gen_tcp:recv(Socket, 17, 2000),\n\t%% The client tries to send another request, ignoring the GOAWAY.\n\tok = gen_tcp:send(Socket, cow_http2:headers(3, fin, HeadersBlock)),\n\t%% The server responds to the first request (streamid 1) and closes.\n\t{ok, <<RespHeadersPayloadLength:24, 1, 4, 0:1, 1:31>>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _RespHeaders} = gen_tcp:recv(Socket, RespHeadersPayloadLength, 1000),\n\t{ok, <<12:24, 0, 1, 0:1, 1:31, \"Hello world!\">>} = gen_tcp:recv(Socket, 21, 1000),\n\t{error, closed} = gen_tcp:recv(Socket, 3, 1000),\n\tok.\n\n%   The GOAWAY frame applies to the connection, not a specific stream.\n%   An endpoint MUST treat a GOAWAY frame with a stream identifier other\n%   than 0x0 as a connection error (Section 5.4.1) of type\n%   PROTOCOL_ERROR.\n\n%% (RFC7540 6.9) WINDOW_UPDATE\n% @todo WINDOW_UPDATE frames have a reserved bit in the payload that must be ignored.\n\nwindow_update_reject_0(Config) ->\n\tdoc(\"WINDOW_UPDATE frames with an increment of 0 for the connection \"\n\t\t\"flow control window must be rejected with a \"\n\t\t\"PROTOCOL_ERROR connection error. (RFC7540 6.9.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send connection-wide WINDOW_UPDATE frame with a value of 0.\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:window_update(0)\n\t]),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nwindow_update_reject_0_stream(Config) ->\n\tdoc(\"WINDOW_UPDATE frames with an increment of 0 for a stream \"\n\t\t\"flow control window must be rejected with a \"\n\t\t\"PROTOCOL_ERROR stream error. (RFC7540 6.9.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame immediately followed by\n\t%% a WINDOW_UPDATE frame with a value of 0.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, fin, HeadersBlock),\n\t\tcow_http2:window_update(1, 0)\n\t]),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n%   A receiver that receives a flow-controlled frame MUST always account\n%   for its contribution against the connection flow-control window,\n%   unless the receiver treats this as a connection error\n%   (Section 5.4.1).  This is necessary even if the frame is in error.\n%   The sender counts the frame toward the flow-control window, but if\n%   the receiver does not, the flow-control window at the sender and\n%   receiver can become different.\n\ndata_reject_overflow(Config0) ->\n\tdoc(\"DATA frames that cause the connection flow control window \"\n\t\t\"to overflow must be rejected with a FLOW_CONTROL_ERROR \"\n\t\t\"connection error. (RFC7540 6.9.1)\"),\n\t%% Create a new listener that allows only a single concurrent stream.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tinitial_stream_window_size => 100000\n\t}, Config0),\n\ttry\n\t\t{ok, Socket} = do_handshake(Config),\n\t\t%% Send a HEADERS frame initiating a stream followed by\n\t\t%% DATA frames totaling 90000 bytes of body.\n\t\tHeaders = [\n\t\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t\t],\n\t\t{HeadersBlock, _} = cow_hpack:encode(Headers),\n\t\tok = gen_tcp:send(Socket, [\n\t\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, fin, <<0:15000/unit:8>>)\n\t\t]),\n\t\t%% Receive a FLOW_CONTROL_ERROR connection error.\n\t\t{ok, << _:24, 7:8, _:72, 3:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\ndata_reject_overflow_stream(Config0) ->\n\tdoc(\"DATA frames that cause the stream flow control window \"\n\t\t\"to overflow must be rejected with a FLOW_CONTROL_ERROR \"\n\t\t\"stream error. (RFC7540 6.9.1)\"),\n\t%% Create a new listener that allows only a single concurrent stream.\n\tConfig = cowboy_test:init_http(?FUNCTION_NAME, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))},\n\t\tinitial_connection_window_size => 100000\n\t}, Config0),\n\ttry\n\t\t%% We need to do the handshake manually because a WINDOW_UPDATE\n\t\t%% frame will be sent to update the connection window.\n\t\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t\t%% Send a valid preface.\n\t\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t\t%% Receive the server preface.\n\t\t{ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t\t{ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000),\n\t\t%% Send the SETTINGS ack.\n\t\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t\t%% Receive the WINDOW_UPDATE for the connection.\n\t\t{ok, << 4:24, 8:8, 0:40, _:32 >>} = gen_tcp:recv(Socket, 13, 1000),\n\t\t%% Receive the SETTINGS ack.\n\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t\t%% Send a HEADERS frame initiating a stream followed by\n\t\t%% DATA frames totaling 90000 bytes of body.\n\t\tHeaders = [\n\t\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t\t],\n\t\t{HeadersBlock, _} = cow_hpack:encode(Headers),\n\t\tok = gen_tcp:send(Socket, [\n\t\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, nofin, <<0:15000/unit:8>>),\n\t\t\tcow_http2:data(1, fin, <<0:15000/unit:8>>)\n\t\t]),\n\t\t%% Receive a FLOW_CONTROL_ERROR stream error.\n\t\t{ok, << _:24, 3:8, _:8, 1:32, 3:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\t\tgen_tcp:close(Socket)\n\tafter\n\t\tcowboy:stop_listener(?FUNCTION_NAME)\n\tend.\n\n%% (RFC7540 6.9.1)\n%   Frames with zero length with the END_STREAM flag set (that\n%   is, an empty DATA frame) MAY be sent if there is no available space\n%   in either flow-control window.\n\nwindow_update_reject_overflow(Config) ->\n\tdoc(\"WINDOW_UPDATE frames that cause the connection flow control \"\n\t\t\"window to exceed 2^31-1 must be rejected with a \"\n\t\t\"FLOW_CONTROL_ERROR connection error. (RFC7540 6.9.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a connection-wide WINDOW_UPDATE frame that causes the window to overflow.\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:window_update(16#7fffffff)\n\t]),\n\t%% Receive a FLOW_CONTROL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 3:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nwindow_update_reject_overflow_stream(Config) ->\n\tdoc(\"WINDOW_UPDATE frames that cause a stream flow control \"\n\t\t\"window to exceed 2^31-1 must be rejected with a \"\n\t\t\"FLOW_CONTROL_ERROR stream error. (RFC7540 6.9.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame immediately followed by a WINDOW_UPDATE\n\t%% frame that causes the stream window to overflow.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, fin, HeadersBlock),\n\t\tcow_http2:window_update(1, 16#7fffffff)\n\t]),\n\t%% Receive a FLOW_CONTROL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 3:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nsettings_initial_window_size_changes(Config) ->\n\tdoc(\"When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, the server \"\n\t\t\"must adjust the size of the flow control windows of the active \"\n\t\t\"streams. (RFC7540 6.9.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Set SETTINGS_INITIAL_WINDOW_SIZE to 0 to prevent sending of DATA.\n\tok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 0})),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a response but no DATA frames are coming.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{error, timeout} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Set SETTINGS_INITIAL_WINDOW_SIZE to a larger value.\n\tok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 5})),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Receive a DATA frame of that size and no other.\n\t{ok, << 5:24, 0:8, 0:8, 1:32, \"Hello\" >>} = gen_tcp:recv(Socket, 14, 1000),\n\t{error, timeout} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Set SETTINGS_INITIAL_WINDOW_SIZE to exactly the size in the body.\n\tok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 12})),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Receive the rest of the response.\n\t{ok, << 7:24, 0:8, 1:8, 1:32, \" world!\" >>} = gen_tcp:recv(Socket, 16, 1000),\n\tok.\n\nsettings_initial_window_size_changes_negative(Config) ->\n\tdoc(\"When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, the server \"\n\t\t\"must adjust the size of the flow control windows of the active \"\n\t\t\"streams even if their window end up negative. (RFC7540 6.9.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Set SETTINGS_INITIAL_WINDOW_SIZE to 5.\n\tok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 5})),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Send a HEADERS frame.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a response with a single DATA frame of the initial size we set.\n\t{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, << 5:24, 0:8, 0:8, 1:32, \"Hello\" >>} = gen_tcp:recv(Socket, 14, 1000),\n\t{error, timeout} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Set SETTINGS_INITIAL_WINDOW_SIZE to 0 to make the stream's window negative.\n\tok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 0})),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Set SETTINGS_INITIAL_WINDOW_SIZE to exactly the size in the body.\n\tok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 12})),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t%% Receive the rest of the response.\n\t{ok, << 7:24, 0:8, 1:8, 1:32, \" world!\" >>} = gen_tcp:recv(Socket, 16, 1000),\n\tok.\n\nsettings_initial_window_size_reject_overflow(Config) ->\n\tdoc(\"A SETTINGS_INITIAL_WINDOW_SIZE that causes a flow control window \"\n\t\t\"to exceed 2^31-1 must be rejected with a FLOW_CONTROL_ERROR \"\n\t\t\"connection error. (RFC7540 6.5.2, RFC7540 6.9.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Set SETTINGS_INITIAL_WINDOW_SIZE to 2^31.\n\tok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 16#80000000})),\n\t%% Receive a FLOW_CONTROL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 3:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% (RFC7540 6.9.3)\n%% @todo The right way to do this seems to be to wait for the SETTINGS ack\n%% before we KNOW the flow control window was updated on the other side.\n%   A receiver that wishes to use a smaller flow-control window than the\n%   current size can send a new SETTINGS frame.  However, the receiver\n%   MUST be prepared to receive data that exceeds this window size, since\n%   the sender might send data that exceeds the lower limit prior to\n%   processing the SETTINGS frame.\n\n%% (RFC7540 6.10) CONTINUATION\n%   CONTINUATION frames MUST be associated with a stream.  If a\n%   CONTINUATION frame is received whose stream identifier field is 0x0,\n%   the recipient MUST respond with a connection error (Section 5.4.1) of\n%   type PROTOCOL_ERROR.\n%\n%   A CONTINUATION frame MUST be preceded by a HEADERS, PUSH_PROMISE or\n%   CONTINUATION frame without the END_HEADERS flag set.  A recipient\n%   that observes violation of this rule MUST respond with a connection\n%   error (Section 5.4.1) of type PROTOCOL_ERROR.\n\n%% (RFC7540 7) Error Codes\n%   Unknown or unsupported error codes MUST NOT trigger any special\n%   behavior.  These MAY be treated by an implementation as being\n%   equivalent to INTERNAL_ERROR.\n\naccept_trailers(Config) ->\n\tdoc(\"Trailing HEADERS frames must be accepted. (RFC7540 8.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a request containing DATA and trailing HEADERS frames.\n\t{HeadersBlock, EncodeState} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>},\n\t\t{<<\"trailer\">>, <<\"x-checksum\">>}\n\t]),\n\t{TrailersBlock, _} = cow_hpack:encode([\n\t\t{<<\"x-checksum\">>, <<\"md5:4cc909a007407f3706399b6496babec3\">>}\n\t], EncodeState),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\tcow_http2:data(1, nofin, <<0:10000/unit:8>>),\n\t\tcow_http2:headers(1, fin, TrailersBlock)\n\t]),\n\t%% Receive a HEADERS frame as a response.\n\t{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\naccept_trailers_continuation(Config) ->\n\tdoc(\"Trailing HEADERS and CONTINUATION frames must be accepted. (RFC7540 8.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a request containing DATA and trailing HEADERS and CONTINUATION frames.\n\t{HeadersBlock, EncodeState} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>},\n\t\t{<<\"trailer\">>, <<\"x-checksum\">>}\n\t]),\n\t{TrailersBlock, _} = cow_hpack:encode([\n\t\t{<<\"x-checksum\">>, <<\"md5:4cc909a007407f3706399b6496babec3\">>}\n\t], EncodeState),\n\tLen = iolist_size(TrailersBlock),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\tcow_http2:data(1, nofin, <<0:10000/unit:8>>),\n\t\t<<0:24, 1:8, 0:7, 1:1, 0:1, 1:31>>,\n\t\t<<Len:24, 9:8, 0:5, 1:1, 0:3, 1:31>>,\n\t\tTrailersBlock\n\t]),\n\t%% Receive a HEADERS frame as a response.\n\t{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\n%% We reject all invalid HEADERS with a connection error because\n%% we do not want to waste resources decoding them.\nreject_trailers_nofin(Config) ->\n\tdoc(\"Trailing HEADERS frames received without the END_STREAM flag \"\n\t\t\"set must be rejected with a PROTOCOL_ERROR connection error. \"\n\t\t\"(RFC7540 8.1, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a request containing DATA and trailing HEADERS frames.\n\t%% The trailing HEADERS does not have the END_STREAM flag set.\n\t{HeadersBlock, EncodeState} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>},\n\t\t{<<\"trailer\">>, <<\"x-checksum\">>}\n\t]),\n\t{TrailersBlock, _} = cow_hpack:encode([\n\t\t{<<\"x-checksum\">>, <<\"md5:4cc909a007407f3706399b6496babec3\">>}\n\t], EncodeState),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\tcow_http2:data(1, nofin, <<0:10000/unit:8>>),\n\t\tcow_http2:headers(1, nofin, TrailersBlock)\n\t]),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% We reject all invalid HEADERS with a connection error because\n%% we do not want to waste resources decoding them.\nreject_trailers_nofin_continuation(Config) ->\n\tdoc(\"Trailing HEADERS frames received without the END_STREAM flag \"\n\t\t\"set must be rejected with a PROTOCOL_ERROR connection error. \"\n\t\t\"(RFC7540 8.1, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a request containing DATA and trailing HEADERS and CONTINUATION frames.\n\t%% The trailing HEADERS does not have the END_STREAM flag set.\n\t{HeadersBlock, EncodeState} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>},\n\t\t{<<\"trailer\">>, <<\"x-checksum\">>}\n\t]),\n\t{TrailersBlock, _} = cow_hpack:encode([\n\t\t{<<\"x-checksum\">>, <<\"md5:4cc909a007407f3706399b6496babec3\">>}\n\t], EncodeState),\n\tLen = iolist_size(TrailersBlock),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\tcow_http2:data(1, nofin, <<0:10000/unit:8>>),\n\t\t<<0:24, 1:8, 0:9, 1:31>>,\n\t\t<<Len:24, 9:8, 0:5, 1:1, 0:3, 1:31>>,\n\t\tTrailersBlock\n\t]),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nheaders_informational_nofin(Config) ->\n\tdoc(\"Informational HEADERS frames must not have the END_STREAM flag set. (RFC7540 8.1)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame on an idle stream.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>},\n\t\t{<<\"expect\">>, <<\"100-continue\">>},\n\t\t{<<\"content-length\">>, <<\"1000000\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),\n\t%% Receive an informational HEADERS frame without the END_STREAM flag.\n\t{ok, << Len:24, 1:8, 0:5, 1:1, 0:2, _:32 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len, 6000),\n\t%% Confirm it has a 100 status code.\n\t{RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),\n\t{_, <<\"100\">>} = lists:keyfind(<<\":status\">>, 1, RespHeaders),\n\tok.\n\n%% @todo This one is interesting to implement because Cowboy DOES this.\n%   A server can\n%   send a complete response prior to the client sending an entire\n%   request if the response does not depend on any portion of the request\n%   that has not been sent and received.  When this is true, a server MAY\n%   request that the client abort transmission of a request without error\n%   by sending a RST_STREAM with an error code of NO_ERROR after sending\n%   a complete response (i.e., a frame with the END_STREAM flag).\n\nheaders_reject_uppercase_header_name(Config) ->\n\tdoc(\"Requests containing uppercase header names must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a uppercase header name.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"HELLO\">>, <<\"world\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_response_pseudo_headers(Config) ->\n\tdoc(\"Requests containing response pseudo-headers must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.1, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a response pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\":status\">>, <<\"200\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_unknown_pseudo_headers(Config) ->\n\tdoc(\"Requests containing unknown pseudo-headers must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.1, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with an unknown pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\":upgrade\">>, <<\"websocket\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_pseudo_headers_in_trailers(Config) ->\n\tdoc(\"Requests containing pseudo-headers in trailers must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.1, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a request containing DATA and trailing HEADERS frames.\n\t%% The trailing HEADERS contains pseudo-headers.\n\t{HeadersBlock, EncodeState} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>},\n\t\t{<<\"trailer\">>, <<\"x-checksum\">>}\n\t]),\n\t{TrailersBlock, _} = cow_hpack:encode([\n\t\t{<<\"x-checksum\">>, <<\"md5:4cc909a007407f3706399b6496babec3\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t], EncodeState),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\tcow_http2:data(1, nofin, <<0:10000/unit:8>>),\n\t\tcow_http2:headers(1, fin, TrailersBlock)\n\t]),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_pseudo_headers_after_regular_headers(Config) ->\n\tdoc(\"Requests containing pseudo-headers after regular headers must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.1, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a pseudo-header after regular headers.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"content-length\">>, <<\"0\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_connection_header(Config) ->\n\tdoc(\"Requests containing a connection header must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a connection header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"connection\">>, <<\"close\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_keep_alive_header(Config) ->\n\tdoc(\"Requests containing a keep-alive header must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a keep-alive header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"keep-alive\">>, <<\"timeout=5, max=1000\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_proxy_authenticate_header(Config) ->\n\tdoc(\"Requests containing a connection header must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a proxy-authenticate header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"proxy-authenticate\">>, <<\"Basic\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_proxy_authorization_header(Config) ->\n\tdoc(\"Requests containing a connection header must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a proxy-authorization header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"proxy-authorization\">>, <<\"Basic YWxhZGRpbjpvcGVuc2VzYW1l\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_transfer_encoding_header(Config) ->\n\tdoc(\"Requests containing a connection header must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a transfer-encoding header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"transfer-encoding\">>, <<\"chunked\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_upgrade_header(Config) ->\n\tdoc(\"Requests containing a connection header must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a upgrade header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"upgrade\">>, <<\"websocket\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\naccept_te_header_value_trailers(Config) ->\n\tdoc(\"Requests containing a TE header with a value of \\\"trailers\\\" \"\n\t\t\"must be accepted. (RFC7540 8.1.2.2)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a TE header with value \"trailers\".\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"te\">>, <<\"trailers\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a response.\n\t{ok, << _:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\tok.\n\nreject_te_header_other_values(Config) ->\n\tdoc(\"Requests containing a TE header with a value other than \\\"trailers\\\" must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a TE header with a different value.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"te\">>, <<\"trailers, deflate;q=0.5\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n%% (RFC7540 8.1.2.2)\n%   This means that an intermediary transforming an HTTP/1.x message to\n%   HTTP/2 will need to remove any header fields nominated by the\n%   Connection header field, along with the Connection header field\n%   itself.  Such intermediaries SHOULD also remove other connection-\n%   specific header fields, such as Keep-Alive, Proxy-Connection,\n%   Transfer-Encoding, and Upgrade, even if they are not nominated by the\n%   Connection header field.\n\nresponse_dont_send_header_in_connection(Config) ->\n\tdoc(\"Intermediaries must remove HTTP/1.1 connection headers when \"\n\t\t\"transforming an HTTP/1.1 messages to HTTP/2. The server must \"\n\t\t\"not send them either. All headers listed in the connection \"\n\t\t\"header must be removed. (RFC7540 8.1.2.2)\"),\n\tdo_response_dont_send_http11_header(Config, <<\"custom-header\">>).\n\nresponse_dont_send_connection_header(Config) ->\n\tdoc(\"Intermediaries must remove HTTP/1.1 connection headers when \"\n\t\t\"transforming an HTTP/1.1 messages to HTTP/2. The server must \"\n\t\t\"not send them either. The connection header must be removed. (RFC7540 8.1.2.2)\"),\n\tdo_response_dont_send_http11_header(Config, <<\"connection\">>).\n\nresponse_dont_send_keep_alive_header(Config) ->\n\tdoc(\"Intermediaries must remove HTTP/1.1 connection headers when \"\n\t\t\"transforming an HTTP/1.1 messages to HTTP/2. The server must \"\n\t\t\"not send them either. The keep-alive header must be removed \"\n\t\t\"even if not listed in the connection header. (RFC7540 8.1.2.2)\"),\n\tdo_response_dont_send_http11_header(Config, <<\"keep-alive\">>).\n\nresponse_dont_send_proxy_connection_header(Config) ->\n\tdoc(\"Intermediaries must remove HTTP/1.1 connection headers when \"\n\t\t\"transforming an HTTP/1.1 messages to HTTP/2. The server must \"\n\t\t\"not send them either. The proxy-connection header must be removed \"\n\t\t\"even if not listed in the connection header. (RFC7540 8.1.2.2)\"),\n\tdo_response_dont_send_http11_header(Config, <<\"proxy-connection\">>).\n\nresponse_dont_send_transfer_encoding_header(Config) ->\n\tdoc(\"Intermediaries must remove HTTP/1.1 connection headers when \"\n\t\t\"transforming an HTTP/1.1 messages to HTTP/2. The server must \"\n\t\t\"not send them either. The transfer-encoding header must be removed \"\n\t\t\"even if not listed in the connection header. (RFC7540 8.1.2.2)\"),\n\tdo_response_dont_send_http11_header(Config, <<\"transfer-encoding\">>).\n\nresponse_dont_send_upgrade_header(Config) ->\n\tdoc(\"Intermediaries must remove HTTP/1.1 connection headers when \"\n\t\t\"transforming an HTTP/1.1 messages to HTTP/2. The server must \"\n\t\t\"not send them either. The upgrade header must be removed \"\n\t\t\"even if not listed in the connection header. (RFC7540 8.1.2.2)\"),\n\tdo_response_dont_send_http11_header(Config, <<\"upgrade\">>).\n\ndo_response_dont_send_http11_header(Config, Name) ->\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/set_resp_headers_http11\"),\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\tfalse = lists:keyfind(Name, 1, Headers),\n\tok.\n\nreject_userinfo(Config) ->\n\tdoc(\"An authority containing a userinfo component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a userinfo authority component.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"user@localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n%% (RFC7540 8.1.2.3)\n%      To ensure that the HTTP/1.1 request line can be reproduced\n%      accurately, this pseudo-header field MUST be omitted when\n%      translating from an HTTP/1.1 request that has a request target in\n%      origin or asterisk form (see [RFC7230], Section 5.3).  Clients\n%      that generate HTTP/2 requests directly SHOULD use the \":authority\"\n%      pseudo-header field instead of the Host header field.  An\n%      intermediary that converts an HTTP/2 request to HTTP/1.1 MUST\n%      create a Host header field if one is not present in a request by\n%      copying the value of the \":authority\" pseudo-header field.\n\nreject_empty_path(Config) ->\n\tdoc(\"A request containing an empty path component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with an empty path component.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<>>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_missing_pseudo_header_method(Config) ->\n\tdoc(\"A request without a method component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame without a :method pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_many_pseudo_header_method(Config) ->\n\tdoc(\"A request containing more than one method component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with more than one :method pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_missing_pseudo_header_scheme(Config) ->\n\tdoc(\"A request without a scheme component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame without a :scheme pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_many_pseudo_header_scheme(Config) ->\n\tdoc(\"A request containing more than one scheme component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with more than one :scheme pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_missing_pseudo_header_authority(Config) ->\n\tdoc(\"A request without an authority or host component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame without an :authority pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\naccept_host_header_on_missing_pseudo_header_authority(Config) ->\n\tdoc(\"A request without an authority but with a host header must be accepted. \"\n\t\t\"(RFC7540 8.1.2.3, RFC7540 8.1.3)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with host header and without an :authority pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"host\">>, <<\"localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a 200 response.\n\t{ok, << Len:24, 1:8, _:8, _:32 >>} = gen_tcp:recv(Socket, 9, 6000),\n\t{ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len, 6000),\n\t{RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),\n\t{_, <<\"200\">>} = lists:keyfind(<<\":status\">>, 1, RespHeaders),\n\tok.\n\n%% When both :authority and host headers are received, the current behavior\n%% is to favor :authority and ignore the host header. The specification does\n%% not describe the correct behavior to follow in that case.\n%% @todo The HTTP/3 spec says both values must be identical and non-empty.\n\nreject_many_pseudo_header_authority(Config) ->\n\tdoc(\"A request containing more than one authority component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with more than one :authority pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_missing_pseudo_header_path(Config) ->\n\tdoc(\"A request without a path component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame without a :path pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>} %% @todo Correct port number.\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_many_pseudo_header_path(Config) ->\n\tdoc(\"A request containing more than one path component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with more than one :path pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n%% (RFC7540 8.1.2.4)\n%   For HTTP/2 responses, a single \":status\" pseudo-header field is\n%   defined that carries the HTTP status code field (see [RFC7231],\n%   Section 6).  This pseudo-header field MUST be included in all\n%   responses; otherwise, the response is malformed (Section 8.1.2.6).\n\n%% (RFC7540 8.1.2.5)\n%   To allow for better compression efficiency, the Cookie header field\n%   MAY be split into separate header fields, each with one or more\n%   cookie-pairs.  If there are multiple Cookie header fields after\n%   decompression, these MUST be concatenated into a single octet string\n%   using the two-octet delimiter of 0x3B, 0x20 (the ASCII string \"; \")\n%   before being passed into a non-HTTP/2 context, such as an HTTP/1.1\n%   connection, or a generic HTTP server application.\n\nreject_data_size_smaller_than_content_length(Config) ->\n\tdoc(\"Requests that have a content-length header whose value does not \"\n\t\t\"match the total length of the DATA frames must be rejected with \"\n\t\t\"a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a content-length header different\n\t%% than the sum of the DATA frame sizes.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>},\n\t\t{<<\"content-length\">>, <<\"12\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\tcow_http2:data(1, fin, <<\"Hello!\">>)\n\t]),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_data_size_larger_than_content_length(Config) ->\n\tdoc(\"Requests that have a content-length header whose value does not \"\n\t\t\"match the total length of the DATA frames must be rejected with \"\n\t\t\"a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a content-length header different\n\t%% than the sum of the DATA frame sizes.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>},\n\t\t{<<\"content-length\">>, <<\"12\">>}\n\t]),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\tcow_http2:data(1, nofin, <<\"Hello! World! Universe!\">>),\n\t\tcow_http2:data(1, fin, <<\"Multiverse!\">>)\n\t]),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_content_length_without_data(Config) ->\n\tdoc(\"Requests that have a content-length header whose value does not \"\n\t\t\"match the total length of the DATA frames must be rejected with \"\n\t\t\"a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a content-length header different\n\t%% than the sum of the DATA frame sizes.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>},\n\t\t{<<\"content-length\">>, <<\"12\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_data_size_different_than_content_length_with_trailers(Config) ->\n\tdoc(\"Requests that have a content-length header whose value does not \"\n\t\t\"match the total length of the DATA frames must be rejected with \"\n\t\t\"a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with a content-length header different\n\t%% than the sum of the DATA frame sizes.\n\t{HeadersBlock, EncodeState} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>},\n\t\t{<<\"content-length\">>, <<\"12\">>},\n\t\t{<<\"trailer\">>, <<\"x-checksum\">>}\n\t]),\n\t{TrailersBlock, _} = cow_hpack:encode([\n\t\t{<<\"x-checksum\">>, <<\"md5:4cc909a007407f3706399b6496babec3\">>}\n\t], EncodeState),\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:headers(1, nofin, HeadersBlock),\n\t\tcow_http2:data(1, nofin, <<\"Hello!\">>),\n\t\tcow_http2:headers(1, fin, TrailersBlock)\n\t]),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_duplicate_content_length_header(Config) ->\n\tdoc(\"A request with duplicate content-length headers must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (RFC7230 3.3.2, RFC7540 8.1.2.6)\"),\n\t{ok, Socket} = do_handshake(Config),\n\t%% Send a HEADERS frame with more than one content-length header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"12\">>},\n\t\t{<<\"content-length\">>, <<\"12\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n%   Intermediaries that process HTTP requests or responses (i.e., any\n%   intermediary not acting as a tunnel) MUST NOT forward a malformed\n%   request or response.  Malformed requests or responses that are\n%   detected MUST be treated as a stream error (Section 5.4.2) of type\n%   PROTOCOL_ERROR.\n%\n%   For malformed requests, a server MAY send an HTTP response prior to\n%   closing or resetting the stream.  Clients MUST NOT accept a malformed\n%   response.  Note that these requirements are intended to protect\n%   against several types of common attacks against HTTP; they are\n%   deliberately strict because being permissive can expose\n%   implementations to these vulnerabilities.\n\n%% @todo It migh be worth reproducing the good examples. (RFC7540 8.1.3)\n\n%% (RFC7540 8.1.4)\n%   A server MUST NOT indicate that a stream has not been processed\n%   unless it can guarantee that fact.  If frames that are on a stream\n%   are passed to the application layer for any stream, then\n%   REFUSED_STREAM MUST NOT be used for that stream, and a GOAWAY frame\n%   MUST include a stream identifier that is greater than or equal to the\n%   given stream identifier.\n\n%% (RFC7540 8.2)\n%   Promised requests MUST be cacheable (see [RFC7231], Section 4.2.3),\n%   MUST be safe (see [RFC7231], Section 4.2.1), and MUST NOT include a\n%   request body.\n%\n%   The server MUST include a value in the \":authority\" pseudo-header\n%   field for which the server is authoritative (see Section 10.1).\n%\n%   A client cannot push.  Thus, servers MUST treat the receipt of a\n%   PUSH_PROMISE frame as a connection error (Section 5.4.1) of type\n%   PROTOCOL_ERROR.\n\npush_has_no_request_body(Config) ->\n\tdoc(\"PUSH_PROMISE frames include the complete set of request headers \"\n\t\t\"and the request can never include a body. (RFC7540 8.2.1)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/push/read_body\"),\n\t{push, PushRef, <<\"GET\">>, _, _} = gun:await(ConnPid, Ref),\n\t{response, fin, 200, _} = gun:await(ConnPid, Ref),\n\t%% We should not get a body in the pushed resource\n\t%% since there was no body in the request.\n\t{response, fin, 200, _} = gun:await(ConnPid, PushRef),\n\tok.\n\n%% (RFC7540 8.2.1)\n%   The header fields in PUSH_PROMISE and any subsequent CONTINUATION\n%   frames MUST be a valid and complete set of request header fields\n%   (Section 8.1.2.3).  The server MUST include a method in the \":method\"\n%   pseudo-header field that is safe and cacheable.  If a client receives\n%   a PUSH_PROMISE that does not include a complete and valid set of\n%   header fields or the \":method\" pseudo-header field identifies a\n%   method that is not safe, it MUST respond with a stream error\n%   (Section 5.4.2) of type PROTOCOL_ERROR.\n%\n%% @todo This probably should be documented.\n%   The server SHOULD send PUSH_PROMISE (Section 6.6) frames prior to\n%   sending any frames that reference the promised responses.  This\n%   avoids a race where clients issue requests prior to receiving any\n%   PUSH_PROMISE frames.\n%\n%   PUSH_PROMISE frames MUST NOT be sent by the client.\n%\n%   PUSH_PROMISE frames can be sent by the server in response to any\n%   client-initiated stream, but the stream MUST be in either the \"open\"\n%   or \"half-closed (remote)\" state with respect to the server.\n%   PUSH_PROMISE frames are interspersed with the frames that comprise a\n%   response, though they cannot be interspersed with HEADERS and\n%   CONTINUATION frames that comprise a single header block.\n\n%% (RFC7540 8.2.2)\n%   If the client determines, for any reason, that it does not wish to\n%   receive the pushed response from the server or if the server takes\n%   too long to begin sending the promised response, the client can send\n%   a RST_STREAM frame, using either the CANCEL or REFUSED_STREAM code\n%   and referencing the pushed stream's identifier.\n%\n%   A client can use the SETTINGS_MAX_CONCURRENT_STREAMS setting to limit\n%   the number of responses that can be concurrently pushed by a server.\n%   Advertising a SETTINGS_MAX_CONCURRENT_STREAMS value of zero disables\n%   server push by preventing the server from creating the necessary\n%   streams.  This does not prohibit a server from sending PUSH_PROMISE\n%   frames; clients need to reset any promised streams that are not\n%   wanted.\n\n%% @todo Implement CONNECT. (RFC7540 8.3)\n\nstatus_code_421(Config) ->\n\tdoc(\"The 421 Misdirected Request status code can be sent. (RFC7540 9.1.2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/reply2/421\"),\n\t{response, fin, 421, _} = gun:await(ConnPid, Ref),\n\tok.\n\n%% @todo Review (RFC7540 9.2, 9.2.1, 9.2.2) TLS 1.2 usage.\n%% We probably want different ways to enforce these to simplify the life\n%% of users. A function cowboy:start_h2_tls could do the same as start_tls\n%% but with the security requirements of HTTP/2 enforced. Another way is to\n%% have an option at the establishment of the connection that checks that\n%% the security of the connection is adequate.\n\n%% (RFC7540 10.3)\n%   The HTTP/2 header field encoding allows the expression of names that\n%   are not valid field names in the Internet Message Syntax used by\n%   HTTP/1.1.  Requests or responses containing invalid header field\n%   names MUST be treated as malformed (Section 8.1.2.6).\n%\n%   Similarly, HTTP/2 allows header field values that are not valid.\n%   While most of the values that can be encoded will not alter header\n%   field parsing, carriage return (CR, ASCII 0xd), line feed (LF, ASCII\n%   0xa), and the zero character (NUL, ASCII 0x0) might be exploited by\n%   an attacker if they are translated verbatim.  Any request or response\n%   that contains a character not permitted in a header field value MUST\n%   be treated as malformed (Section 8.1.2.6).  Valid characters are\n%   defined by the \"field-content\" ABNF rule in Section 3.2 of [RFC7230].\n\n%% (RFC7540 10.5) Denial-of-Service Considerations\n%   An endpoint that doesn't monitor this behavior exposes itself to a\n%   risk of denial-of-service attack.  Implementations SHOULD track the\n%   use of these features and set limits on their use.  An endpoint MAY\n%   treat activity that is suspicious as a connection error\n%   (Section 5.4.1) of type ENHANCE_YOUR_CALM.\n\n%% (RFC7540 10.5.1)\n%   A server that receives a larger header block than it is willing to\n%   handle can send an HTTP 431 (Request Header Fields Too Large) status\n%   code [RFC6585].  A client can discard responses that it cannot\n%   process.  The header block MUST be processed to ensure a consistent\n%   connection state, unless the connection is closed.\n\n%% @todo Implement CONNECT and limit the number of CONNECT streams (RFC7540 10.5.2).\n\n%% @todo This probably should be documented. (RFC7540 10.6)\n%   Implementations communicating on a secure channel MUST NOT compress\n%   content that includes both confidential and attacker-controlled data\n%   unless separate compression dictionaries are used for each source of\n%   data.  Compression MUST NOT be used if the source of data cannot be\n%   reliably determined.  Generic stream compression, such as that\n%   provided by TLS, MUST NOT be used with HTTP/2 (see Section 9.2).\n\n%% (RFC7540 A)\n%   An HTTP/2 implementation MAY treat the negotiation of any of the\n%   following cipher suites with TLS 1.2 as a connection error\n%   (Section 5.4.1) of type INADEQUATE_SECURITY.\n"
  },
  {
    "path": "test/rfc8297_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(rfc8297_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{\"[...]\", [\n\t\t{\"/resp/:key[/:arg]\", resp_h, []}\n\t]}]).\n\nstatus_code_103(Config) ->\n\tdoc(\"The 103 Early Hints status code can be sent. (RFC8297 2)\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/resp/inform2/103\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>}\n\t]),\n\t{inform, 103, []} = gun:await(ConnPid, Ref),\n\tok.\n"
  },
  {
    "path": "test/rfc8441_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(rfc8441_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n\nall() -> [{group, enabled}].\n\ngroups() ->\n\tTests = ct_helper:all(?MODULE),\n\t[{enabled, [parallel], Tests}].\n\ninit_per_group(Name = enabled, Config) ->\n\tcowboy_test:init_http(Name, #{\n\t\tenable_connect_protocol => true,\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))}\n\t}, Config).\n\nend_per_group(Name, _) ->\n\tok = cowboy:stop_listener(Name).\n\ninit_routes(_) -> [\n\t{\"localhost\", [\n\t\t{\"/ws\", ws_echo, []}\n\t]}\n].\n\n%% Do a prior knowledge handshake.\ndo_handshake(Config) ->\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t%% Send a valid preface.\n\tok = gen_tcp:send(Socket, [\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\", cow_http2:settings(#{})]),\n\t%% Receive the server preface.\n\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t{ok, << 4:8, 0:40, SettingsPayload:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\tSettings = cow_http2:parse_settings_payload(SettingsPayload),\n\t%% Send the SETTINGS ack.\n\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t%% Receive the SETTINGS ack.\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, Socket, Settings}.\n\n% The SETTINGS_ENABLE_CONNECT_PROTOCOL SETTINGS Parameter.\n\n% The new parameter name is SETTINGS_ENABLE_CONNECT_PROTOCOL.  The\n% value of the parameter MUST be 0 or 1.\n\n%    Upon receipt of SETTINGS_ENABLE_CONNECT_PROTOCOL with a value of 1 a\n%    client MAY use the Extended CONNECT definition of this document when\n%    creating new streams.  Receipt of this parameter by a server does not\n%    have any impact.\n%% @todo ignore_client_enable_setting(Config) ->\n\n% A sender MUST NOT send a SETTINGS_ENABLE_CONNECT_PROTOCOL parameter\n% with the value of 0 after previously sending a value of 1.\n\nreject_handshake_when_disabled(Config0) ->\n\tdoc(\"Extended CONNECT requests MUST be rejected with a \"\n\t\t\"PROTOCOL_ERROR stream error when enable_connect_protocol=false. (draft-01 3)\"),\n\tConfig = cowboy_test:init_http(disabled, #{\n\t\tenable_connect_protocol => false,\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))}\n\t}, Config0),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 0.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\tcase Settings of\n\t\t#{enable_connect_protocol := false} -> ok;\n\t\t_ when map_size(Settings) =:= 0 -> ok\n\tend,\n\t%% Send a CONNECT :protocol request to upgrade the stream to Websocket.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_handshake_disabled_by_default(Config0) ->\n\tdoc(\"Extended CONNECT requests MUST be rejected with a \"\n\t\t\"PROTOCOL_ERROR stream error with default enable_connect_protocol. (draft-01 3)\"),\n\tConfig = cowboy_test:init_http(disabled_by_default, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))}\n\t}, Config0),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 0.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\tcase Settings of\n\t\t#{enable_connect_protocol := false} -> ok;\n\t\t_ when map_size(Settings) =:= 0 -> ok\n\tend,\n\t%% Send a CONNECT :protocol request to upgrade the stream to Websocket.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n% The Extended CONNECT Method.\n\n%% @todo Refer to RFC9110 7.8 about the case insensitive comparison.\naccept_uppercase_pseudo_header_protocol(Config) ->\n\tdoc(\"The :protocol pseudo header is case insensitive. (draft-01 4)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send a CONNECT :protocol request to upgrade the stream to Websocket.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"WEBSOCKET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a 200 response.\n\t{ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000),\n\t{RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),\n\t{_, <<\"200\">>} = lists:keyfind(<<\":status\">>, 1, RespHeaders),\n\tok.\n\nreject_many_pseudo_header_protocol(Config) ->\n\tdoc(\"An extended CONNECT request containing more than one protocol component \"\n\t\t\"must be rejected with a PROTOCOL_ERROR stream error. (draft-01 4, RFC7540 8.1.2.6)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request with more than one :protocol pseudo-header.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":protocol\">>, <<\"mqtt\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_unknown_pseudo_header_protocol(Config) ->\n\t%% @todo This probably shouldn't send 400 but 501 instead based on RFC 9220.\n\tdoc(\"An extended CONNECT request with an unknown protocol must be rejected \"\n\t\t\"with a 400 error. (draft-01 4)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request with an unknown :protocol pseudo-header.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"mqtt\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a 400 response.\n\t{ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000),\n\t{RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),\n\t{_, <<\"501\">>} = lists:keyfind(<<\":status\">>, 1, RespHeaders),\n\tok.\n\nreject_invalid_pseudo_header_protocol(Config) ->\n\t%% @todo This probably shouldn't send 400 but 501 instead based on RFC 9220.\n\tdoc(\"An extended CONNECT request with an invalid protocol must be rejected \"\n\t\t\"with a 400 error. (draft-01 4)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request with an invalid :protocol pseudo-header.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket mqtt\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a 400 response.\n\t{ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000),\n\t{RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),\n\t{_, <<\"501\">>} = lists:keyfind(<<\":status\">>, 1, RespHeaders),\n\tok.\n\nreject_missing_pseudo_header_scheme(Config) ->\n\tdoc(\"An extended CONNECT request without a scheme component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (draft-01 4, RFC7540 8.1.2.6)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request without a :scheme pseudo-header.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_missing_pseudo_header_path(Config) ->\n\tdoc(\"An extended CONNECT request without a path component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (draft-01 4, RFC7540 8.1.2.6)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request without a :path pseudo-header.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n% On requests bearing the :protocol pseudo-header, the :authority\n% pseudo-header field is interpreted according to Section 8.1.2.3 of\n% [RFC7540] instead of Section 8.3 of [RFC7540].  In particular the\n% server MUST not make a new TCP connection to the host and port\n% indicated by the :authority.\n\nreject_missing_pseudo_header_authority(Config) ->\n\tdoc(\"An extended CONNECT request without an authority component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (draft-01 4, draft-01 5)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request without an :authority pseudo-header.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n% Using Extended CONNECT To Bootstrap The WebSocket Protocol.\n\nreject_missing_pseudo_header_protocol(Config) ->\n\tdoc(\"An extended CONNECT request without a protocol component must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (draft-01 4, RFC7540 8.1.2.6)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request without a :protocol pseudo-header.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n% The scheme of the Target URI [RFC7230] MUST be https for wss schemed\n% WebSockets and http for ws schemed WebSockets.  The websocket URI is\n% still used for proxy autoconfiguration.\n\nreject_connection_header(Config) ->\n\tdoc(\"An extended CONNECT request with a connection header must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (draft-01 5, RFC7540 8.1.2.6)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request with a connection header.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"connection\">>, <<\"upgrade\">>},\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nreject_upgrade_header(Config) ->\n\tdoc(\"An extended CONNECT request with a upgrade header must be rejected \"\n\t\t\"with a PROTOCOL_ERROR stream error. (draft-01 5, RFC7540 8.1.2.6)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request with a upgrade header.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"upgrade\">>, <<\"websocket\">>},\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\n%    After successfully processing the opening handshake the peers should\n%    proceed with The WebSocket Protocol [RFC6455] using the HTTP/2 stream\n%    from the CONNECT transaction as if it were the TCP connection\n%    referred to in [RFC6455].  The state of the WebSocket connection at\n%    this point is OPEN as defined by [RFC6455], Section 4.1.\n%% @todo I'm guessing we should test for things like RST_STREAM,\n%% closing the connection and others?\n\n% Examples.\n\n%% @todo Probably worth testing that we get the correct option\n%% over all different connection types (alpn, prior, upgrade).\naccept_handshake_when_enabled(Config) ->\n\tdoc(\"Confirm the example for Websocket over HTTP/2 works. (draft-01 5.1)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t{ok, Socket, Settings} = do_handshake(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send a CONNECT :protocol request to upgrade the stream to Websocket.\n\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t%% Receive a 200 response.\n\t{ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000),\n\t{RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),\n\t{_, <<\"200\">>} = lists:keyfind(<<\":status\">>, 1, RespHeaders),\n\t%% Masked text hello echoed back clear by the server.\n\tMask = 16#37fa213d,\n\tMaskedHello = ws_SUITE:do_mask(<<\"Hello\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, cow_http2:data(1, nofin,\n\t\t<<1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary>>)),\n\t%% Ignore expected WINDOW_UPDATE frames.\n\t{ok, <<4:24, 8:8, _:72>>} = gen_tcp:recv(Socket, 13, 1000),\n\t{ok, <<4:24, 8:8, _:72>>} = gen_tcp:recv(Socket, 13, 1000),\n\t{ok, <<Len2:24, _:8, _:8, _:32>>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, <<1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\">>} = gen_tcp:recv(Socket, Len2, 1000),\n\tok.\n\n%% Closing a Websocket stream.\n\n%    The HTTP/2 stream closure is also analagous to the TCP connection closure of\n% \t [RFC6455].  Orderly TCP level closures are represented as END_STREAM\n% \t ([RFC7540] Section 6.1) flags and RST exceptions are represented with\n% \t the RST_STREAM ([RFC7540] Section 6.4) frame with the CANCEL\n% \t ([RFC7540] Secion 7) error code.\n\n%% @todo client close frame with END_STREAM\n%% @todo server close frame with END_STREAM\n%% @todo client other frame with END_STREAM\n%% @todo server other frame with END_STREAM\n%% @todo client close connection\n"
  },
  {
    "path": "test/rfc9114_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(rfc9114_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n\n-ifdef(COWBOY_QUICER).\n\n-include_lib(\"quicer/include/quicer.hrl\").\n\nall() ->\n\t[{group, h3}].\n\ngroups() ->\n\t%% @todo Enable parallel tests but for this issues in the\n\t%% QUIC accept loop need to be figured out (can't connect\n\t%% concurrently somehow, no backlog?).\n\t[{h3, [], ct_helper:all(?MODULE)}].\n\ninit_per_group(Name = h3, Config) ->\n\tcowboy_test:init_http3(Name, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))}\n\t}, Config).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_routes(_) -> [\n\t{\"localhost\", [\n\t\t{\"/\", hello_h, []},\n\t\t{\"/echo/:key\", echo_h, []}\n\t]}\n].\n\n%% Starting HTTP/3 for \"https\" URIs.\n\nalpn(Config) ->\n\tdoc(\"Successful ALPN negotiation. (RFC9114 3.1)\"),\n\t{ok, Conn} = quicer:connect(\"localhost\", config(port, Config),\n\t\t#{alpn => [\"h3\"], verify => none}, 5000),\n\t{ok, <<\"h3\">>} = quicer:negotiated_protocol(Conn),\n\t%% To make sure the connection is fully established we wait\n\t%% to receive the SETTINGS frame on the control stream.\n\t{ok, _ControlRef, _Settings} = do_wait_settings(Conn),\n\tok.\n\nalpn_error(Config) ->\n\tdoc(\"Failed ALPN negotiation using the 'h2' token. (RFC9114 3.1)\"),\n\t{error, transport_down, #{status := alpn_neg_failure}}\n\t\t= quicer:connect(\"localhost\", config(port, Config),\n\t\t\t#{alpn => [\"h2\"], verify => none}, 5000),\n\tok.\n\n%% @todo 3.2. Connection Establishment\n%% After the QUIC connection is established, a SETTINGS frame MUST be sent by each endpoint as the initial frame of their respective HTTP control stream.\n\n%% @todo 3.3. Connection Reuse\n%% Servers are encouraged to maintain open HTTP/3 connections for as long as\n%possible but are permitted to terminate idle connections if necessary. When\n%either endpoint chooses to close the HTTP/3 connection, the terminating\n%endpoint SHOULD first send a GOAWAY frame (Section 5.2) so that both endpoints\n%can reliably determine whether previously sent frames have been processed and\n%gracefully complete or terminate any necessary remaining tasks.\n\n%% Frame format.\n\nreq_stream(Config) ->\n\tdoc(\"Complete lifecycle of a request stream. (RFC9114 4.1)\"),\n\t{ok, Conn} = quicer:connect(\"localhost\", config(port, Config),\n\t\t#{alpn => [\"h3\"], verify => none}, 5000),\n\t%% To make sure the connection is fully established we wait\n\t%% to receive the SETTINGS frame on the control stream.\n\t{ok, ControlRef, _Settings} = do_wait_settings(Conn),\n\t%% Send a request on a request stream.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% Receive the response.\n\t{ok, Data} = do_receive_data(StreamRef),\n\t{HLenEnc, HLenBits} = do_guess_int_encoding(Data),\n\t<<\n\t\t1, %% HEADERS frame.\n\t\tHLenEnc:2, HLen:HLenBits,\n\t\tEncodedResponse:HLen/bytes,\n\t\tRest/bits\n\t>> = Data,\n\t{ok, DecodedResponse, _DecData, _DecSt}\n\t\t= cow_qpack:decode_field_section(EncodedResponse, 0, cow_qpack:init(decoder)),\n\t#{\n\t\t<<\":status\">> := <<\"200\">>,\n\t\t<<\"content-length\">> := BodyLen\n\t} = maps:from_list(DecodedResponse),\n\t{DLenEnc, DLenBits} = do_guess_int_encoding(Rest),\n\t<<\n\t\t0, %% DATA frame.\n\t\tDLenEnc:2, DLen:DLenBits,\n\t\tBody:DLen/bytes\n\t>> = Rest,\n\t<<\"Hello world!\">> = Body,\n\tBodyLen = integer_to_binary(byte_size(Body)),\n\tok = do_wait_peer_send_shutdown(StreamRef),\n\tok = do_wait_stream_closed(StreamRef).\n\n%% @todo Same test as above but with content-length unset?\n\nreq_stream_two_requests(Config) ->\n\tdoc(\"Receipt of multiple requests on a single stream must \"\n\t\t\"be rejected with an H3_MESSAGE_ERROR stream error. \"\n\t\t\"(RFC9114 4.1, RFC9114 4.1.2)\"),\n\t{ok, Conn} = quicer:connect(\"localhost\", config(port, Config),\n\t\t#{alpn => [\"h3\"], verify => none}, 5000),\n\t%% To make sure the connection is fully established we wait\n\t%% to receive the SETTINGS frame on the control stream.\n\t{ok, ControlRef, _Settings} = do_wait_settings(Conn),\n\t%% Send two requests on a request stream.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest1, _EncData1, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedRequest2, _EncData2, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, EncSt0),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest1)),\n\t\tEncodedRequest1,\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest2)),\n\t\tEncodedRequest2\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = do_wait_stream_aborted(StreamRef),\n\tok.\n\nheaders_then_trailers(Config) ->\n\tdoc(\"Receipt of HEADERS followed by trailer HEADERS must be accepted. (RFC9114 4.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], 0, EncSt0),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers)),\n\t\tEncodedTrailers\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\nheaders_then_data_then_trailers(Config) ->\n\tdoc(\"Receipt of HEADERS followed by DATA followed by trailer HEADERS \"\n\t\t\"must be accepted. (RFC9114 4.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"13\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], 0, EncSt0),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello server!\">>,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers)),\n\t\tEncodedTrailers\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\ndata_then_headers(Config) ->\n\tdoc(\"Receipt of DATA before HEADERS must be rejected \"\n\t\t\"with an H3_FRAME_UNEXPECTED connection error. (RFC9114 4.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"13\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello server!\">>,\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\nheaders_then_trailers_then_data(Config) ->\n\tdoc(\"Receipt of DATA after trailer HEADERS must be rejected \"\n\t\t\"with an H3_FRAME_UNEXPECTED connection error. (RFC9114 4.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], 0, EncSt0),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers)),\n\t\tEncodedTrailers,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello server!\">>\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\nheaders_then_data_then_trailers_then_data(Config) ->\n\tdoc(\"Receipt of DATA after trailer HEADERS must be rejected \"\n\t\t\"with an H3_FRAME_UNEXPECTED connection error. (RFC9114 4.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"13\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], 0, EncSt0),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello server!\">>,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers)),\n\t\tEncodedTrailers,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello server!\">>\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\nheaders_then_data_then_trailers_then_trailers(Config) ->\n\tdoc(\"Receipt of DATA after trailer HEADERS must be rejected \"\n\t\t\"with an H3_FRAME_UNEXPECTED connection error. (RFC9114 4.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"13\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedTrailers1, _EncData2, EncSt1} = cow_qpack:encode_field_section([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], 0, EncSt0),\n\t{ok, EncodedTrailers2, _EncData3, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], 0, EncSt1),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello server!\">>,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers1)),\n\t\tEncodedTrailers1,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers2)),\n\t\tEncodedTrailers2\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\nunknown_then_headers(Config) ->\n\tdoc(\"Receipt of unknown frame followed by HEADERS \"\n\t\t\"must be accepted. (RFC9114 4.1, RFC9114 9)\"),\n\tunknown_then_headers(Config, do_unknown_frame_type(),\n\t\trand:bytes(rand:uniform(4096))).\n\nunknown_then_headers(Config, Type, Bytes) ->\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\tcow_http3:encode_int(Type), %% Unknown frame.\n\t\tcow_http3:encode_int(iolist_size(Bytes)),\n\t\tBytes,\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\nheaders_then_unknown(Config) ->\n\tdoc(\"Receipt of HEADERS followed by unknown frame \"\n\t\t\"must be accepted. (RFC9114 4.1, RFC9114 9)\"),\n\theaders_then_unknown(Config, do_unknown_frame_type(),\n\t\trand:bytes(rand:uniform(4096))).\n\nheaders_then_unknown(Config, Type, Bytes) ->\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\tcow_http3:encode_int(Type), %% Unknown frame.\n\t\tcow_http3:encode_int(iolist_size(Bytes)),\n\t\tBytes\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\nheaders_then_data_then_unknown(Config) ->\n\tdoc(\"Receipt of HEADERS followed by DATA followed by unknown frame \"\n\t\t\"must be accepted. (RFC9114 4.1, RFC9114 9)\"),\n\theaders_then_data_then_unknown(Config, do_unknown_frame_type(),\n\t\trand:bytes(rand:uniform(4096))).\n\nheaders_then_data_then_unknown(Config, Type, Bytes) ->\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"13\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello server!\">>,\n\t\tcow_http3:encode_int(Type), %% Unknown frame.\n\t\tcow_http3:encode_int(iolist_size(Bytes)),\n\t\tBytes\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\nheaders_then_trailers_then_unknown(Config) ->\n\tdoc(\"Receipt of HEADERS followed by trailer HEADERS followed by unknown frame \"\n\t\t\"must be accepted. (RFC9114 4.1, RFC9114 9)\"),\n\theaders_then_data_then_unknown(Config, do_unknown_frame_type(),\n\t\trand:bytes(rand:uniform(4096))).\n\nheaders_then_trailers_then_unknown(Config, Type, Bytes) ->\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], 0, EncSt0),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers)),\n\t\tEncodedTrailers,\n\t\tcow_http3:encode_int(Type), %% Unknown frame.\n\t\tcow_http3:encode_int(iolist_size(Bytes)),\n\t\tBytes\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\nheaders_then_data_then_unknown_then_trailers(Config) ->\n\tdoc(\"Receipt of HEADERS followed by DATA followed by \"\n\t\t\"unknown frame followed by trailer HEADERS \"\n\t\t\"must be accepted. (RFC9114 4.1, RFC9114 9)\"),\n\theaders_then_data_then_unknown_then_trailers(Config,\n\t\tdo_unknown_frame_type(), rand:bytes(rand:uniform(4096))).\n\nheaders_then_data_then_unknown_then_trailers(Config, Type, Bytes) ->\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"13\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], 0, EncSt0),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello server!\">>,\n\t\tcow_http3:encode_int(Type), %% Unknown frame.\n\t\tcow_http3:encode_int(iolist_size(Bytes)),\n\t\tBytes,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers)),\n\t\tEncodedTrailers\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\nheaders_then_data_then_unknown_then_data(Config) ->\n\tdoc(\"Receipt of HEADERS followed by DATA followed by \"\n\t\t\"unknown frame followed by DATA \"\n\t\t\"must be accepted. (RFC9114 4.1, RFC9114 9)\"),\n\theaders_then_data_then_unknown_then_data(Config,\n\t\tdo_unknown_frame_type(), rand:bytes(rand:uniform(4096))).\n\nheaders_then_data_then_unknown_then_data(Config, Type, Bytes) ->\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"13\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(6),\n\t\t<<\"Hello \">>,\n\t\tcow_http3:encode_int(Type), %% Unknown frame.\n\t\tcow_http3:encode_int(iolist_size(Bytes)),\n\t\tBytes,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(7),\n\t\t<<\"server!\">>\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\nheaders_then_data_then_trailers_then_unknown(Config) ->\n\tdoc(\"Receipt of HEADERS followed by DATA followed by \"\n\t\t\"trailer HEADERS followed by unknown frame \"\n\t\t\"must be accepted. (RFC9114 4.1, RFC9114 9)\"),\n\theaders_then_data_then_trailers_then_unknown(Config,\n\t\tdo_unknown_frame_type(), rand:bytes(rand:uniform(4096))).\n\nheaders_then_data_then_trailers_then_unknown(Config, Type, Bytes) ->\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"13\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], 0, EncSt0),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello server!\">>,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers)),\n\t\tEncodedTrailers,\n\t\tcow_http3:encode_int(Type), %% Unknown frame.\n\t\tcow_http3:encode_int(iolist_size(Bytes)),\n\t\tBytes\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\ndo_unknown_frame_type() ->\n\tType = rand:uniform(4611686018427387904) - 1,\n\t%% Retry if we get a value that's specified.\n\tcase lists:member(Type, [\n\t\t16#0, 16#1, 16#3, 16#4, 16#5, 16#7, 16#d, %% HTTP/3 core frame types.\n\t\t16#2, 16#6, 16#8, 16#9 %% HTTP/3 reserved frame types that must be rejected.\n\t]) of\n\t\ttrue -> do_unknown_frame_type();\n\t\tfalse -> Type\n\tend.\n\nreserved_then_headers(Config) ->\n\tdoc(\"Receipt of reserved frame followed by HEADERS \"\n\t\t\"must be accepted when the reserved frame type is \"\n\t\t\"of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)\"),\n\tunknown_then_headers(Config, do_reserved_type(),\n\t\trand:bytes(rand:uniform(4096))).\n\nheaders_then_reserved(Config) ->\n\tdoc(\"Receipt of HEADERS followed by reserved frame \"\n\t\t\"must be accepted when the reserved frame type is \"\n\t\t\"of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)\"),\n\theaders_then_unknown(Config, do_reserved_type(),\n\t\trand:bytes(rand:uniform(4096))).\n\nheaders_then_data_then_reserved(Config) ->\n\tdoc(\"Receipt of HEADERS followed by DATA followed by reserved frame \"\n\t\t\"must be accepted when the reserved frame type is \"\n\t\t\"of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)\"),\n\theaders_then_data_then_unknown(Config, do_reserved_type(),\n\t\trand:bytes(rand:uniform(4096))).\n\nheaders_then_trailers_then_reserved(Config) ->\n\tdoc(\"Receipt of HEADERS followed by trailer HEADERS followed by reserved frame \"\n\t\t\"must be accepted when the reserved frame type is \"\n\t\t\"of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)\"),\n\theaders_then_trailers_then_unknown(Config, do_reserved_type(),\n\t\trand:bytes(rand:uniform(4096))).\n\nheaders_then_data_then_reserved_then_trailers(Config) ->\n\tdoc(\"Receipt of HEADERS followed by DATA followed by \"\n\t\t\"reserved frame followed by trailer HEADERS \"\n\t\t\"must be accepted when the reserved frame type is \"\n\t\t\"of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)\"),\n\theaders_then_data_then_unknown_then_trailers(Config,\n\t\tdo_reserved_type(), rand:bytes(rand:uniform(4096))).\n\nheaders_then_data_then_reserved_then_data(Config) ->\n\tdoc(\"Receipt of HEADERS followed by DATA followed by \"\n\t\t\"reserved frame followed by DATA \"\n\t\t\"must be accepted when the reserved frame type is \"\n\t\t\"of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)\"),\n\theaders_then_data_then_unknown_then_data(Config,\n\t\tdo_reserved_type(), rand:bytes(rand:uniform(4096))).\n\nheaders_then_data_then_trailers_then_reserved(Config) ->\n\tdoc(\"Receipt of HEADERS followed by DATA followed by \"\n\t\t\"trailer HEADERS followed by reserved frame \"\n\t\t\"must be accepted when the reserved frame type is \"\n\t\t\"of the format 0x1f * N + 0x21. (RFC9114 4.1, RFC9114 7.2.8)\"),\n\theaders_then_data_then_trailers_then_unknown(Config,\n\t\tdo_reserved_type(), rand:bytes(rand:uniform(4096))).\n\nreject_transfer_encoding_header_with_body(Config) ->\n\tdoc(\"Requests containing a transfer-encoding header must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.1, RFC9114 4.1.2, RFC9114 4.2)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, _EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"transfer-encoding\">>, <<\"chunked\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(24),\n\t\t<<\"13\\r\\nHello server!\\r\\n0\\r\\n\\r\\n\">>\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = do_wait_stream_aborted(StreamRef),\n\tok.\n\n%% 4. Expressing HTTP Semantics in HTTP/3\n%% 4.1. HTTP Message Framing\n\n%% An HTTP request/response exchange fully consumes a client-initiated\n%bidirectional QUIC stream. After sending a request, a client MUST close the\n%stream for sending. Unless using the CONNECT method (see Section 4.4), clients\n%MUST NOT make stream closure dependent on receiving a response to their\n%request. After sending a final response, the server MUST close the stream for\n%sending. At this point, the QUIC stream is fully closed.\n%% @todo What to do with clients that DON'T close the stream\n%%       for sending after the request is sent?\n\n%% If a client-initiated stream terminates without enough of the HTTP message\n%to provide a complete response, the server SHOULD abort its response stream\n%with the error code H3_REQUEST_INCOMPLETE.\n%% @todo difficult!!\n\n%% When the server does not need to receive the remainder of the request, it\n%MAY abort reading the request stream, send a complete response, and cleanly\n%close the sending part of the stream. The error code H3_NO_ERROR SHOULD be\n%used when requesting that the client stop sending on the request stream.\n%% @todo read_body related; h2 has this behavior but there is no corresponding test\n\n%% 4.1.1. Request Cancellation and Rejection\n\n%% When possible, it is RECOMMENDED that servers send an HTTP response with an\n%appropriate status code rather than cancelling a request it has already begun\n%processing.\n\n%% Implementations SHOULD cancel requests by abruptly terminating any\n%directions of a stream that are still open. To do so, an implementation resets\n%the sending parts of streams and aborts reading on the receiving parts of\n%streams; see Section 2.4 of [QUIC-TRANSPORT].\n\n%% When the server cancels a request without performing any application\n%processing, the request is considered \"rejected\". The server SHOULD abort its\n%response stream with the error code H3_REQUEST_REJECTED. In this context,\n%\"processed\" means that some data from the stream was passed to some higher\n%layer of software that might have taken some action as a result. The client\n%can treat requests rejected by the server as though they had never been sent\n%at all, thereby allowing them to be retried later.\n\n%% Servers MUST NOT use the H3_REQUEST_REJECTED error code for requests that\n%were partially or fully processed. When a server abandons a response after\n%partial processing, it SHOULD abort its response stream with the error code\n%H3_REQUEST_CANCELLED.\n%% @todo\n\n%% Client SHOULD use the error code H3_REQUEST_CANCELLED to cancel requests.\n%Upon receipt of this error code, a server MAY abruptly terminate the response\n%using the error code H3_REQUEST_REJECTED if no processing was performed.\n%Clients MUST NOT use the H3_REQUEST_REJECTED error code, except when a server\n%has requested closure of the request stream with this error code.\n%% @todo\n\n%4.1.2. Malformed Requests and Responses\n%A malformed request or response is one that is an otherwise valid sequence of\n%frames but is invalid due to:\n%\n%the presence of prohibited fields or pseudo-header fields,\n%% @todo reject_response_pseudo_headers\n%% @todo reject_unknown_pseudo_headers\n%% @todo reject_pseudo_headers_in_trailers\n\n%the absence of mandatory pseudo-header fields,\n%invalid values for pseudo-header fields,\n%pseudo-header fields after fields,\n%% @todo reject_pseudo_headers_after_regular_headers\n\n%an invalid sequence of HTTP messages,\n%the inclusion of invalid characters in field names or values.\n%\n%A request or response that is defined as having content when it contains a\n%Content-Length header field (Section 8.6 of [HTTP]) is malformed if the value\n%of the Content-Length header field does not equal the sum of the DATA frame\n%lengths received. A response that is defined as never having content, even\n%when a Content-Length is present, can have a non-zero Content-Length header\n%field even though no content is included in DATA frames.\n%\n%For malformed requests, a server MAY send an HTTP response indicating the\n%error prior to closing or resetting the stream.\n%% @todo All the malformed tests\n\nheaders_reject_uppercase_header_name(Config) ->\n\tdoc(\"Requests containing uppercase header names must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_header(Config,\n\t\t{<<\"I-AM-GIGANTIC\">>, <<\"How's the weather up there?\">>}\n\t).\n\n%% 4.2. HTTP Fields\n%% An endpoint MUST NOT generate an HTTP/3 field section containing\n%connection-specific fields; any message containing connection-specific fields\n%MUST be treated as malformed.\n\nreject_connection_header(Config) ->\n\tdoc(\"Requests containing a connection header must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_header(Config,\n\t\t{<<\"connection\">>, <<\"close\">>}\n\t).\n\nreject_keep_alive_header(Config) ->\n\tdoc(\"Requests containing a keep-alive header must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_header(Config,\n\t\t{<<\"keep-alive\">>, <<\"timeout=5, max=1000\">>}\n\t).\n\nreject_proxy_authenticate_header(Config) ->\n\tdoc(\"Requests containing a proxy-authenticate header must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_header(Config,\n\t\t{<<\"proxy-authenticate\">>, <<\"Basic\">>}\n\t).\n\nreject_proxy_authorization_header(Config) ->\n\tdoc(\"Requests containing a proxy-authorization header must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_header(Config,\n\t\t{<<\"proxy-authorization\">>, <<\"Basic YWxhZGRpbjpvcGVuc2VzYW1l\">>}\n\t).\n\nreject_transfer_encoding_header(Config) ->\n\tdoc(\"Requests containing a transfer-encoding header must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_header(Config,\n\t\t{<<\"transfer-encoding\">>, <<\"chunked\">>}\n\t).\n\nreject_upgrade_header(Config) ->\n\tdoc(\"Requests containing an upgrade header must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.5, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_header(Config,\n\t\t{<<\"upgrade\">>, <<\"websocket\">>}\n\t).\n\naccept_te_header_value_trailers(Config) ->\n\tdoc(\"Requests containing a TE header with a value of \\\"trailers\\\" \"\n\t\t\"must be accepted. (RFC9114 4.2)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>},\n\t\t{<<\"te\">>, <<\"trailers\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\"content-type\">>, <<\"text/plain\">>}\n\t], 0, EncSt0),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers)),\n\t\tEncodedTrailers\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\nreject_te_header_other_values(Config) ->\n\tdoc(\"Requests containing a TE header with a value other than \\\"trailers\\\" must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.2, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_header(Config,\n\t\t{<<\"te\">>, <<\"trailers, deflate;q=0.5\">>}\n\t).\n\n%% @todo response_dont_send_header_in_connection\n%% @todo response_dont_send_connection_header\n%% @todo response_dont_send_keep_alive_header\n%% @todo response_dont_send_proxy_connection_header\n%% @todo response_dont_send_transfer_encoding_header\n%% @todo response_dont_send_upgrade_header\n\n%% 4.2.1. Field Compression\n%% To allow for better compression efficiency, the Cookie header field\n%([COOKIES]) MAY be split into separate field lines, each with one or more\n%cookie-pairs, before compression. If a decompressed field section contains\n%multiple cookie field lines, these MUST be concatenated into a single byte\n%string using the two-byte delimiter of \"; \" (ASCII 0x3b, 0x20) before being\n%passed into a context other than HTTP/2 or HTTP/3, such as an HTTP/1.1\n%connection, or a generic HTTP server application.\n\n%% 4.2.2. Header Size Constraints\n%% An HTTP/3 implementation MAY impose a limit on the maximum size of the\n%message header it will accept on an individual HTTP message. A server that\n%receives a larger header section than it is willing to handle can send an HTTP\n%431 (Request Header Fields Too Large) status code ([RFC6585]). The size of a\n%field list is calculated based on the uncompressed size of fields, including\n%the length of the name and value in bytes plus an overhead of 32 bytes for\n%each field.\n%% If an implementation wishes to advise its peer of this limit, it can be\n%conveyed as a number of bytes in the SETTINGS_MAX_FIELD_SECTION_SIZE\n%parameter. \n\nreject_unknown_pseudo_headers(Config) ->\n\tdoc(\"Requests containing unknown pseudo-headers must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_header(Config,\n\t\t{<<\":upgrade\">>, <<\"websocket\">>}\n\t).\n\nreject_response_pseudo_headers(Config) ->\n\tdoc(\"Requests containing response pseudo-headers must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_header(Config,\n\t\t{<<\":status\">>, <<\"200\">>}\n\t).\n\nreject_pseudo_headers_in_trailers(Config) ->\n\tdoc(\"Requests containing pseudo-headers in trailers must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3, RFC9114 4.1.2)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"trailer\">>, <<\"x-checksum\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, EncodedTrailers, _EncData2, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\"x-checksum\">>, <<\"md5:4cc909a007407f3706399b6496babec3\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t], 0, EncSt0),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(10000),\n\t\t<<0:10000/unit:8>>,\n\t\t<<1>>, %% HEADERS frame for trailers.\n\t\tcow_http3:encode_int(iolist_size(EncodedTrailers)),\n\t\tEncodedTrailers\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = do_wait_stream_aborted(StreamRef),\n\tok.\n\nreject_pseudo_headers_after_regular_headers(Config) ->\n\tdoc(\"Requests containing pseudo-headers after regular headers must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]).\n\nreject_userinfo(Config) ->\n\tdoc(\"An authority containing a userinfo component must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"user@localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]).\n\n%% To ensure that the HTTP/1.1 request line can be reproduced accurately, this\n%% pseudo-header field (:authority) MUST be omitted when translating from an\n%% HTTP/1.1 request that has a request target in a method-specific form;\n%% see Section 7.1 of [HTTP]. \n\nreject_empty_path(Config) ->\n\tdoc(\"A request containing an empty path component must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<>>}\n\t]).\n\nreject_missing_pseudo_header_method(Config) ->\n\tdoc(\"A request without a method component must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]).\n\nreject_many_pseudo_header_method(Config) ->\n\tdoc(\"A request containing more than one method component must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]).\n\nreject_missing_pseudo_header_scheme(Config) ->\n\tdoc(\"A request without a scheme component must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]).\n\nreject_many_pseudo_header_scheme(Config) ->\n\tdoc(\"A request containing more than one scheme component must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]).\n\nreject_missing_pseudo_header_authority(Config) ->\n\tdoc(\"A request without an authority or host component must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]).\n\naccept_host_header_on_missing_pseudo_header_authority(Config) ->\n\tdoc(\"A request without an authority but with a host header must be accepted. \"\n\t\t\"(RFC9114 4.3.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, _EncSt0} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"host\">>, <<\"localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\n%% @todo\n%% If the :scheme pseudo-header field identifies a scheme that has a mandatory\n%% authority component (including \"http\" and \"https\"), the request MUST contain\n%% either an :authority pseudo-header field or a Host header field.\n%%  - If both fields are present, they MUST NOT be empty.\n%%  - If both fields are present, they MUST contain the same value. \n\nreject_many_pseudo_header_authority(Config) ->\n\tdoc(\"A request containing more than one authority component must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]).\n\nreject_missing_pseudo_header_path(Config) ->\n\tdoc(\"A request without a path component must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}\n\t]).\n\nreject_many_pseudo_header_path(Config) ->\n\tdoc(\"A request containing more than one path component must be rejected \"\n\t\t\"with an H3_MESSAGE_ERROR stream error. (RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]).\n\ndo_reject_malformed_header(Config, Header) ->\n\tdo_reject_malformed_headers(Config, [\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\tHeader\n\t]).\n\ndo_reject_malformed_headers(Config, Headers) ->\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData1, _EncSt0}\n\t\t= cow_qpack:encode_field_section(Headers, 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = do_wait_stream_aborted(StreamRef),\n\tok.\n\n%% For responses, a single \":status\" pseudo-header field is defined that\n%% carries the HTTP status code; see Section 15 of [HTTP]. This pseudo-header\n%% field MUST be included in all responses; otherwise, the response is malformed\n%% (see Section 4.1.2).\n\n%% @todo Implement CONNECT. (RFC9114 4.4. The CONNECT Method)\n\n%% @todo Maybe block the sending of 101 responses? (RFC9114 4.5. HTTP Upgrade) - also HTTP/2.\n\n%% @todo Implement server push (RFC9114 4.6. Server Push)\n\n%% @todo - need a way to list connections\n%% 5.2. Connection Shutdown\n%% Endpoints initiate the graceful shutdown of an HTTP/3 connection by sending\n%% a GOAWAY frame. The GOAWAY frame contains an identifier that indicates to the\n%% receiver the range of requests or pushes that were or might be processed in\n%% this connection. The server sends a client-initiated bidirectional stream ID;\n%% the client sends a push ID. Requests or pushes with the indicated identifier\n%% or greater are rejected (Section 4.1.1) by the sender of the GOAWAY. This\n%% identifier MAY be zero if no requests or pushes were processed.\n\n%% @todo\n%% Upon sending a GOAWAY frame, the endpoint SHOULD explicitly cancel (see\n%% Sections 4.1.1 and 7.2.3) any requests or pushes that have identifiers greater\n%% than or equal to the one indicated, in order to clean up transport state for\n%% the affected streams. The endpoint SHOULD continue to do so as more requests\n%% or pushes arrive.\n\n%% @todo\n%% Endpoints MUST NOT initiate new requests or promise new pushes on the\n%% connection after receipt of a GOAWAY frame from the peer.\n\n%% @todo\n%% Requests on stream IDs less than the stream ID in a GOAWAY frame from the\n%% server might have been processed; their status cannot be known until a\n%% response is received, the stream is reset individually, another GOAWAY is\n%% received with a lower stream ID than that of the request in question, or the\n%% connection terminates.\n\n%% @todo\n%% Servers MAY reject individual requests on streams below the indicated ID if\n%% these requests were not processed.\n\n%% @todo\n%% If a server receives a GOAWAY frame after having promised pushes with a push\n%% ID greater than or equal to the identifier contained in the GOAWAY frame,\n%% those pushes will not be accepted.\n\n%% @todo\n%% Servers SHOULD send a GOAWAY frame when the closing of a connection is known\n%% in advance, even if the advance notice is small, so that the remote peer can\n%% know whether or not a request has been partially processed.\n\n%% @todo\n%% An endpoint MAY send multiple GOAWAY frames indicating different\n%% identifiers, but the identifier in each frame MUST NOT be greater than the\n%% identifier in any previous frame, since clients might already have retried\n%% unprocessed requests on another HTTP connection. Receiving a GOAWAY containing\n%% a larger identifier than previously received MUST be treated as a connection\n%% error of type H3_ID_ERROR.\n\n%% @todo\n%% An endpoint that is attempting to gracefully shut down a connection can send\n%% a GOAWAY frame with a value set to the maximum possible value (2^62-4 for\n%% servers, 2^62-1 for clients).\n\n%% @todo\n%% Even when a GOAWAY indicates that a given request or push will not be\n%% processed or accepted upon receipt, the underlying transport resources still\n%% exist. The endpoint that initiated these requests can cancel them to clean up\n%% transport state.\n\n%% @todo\n%% Once all accepted requests and pushes have been processed, the endpoint can\n%% permit the connection to become idle, or it MAY initiate an immediate closure\n%% of the connection. An endpoint that completes a graceful shutdown SHOULD use\n%% the H3_NO_ERROR error code when closing the connection.\n\n%% @todo\n%% If a client has consumed all available bidirectional stream IDs with\n%% requests, the server need not send a GOAWAY frame, since the client is unable\n%% to make further requests. @todo OK that one's some weird stuff lol\n\n%% @todo\n%% 5.3. Immediate Application Closure\n%% Before closing the connection, a GOAWAY frame MAY be sent to allow the\n%% client to retry some requests. Including the GOAWAY frame in the same packet\n%% as the QUIC CONNECTION_CLOSE frame improves the chances of the frame being\n%% received by clients.\n\nbidi_allow_at_least_a_hundred(Config) ->\n\tdoc(\"Endpoints must allow the peer to create at least \"\n\t\t\"one hundred bidirectional streams. (RFC9114 6.1\"),\n\t#{conn := Conn} = do_connect(Config),\n\treceive\n\t\t{quic, streams_available, Conn, #{bidi_streams := NumStreams}} ->\n\t\t\ttrue = NumStreams >= 100,\n\t\t\tok\n\tafter 5000 ->\n\t\terror(timeout)\n\tend.\n\nunidi_allow_at_least_three(Config) ->\n\tdoc(\"Endpoints must allow the peer to create at least \"\n\t\t\"three unidirectional streams. (RFC9114 6.2\"),\n\t#{conn := Conn} = do_connect(Config),\n\t%% Confirm that the server advertised support for at least 3 unidi streams.\n\treceive\n\t\t{quic, streams_available, Conn, #{unidi_streams := NumStreams}} ->\n\t\t\ttrue = NumStreams >= 3,\n\t\t\tok\n\tafter 5000 ->\n\t\terror(timeout)\n\tend,\n\t%% Confirm that we can create the unidi streams.\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(ControlRef, [<<0>>, SettingsBin]),\n\t{ok, EncoderRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(EncoderRef, <<2>>),\n\t{ok, DecoderRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(DecoderRef, <<3>>),\n\t%% Streams shouldn't get closed.\n\tfun Loop() ->\n\t\treceive\n\t\t\t%% We don't care about these messages.\n\t\t\t{quic, dgram_state_changed, Conn, _} ->\n\t\t\t\tLoop();\n\t\t\t{quic, peer_needs_streams, Conn, _} ->\n\t\t\t\tLoop();\n\t\t\t%% Any other we do care.\n\t\t\tMsg ->\n\t\t\t\terror(Msg)\n\t\tafter 1000 ->\n\t\t\tok\n\t\tend\n\tend().\n\nunidi_create_critical_first(Config) ->\n\tdoc(\"Endpoints should create the HTTP control stream as well as \"\n\t\t\"the QPACK encoder and decoder streams first. (RFC9114 6.2\"),\n\t%% The control stream is accepted in the do_connect/1 function.\n\t#{conn := Conn} = do_connect(Config, #{peer_unidi_stream_count => 3}),\n\tUnidi1 = do_accept_qpack_stream(Conn),\n\tUnidi2 = do_accept_qpack_stream(Conn),\n\tcase {Unidi1, Unidi2} of\n\t\t{{encoder, _}, {decoder, _}} ->\n\t\t\tok;\n\t\t{{decoder, _}, {encoder, _}} ->\n\t\t\tok\n\tend.\n\ndo_accept_qpack_stream(Conn) ->\n\treceive\n\t\t{quic, new_stream, StreamRef, #{flags := Flags}} ->\n\t\t\tok = quicer:setopt(StreamRef, active, true),\n\t\t\ttrue = quicer:is_unidirectional(Flags),\n\t\t\treceive {quic, <<Type>>, StreamRef, _} ->\n\t\t\t\t{case Type of\n\t\t\t\t\t2 -> encoder;\n\t\t\t\t\t3 -> decoder\n\t\t\t\tend, StreamRef}\n\t\t\tafter 5000 ->\n\t\t\t\terror(timeout)\n\t\t\tend\n\tafter 5000 ->\n\t\terror(timeout)\n\tend.\n\n%% @todo We should also confirm that there's at least 1,024 bytes of\n%%       flow-control credit for each unidi stream the server creates. (How?)\n%%       It can be set via stream_recv_window_default in quicer.\n\nunidi_abort_unknown_type(Config) ->\n\tdoc(\"Receipt of an unknown stream type must be aborted \"\n\t\t\"with an H3_STREAM_CREATION_ERROR stream error. (RFC9114 6.2, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t%% Create an unknown unidirectional stream.\n\t{ok, StreamRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\tcow_http3:encode_int(1 + do_reserved_type()),\n\t\trand:bytes(rand:uniform(4096))\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_stream_creation_error} = do_wait_stream_aborted(StreamRef),\n\tok.\n\nunidi_abort_reserved_type(Config) ->\n\tdoc(\"Receipt of a reserved stream type must be aborted \"\n\t\t\"with an H3_STREAM_CREATION_ERROR stream error. \"\n\t\t\"(RFC9114 6.2, RFC9114 6.2.3, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t%% Create a reserved unidirectional stream.\n\t{ok, StreamRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\tcow_http3:encode_int(do_reserved_type()),\n\t\trand:bytes(rand:uniform(4096))\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_stream_creation_error} = do_wait_stream_aborted(StreamRef),\n\tok.\n\n%% As certain stream types can affect connection state, a recipient SHOULD NOT\n%% discard data from incoming unidirectional streams prior to reading the stream type.\n\n%% Implementations MAY send stream types before knowing whether the peer\n%supports them. However, stream types that could modify the state or semantics\n%of existing protocol components, including QPACK or other extensions, MUST NOT\n%be sent until the peer is known to support them.\n%% @todo It may make sense for Cowboy to delay the creation of unidi streams\n%%       a little in order to save resources. We could create them when the\n%%       client does as well, or something similar.\n\n%% A receiver MUST tolerate unidirectional streams being closed or reset prior\n%% to the reception of the unidirectional stream header.\n\n%% Each side MUST initiate a single control stream at the beginning of the\n%% connection and send its SETTINGS frame as the first frame on this stream.\n%% @todo What to do when the client never opens a control stream?\n%% @todo Similarly, a stream could be opened but with no data being sent.\n%% @todo Similarly, a control stream could be opened with no SETTINGS frame sent.\n\ncontrol_reject_first_frame_data(Config) ->\n\tdoc(\"The first frame on a control stream must be a SETTINGS frame \"\n\t\t\"or the connection must be closed with an H3_MISSING_SETTINGS \"\n\t\t\"connection error. (RFC9114 6.2.1, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(12),\n\t\t<<\"Hello world!\">>\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_missing_settings} = do_wait_connection_closed(Conn),\n\tok.\n\ncontrol_reject_first_frame_headers(Config) ->\n\tdoc(\"The first frame on a control stream must be a SETTINGS frame \"\n\t\t\"or the connection must be closed with an H3_MISSING_SETTINGS \"\n\t\t\"connection error. (RFC9114 6.2.1, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_missing_settings} = do_wait_connection_closed(Conn),\n\tok.\n\ncontrol_reject_first_frame_cancel_push(Config) ->\n\tdoc(\"The first frame on a control stream must be a SETTINGS frame \"\n\t\t\"or the connection must be closed with an H3_MISSING_SETTINGS \"\n\t\t\"connection error. (RFC9114 6.2.1, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\t<<3>>, %% CANCEL_PUSH frame.\n\t\tcow_http3:encode_int(1),\n\t\tcow_http3:encode_int(0)\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_missing_settings} = do_wait_connection_closed(Conn),\n\tok.\n\ncontrol_accept_first_frame_settings(Config) ->\n\tdoc(\"The first frame on a control stream \"\n\t\t\"must be a SETTINGS frame. (RFC9114 6.2.1, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin\n\t]),\n\t%% The connection should remain up.\n\treceive\n\t\t{quic, shutdown, Conn, {unknown_quic_status, Code}} ->\n\t\t\tReason = cow_http3:code_to_error(Code),\n\t\t\terror(Reason)\n\tafter 1000 ->\n\t\tok\n\tend.\n\ncontrol_reject_first_frame_push_promise(Config) ->\n\tdoc(\"The first frame on a control stream must be a SETTINGS frame \"\n\t\t\"or the connection must be closed with an H3_MISSING_SETTINGS \"\n\t\t\"connection error. (RFC9114 6.2.1, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\t<<5>>, %% PUSH_PROMISE frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders) + 1),\n\t\tcow_http3:encode_int(0),\n\t\tEncodedHeaders\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_missing_settings} = do_wait_connection_closed(Conn),\n\tok.\n\ncontrol_reject_first_frame_goaway(Config) ->\n\tdoc(\"The first frame on a control stream must be a SETTINGS frame \"\n\t\t\"or the connection must be closed with an H3_MISSING_SETTINGS \"\n\t\t\"connection error. (RFC9114 6.2.1, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\t<<7>>, %% GOAWAY frame.\n\t\tcow_http3:encode_int(1),\n\t\tcow_http3:encode_int(0)\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_missing_settings} = do_wait_connection_closed(Conn),\n\tok.\n\ncontrol_reject_first_frame_max_push_id(Config) ->\n\tdoc(\"The first frame on a control stream must be a SETTINGS frame \"\n\t\t\"or the connection must be closed with an H3_MISSING_SETTINGS \"\n\t\t\"connection error. (RFC9114 6.2.1, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\t<<13>>, %% MAX_PUSH_ID frame.\n\t\tcow_http3:encode_int(1),\n\t\tcow_http3:encode_int(0)\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_missing_settings} = do_wait_connection_closed(Conn),\n\tok.\n\ncontrol_reject_first_frame_reserved(Config) ->\n\tdoc(\"The first frame on a control stream must be a SETTINGS frame \"\n\t\t\"or the connection must be closed with an H3_MISSING_SETTINGS \"\n\t\t\"connection error. (RFC9114 6.2.1, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\tLen = rand:uniform(512),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tcow_http3:encode_int(do_reserved_type()),\n\t\tcow_http3:encode_int(Len),\n\t\trand:bytes(Len)\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_missing_settings} = do_wait_connection_closed(Conn),\n\tok.\n\ncontrol_reject_multiple(Config) ->\n\tdoc(\"Endpoints must not create multiple control streams. (RFC9114 6.2.1)\"),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\tdo_critical_reject_multiple(Config, [<<0>>, SettingsBin]).\n\ndo_critical_reject_multiple(Config, HeaderData) ->\n\t#{conn := Conn} = do_connect(Config),\n\t%% Create two critical streams.\n\t{ok, StreamRef1} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(StreamRef1, HeaderData),\n\t{ok, StreamRef2} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(StreamRef2, HeaderData),\n\t%% The connection should have been closed.\n\t#{reason := h3_stream_creation_error} = do_wait_connection_closed(Conn),\n\tok.\n\ncontrol_local_closed_abort(Config) ->\n\tdoc(\"Endpoints must not close the control stream. (RFC9114 6.2.1)\"),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\tdo_critical_local_closed_abort(Config, [<<0>>, SettingsBin]).\n\ndo_critical_local_closed_abort(Config, HeaderData) ->\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(StreamRef, HeaderData),\n\t%% Wait a little to make sure the stream data was received before we abort.\n\ttimer:sleep(100),\n\t%% Close the critical stream.\n\tquicer:async_shutdown_stream(StreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0),\n\t%% The connection should have been closed.\n\ttimer:sleep(1000),\n\t#{reason := h3_closed_critical_stream} = do_wait_connection_closed(Conn),\n\tok.\n\ncontrol_local_closed_graceful(Config) ->\n\tdoc(\"Endpoints must not close the control stream. (RFC9114 6.2.1)\"),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\tdo_critical_local_closed_graceful(Config, [<<0>>, SettingsBin]).\n\ndo_critical_local_closed_graceful(Config, HeaderData) ->\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, _} = quicer:send(StreamRef, HeaderData),\n\t%% Close the critical stream.\n\tquicer:async_shutdown_stream(StreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_GRACEFUL, 0),\n\t%% The connection should have been closed.\n\t#{reason := h3_closed_critical_stream} = do_wait_connection_closed(Conn),\n\tok.\n\ncontrol_remote_closed_abort(Config) ->\n\tdoc(\"Endpoints must not close the control stream. (RFC9114 6.2.1)\"),\n\t#{conn := Conn, control := ControlRef} = do_connect(Config),\n\t%% Close the control stream.\n\tquicer:async_shutdown_stream(ControlRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0),\n\t%% The connection should have been closed.\n\t#{reason := h3_closed_critical_stream} = do_wait_connection_closed(Conn),\n\tok.\n\n%% We cannot gracefully shutdown a remote unidi stream; only abort reading.\n\n%% Because the contents of the control stream are used to manage the behavior\n%% of other streams, endpoints SHOULD provide enough flow-control credit to keep\n%% the peer's control stream from becoming blocked.\n\n%% @todo Implement server push (RFC9114 6.2.2 Push Streams)\n\ndata_frame_can_span_multiple_packets(Config) ->\n\tdoc(\"HTTP/3 frames can span multiple packets. (RFC9114 7)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>},\n\t\t{<<\"content-length\">>, <<\"13\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello \">>\n\t]),\n\ttimer:sleep(100),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<\"server!\">>\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello server!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\nheaders_frame_can_span_multiple_packets(Config) ->\n\tdoc(\"HTTP/3 frames can span multiple packets. (RFC9114 7)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\tHalf = iolist_size(EncodedHeaders) div 2,\n\t<<EncodedHeadersPart1:Half/binary, EncodedHeadersPart2/bits>>\n\t\t= iolist_to_binary(EncodedHeaders),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeadersPart1\n\t]),\n\ttimer:sleep(100),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\tEncodedHeadersPart2\n\t]),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\n%% @todo Implement server push. cancel_push_frame_can_span_multiple_packets(Config) ->\n\nsettings_frame_can_span_multiple_packets(Config) ->\n\tdoc(\"HTTP/3 frames can span multiple packets. (RFC9114 7)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t<<SettingsPart1:1/binary, SettingsPart2/bits>> = SettingsBin,\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsPart1\n\t]),\n\ttimer:sleep(100),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\tSettingsPart2\n\t]),\n\t%% The connection should remain up.\n\treceive\n\t\t{quic, shutdown, Conn, {unknown_quic_status, Code}} ->\n\t\t\tReason = cow_http3:code_to_error(Code),\n\t\t\terror(Reason)\n\tafter 1000 ->\n\t\tok\n\tend.\n\ngoaway_frame_can_span_multiple_packets(Config) ->\n\tdoc(\"HTTP/3 frames can span multiple packets. (RFC9114 7)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\t<<7>>, cow_http3:encode_int(1) %% GOAWAY part 1.\n\t]),\n\ttimer:sleep(100),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\tcow_http3:encode_int(0) %% GOAWAY part 2.\n\t]),\n\t%% The connection should be closed gracefully.\n\treceive\n\t\t{quic, shutdown, Conn, {unknown_quic_status, Code}} ->\n\t\t\th3_no_error = cow_http3:code_to_error(Code),\n\t\t\tok;\n\t\t%% @todo Temporarily also accept this message. I am\n\t\t%%       not sure why it happens but it isn't wrong per se.\n\t\t{quic, shutdown, Conn, success} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\nmax_push_id_frame_can_span_multiple_packets(Config) ->\n\tdoc(\"HTTP/3 frames can span multiple packets. (RFC9114 7)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\t<<13>>, cow_http3:encode_int(1) %% MAX_PUSH_ID part 1.\n\t]),\n\ttimer:sleep(100),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\tcow_http3:encode_int(0) %% MAX_PUSH_ID part 2.\n\t]),\n\t%% The connection should remain up.\n\treceive\n\t\t{quic, shutdown, Conn, {unknown_quic_status, Code}} ->\n\t\t\tReason = cow_http3:code_to_error(Code),\n\t\t\terror(Reason)\n\tafter 1000 ->\n\t\tok\n\tend.\n\nunknown_frame_can_span_multiple_packets(Config) ->\n\tdoc(\"HTTP/3 frames can span multiple packets. (RFC9114 7)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\tcow_http3:encode_int(do_unknown_frame_type()),\n\t\tcow_http3:encode_int(16383)\n\t]),\n\ttimer:sleep(100),\n\t{ok, _} = quicer:send(StreamRef, rand:bytes(4096)),\n\ttimer:sleep(100),\n\t{ok, _} = quicer:send(StreamRef, rand:bytes(4096)),\n\ttimer:sleep(100),\n\t{ok, _} = quicer:send(StreamRef, rand:bytes(4096)),\n\ttimer:sleep(100),\n\t{ok, _} = quicer:send(StreamRef, rand:bytes(4095)),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders\n\t], ?QUIC_SEND_FLAG_FIN),\n\t#{\n\t\theaders := #{<<\":status\">> := <<\"200\">>},\n\t\tbody := <<\"Hello world!\">>\n\t} = do_receive_response(StreamRef),\n\tok.\n\n%% The DATA and SETTINGS frames can be zero-length therefore\n%% they cannot be too short.\n\nheaders_frame_too_short(Config) ->\n\tdoc(\"Frames that terminate before the end of identified fields \"\n\t\t\"must be rejected with an H3_FRAME_ERROR connection error. \"\n\t\t\"(RFC9114 7.1, RFC9114 10.8)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(0)\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_error} = do_wait_connection_closed(Conn),\n\tok.\n\n%% @todo Implement server push. cancel_push_frame_too_short(Config) ->\n\ngoaway_frame_too_short(Config) ->\n\tdoc(\"Frames that terminate before the end of identified fields \"\n\t\t\"must be rejected with an H3_FRAME_ERROR connection error. \"\n\t\t\"(RFC9114 7.1, RFC9114 10.8)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\t<<7>>, cow_http3:encode_int(0) %% GOAWAY.\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_error} = do_wait_connection_closed(Conn),\n\tok.\n\nmax_push_id_frame_too_short(Config) ->\n\tdoc(\"Frames that terminate before the end of identified fields \"\n\t\t\"must be rejected with an H3_FRAME_ERROR connection error. \"\n\t\t\"(RFC9114 7.1, RFC9114 10.8)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\t<<13>>, cow_http3:encode_int(0) %% MAX_PUSH_ID.\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_error} = do_wait_connection_closed(Conn),\n\tok.\n\ndata_frame_truncated(Config) ->\n\tdoc(\"Truncated frames must be rejected with an \"\n\t\t\"H3_FRAME_ERROR connection error. (RFC9114 7.1, RFC9114 10.8)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>},\n\t\t{<<\"content-length\">>, <<\"13\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(13),\n\t\t<<\"Hello \">>\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_error} = do_wait_connection_closed(Conn),\n\tok.\n\nheaders_frame_truncated(Config) ->\n\tdoc(\"Truncated frames must be rejected with an \"\n\t\t\"H3_FRAME_ERROR connection error. (RFC9114 7.1, RFC9114 10.8)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders))\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_error} = do_wait_connection_closed(Conn),\n\tok.\n\n%% I am not sure how to test truncated CANCEL_PUSH, SETTINGS, GOAWAY\n%% or MAX_PUSH_ID frames, as those are sent on the control stream,\n%% which we cannot terminate.\n\n%% The DATA, HEADERS and SETTINGS frames can be of any length\n%% therefore they cannot be too long per se, even if unwanted\n%% data can be included at the end of the frame's payload.\n\n%% @todo Implement server push. cancel_push_frame_too_long(Config) ->\n\ngoaway_frame_too_long(Config) ->\n\tdoc(\"Frames that contain additional bytes after the end of identified fields \"\n\t\t\"must be rejected with an H3_FRAME_ERROR connection error. \"\n\t\t\"(RFC9114 7.1, RFC9114 10.8)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\t<<7>>, cow_http3:encode_int(3), %% GOAWAY.\n\t\t<<0, 1, 2>>\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_error} = do_wait_connection_closed(Conn),\n\tok.\n\nmax_push_id_frame_too_long(Config) ->\n\tdoc(\"Frames that contain additional bytes after the end of identified fields \"\n\t\t\"must be rejected with an H3_FRAME_ERROR connection error. \"\n\t\t\"(RFC9114 7.1, RFC9114 10.8)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\t<<13>>, cow_http3:encode_int(9), %% MAX_PUSH_ID.\n\t\t<<0, 1, 2, 3, 4, 5, 6, 7, 8>>\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_error} = do_wait_connection_closed(Conn),\n\tok.\n\n%% Streams may terminate abruptly in the middle of frames.\n\ndata_frame_rejected_on_control_stream(Config) ->\n\tdoc(\"DATA frames received on the control stream must be rejected \"\n\t\t\"with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\t<<0>>, %% DATA frame.\n\t\tcow_http3:encode_int(12),\n\t\t<<\"Hello world!\">>\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\nheaders_frame_rejected_on_control_stream(Config) ->\n\tdoc(\"HEADERS frames received on the control stream must be rejected \"\n\t\t\"with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.2)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\n%% @todo Implement server push. (RFC9114 7.2.3. CANCEL_PUSH)\n\nsettings_twice(Config) ->\n\tdoc(\"Receipt of a second SETTINGS frame on the control stream \"\n\t\t\"must be rejected with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.4)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\tSettingsBin\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\nsettings_on_bidi_stream(Config) ->\n\tdoc(\"Receipt of a SETTINGS frame on a bidirectional stream \"\n\t\t\"must be rejected with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.4)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\tSettingsBin,\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\nsettings_identifier_twice(Config) ->\n\tdoc(\"Receipt of a duplicate SETTINGS identifier must be rejected \"\n\t\t\"with an H3_SETTINGS_ERROR connection error. (RFC9114 7.2.4)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\tSettingsPayload = [\n\t\tcow_http3:encode_int(6), cow_http3:encode_int(4096),\n\t\tcow_http3:encode_int(6), cow_http3:encode_int(8192)\n\t],\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\t<<4>>, %% SETTINGS frame.\n\t\tcow_http3:encode_int(iolist_size(SettingsPayload)),\n\t\tSettingsPayload\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_settings_error} = do_wait_connection_closed(Conn),\n\tok.\n\nsettings_ignore_unknown_identifier(Config) ->\n\tdoc(\"Unknown SETTINGS identifiers must be ignored (RFC9114 7.2.4, RFC9114 9)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\tSettingsPayload = [\n\t\tcow_http3:encode_int(999), cow_http3:encode_int(4096)\n\t],\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\t<<4>>, %% SETTINGS frame.\n\t\tcow_http3:encode_int(iolist_size(SettingsPayload)),\n\t\tSettingsPayload\n\t]),\n\t%% The connection should remain up.\n\treceive\n\t\t{quic, shutdown, Conn, {unknown_quic_status, Code}} ->\n\t\t\tReason = cow_http3:code_to_error(Code),\n\t\t\terror(Reason)\n\tafter 1000 ->\n\t\tok\n\tend.\n\nsettings_ignore_reserved_identifier(Config) ->\n\tdoc(\"Reserved SETTINGS identifiers must be ignored (RFC9114 7.2.4.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\tSettingsPayload = [\n\t\tcow_http3:encode_int(do_reserved_type()), cow_http3:encode_int(4096)\n\t],\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\t<<4>>, %% SETTINGS frame.\n\t\tcow_http3:encode_int(iolist_size(SettingsPayload)),\n\t\tSettingsPayload\n\t]),\n\t%% The connection should remain up.\n\treceive\n\t\t{quic, shutdown, Conn, {unknown_quic_status, Code}} ->\n\t\t\tReason = cow_http3:code_to_error(Code),\n\t\t\terror(Reason)\n\tafter 1000 ->\n\t\tok\n\tend.\n\n%% @todo Check that we send a reserved SETTINGS identifier when sending a\n%%       non-empty SETTINGS frame. (7.2.4.1. Defined SETTINGS Parameters)\n\n%% @todo Check that setting SETTINGS_MAX_FIELD_SECTION_SIZE works.\n\n%% It is unclear whether the SETTINGS identifier 0x00 must be rejected or ignored.\n\nsettings_reject_http2_0x02(Config) ->\n\tdo_settings_reject_http2(Config, 2, 1).\n\nsettings_reject_http2_0x03(Config) ->\n\tdo_settings_reject_http2(Config, 3, 100).\n\nsettings_reject_http2_0x04(Config) ->\n\tdo_settings_reject_http2(Config, 4, 128000).\n\nsettings_reject_http2_0x05(Config) ->\n\tdo_settings_reject_http2(Config, 5, 1000000).\n\ndo_settings_reject_http2(Config, Identifier, Value) ->\n\tdoc(\"Receipt of an unused HTTP/2 SETTINGS identifier must be rejected \"\n\t\t\"with an H3_SETTINGS_ERROR connection error. (RFC9114 7.2.4, RFC9114 11.2.2)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\tSettingsPayload = [\n\t\tcow_http3:encode_int(Identifier), cow_http3:encode_int(Value)\n\t],\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\t<<4>>, %% SETTINGS frame.\n\t\tcow_http3:encode_int(iolist_size(SettingsPayload)),\n\t\tSettingsPayload\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_settings_error} = do_wait_connection_closed(Conn),\n\tok.\n\n%% 7.2.4.2. Initialization\n%% An HTTP implementation MUST NOT send frames or requests that would be\n%% invalid based on its current understanding of the peer's settings.\n%% @todo In the case of SETTINGS_MAX_FIELD_SECTION_SIZE I don't think we have a choice.\n\n%% All settings begin at an initial value. Each endpoint SHOULD use these\n%% initial values to send messages before the peer's SETTINGS frame has arrived,\n%% as packets carrying the settings can be lost or delayed. When the SETTINGS\n%% frame arrives, any settings are changed to their new values.\n\n%% Endpoints MUST NOT require any data to be received from the peer prior to\n%% sending the SETTINGS frame; settings MUST be sent as soon as the transport is\n%% ready to send data.\n\n%% @todo Implement 0-RTT. (7.2.4.2. Initialization)\n\n%% @todo Implement server push. (7.2.5. PUSH_PROMISE)\n\ngoaway_on_bidi_stream(Config) ->\n\tdoc(\"Receipt of a GOAWAY frame on a bidirectional stream \"\n\t\t\"must be rejected with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.6)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<7>>, cow_http3:encode_int(1), cow_http3:encode_int(0) %% GOAWAY.\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\n%% @todo Implement server push. (7.2.6 GOAWAY - will have to reject too large push IDs)\n\nmax_push_id_on_bidi_stream(Config) ->\n\tdoc(\"Receipt of a MAX_PUSH_ID frame on a bidirectional stream \"\n\t\t\"must be rejected with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.7)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<13>>, cow_http3:encode_int(1), cow_http3:encode_int(0) %% MAX_PUSH_ID.\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\n%% @todo Implement server push. (7.2.7 MAX_PUSH_ID)\n\nmax_push_id_reject_lower(Config) ->\n\tdoc(\"Receipt of a MAX_PUSH_ID value lower than previously received \"\n\t\t\"must be rejected with an H3_ID_ERROR connection error. (RFC9114 7.2.7)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\t<<13>>, cow_http3:encode_int(1), cow_http3:encode_int(20), %% MAX_PUSH_ID.\n\t\t<<13>>, cow_http3:encode_int(1), cow_http3:encode_int(10) %% MAX_PUSH_ID.\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_id_error} = do_wait_connection_closed(Conn),\n\tok.\n\nreserved_on_control_stream(Config) ->\n\tdoc(\"Receipt of a reserved frame type on a control stream \"\n\t\t\"must be ignored. (RFC9114 7.2.8)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\tLen = rand:uniform(512),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\tcow_http3:encode_int(do_reserved_type()),\n\t\tcow_http3:encode_int(Len),\n\t\trand:bytes(Len)\n\t]),\n\t%% The connection should remain up.\n\treceive\n\t\t{quic, shutdown, Conn, {unknown_quic_status, Code}} ->\n\t\t\tReason = cow_http3:code_to_error(Code),\n\t\t\terror(Reason)\n\tafter 1000 ->\n\t\tok\n\tend.\n\nreserved_reject_http2_0x02_control(Config) ->\n\tdo_reserved_reject_http2_control(Config, 2).\n\nreserved_reject_http2_0x06_control(Config) ->\n\tdo_reserved_reject_http2_control(Config, 6).\n\nreserved_reject_http2_0x08_control(Config) ->\n\tdo_reserved_reject_http2_control(Config, 8).\n\nreserved_reject_http2_0x09_control(Config) ->\n\tdo_reserved_reject_http2_control(Config, 9).\n\ndo_reserved_reject_http2_control(Config, Type) ->\n\tdoc(\"Receipt of an unused HTTP/2 frame type must be rejected \"\n\t\t\"with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.8, RFC9114 11.2.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, ControlRef} = quicer:start_stream(Conn,\n\t\t#{open_flag => ?QUIC_STREAM_OPEN_FLAG_UNIDIRECTIONAL}),\n\t{ok, SettingsBin, _HTTP3Machine0} = cow_http3_machine:init(client, #{}),\n\tLen = rand:uniform(512),\n\t{ok, _} = quicer:send(ControlRef, [\n\t\t<<0>>, %% CONTROL stream.\n\t\tSettingsBin,\n\t\tcow_http3:encode_int(Type),\n\t\tcow_http3:encode_int(Len),\n\t\trand:bytes(Len)\n\t]),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\nreserved_reject_http2_0x02_bidi(Config) ->\n\tdo_reserved_reject_http2_bidi(Config, 2).\n\nreserved_reject_http2_0x06_bidi(Config) ->\n\tdo_reserved_reject_http2_bidi(Config, 6).\n\nreserved_reject_http2_0x08_bidi(Config) ->\n\tdo_reserved_reject_http2_bidi(Config, 8).\n\nreserved_reject_http2_0x09_bidi(Config) ->\n\tdo_reserved_reject_http2_bidi(Config, 9).\n\ndo_reserved_reject_http2_bidi(Config, Type) ->\n\tdoc(\"Receipt of an unused HTTP/2 frame type must be rejected \"\n\t\t\"with an H3_FRAME_UNEXPECTED connection error. (RFC9114 7.2.8, RFC9114 11.2.1)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedHeaders, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>},\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<\"content-length\">>, <<\"0\">>}\n\t], 0, cow_qpack:init(encoder)),\n\tLen = rand:uniform(512),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\tcow_http3:encode_int(Type),\n\t\tcow_http3:encode_int(Len),\n\t\trand:bytes(Len),\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedHeaders)),\n\t\tEncodedHeaders\n\t], ?QUIC_SEND_FLAG_FIN),\n\t%% The connection should have been closed.\n\t#{reason := h3_frame_unexpected} = do_wait_connection_closed(Conn),\n\tok.\n\n%% An endpoint MAY choose to treat a stream error as a connection error under\n%% certain circumstances, closing the entire connection in response to a\n%% condition on a single stream.\n\n%% Because new error codes can be defined without negotiation (see Section 9),\n%% use of an error code in an unexpected context or receipt of an unknown error\n%% code MUST be treated as equivalent to H3_NO_ERROR.\n\n%% 8.1. HTTP/3 Error Codes\n%% H3_INTERNAL_ERROR (0x0102): An internal error has occurred in the HTTP stack.\n%% H3_EXCESSIVE_LOAD (0x0107): The endpoint detected that its peer is\n%% exhibiting a behavior that might be generating excessive load.\n%% H3_MISSING_SETTINGS (0x010a): No SETTINGS frame was received\n%% at the beginning of the control stream.\n%% H3_REQUEST_REJECTED (0x010b): A server rejected a request without\n%% performing any application processing.\n%% H3_REQUEST_CANCELLED (0x010c): The request or its response\n%% (including pushed response) is cancelled.\n%% H3_REQUEST_INCOMPLETE (0x010d): The client's stream terminated\n%% without containing a fully formed request.\n%% H3_CONNECT_ERROR (0x010f): The TCP connection established in\n%% response to a CONNECT request was reset or abnormally closed.\n%% H3_VERSION_FALLBACK (0x0110): The requested operation cannot\n%% be served over HTTP/3. The peer should retry over HTTP/1.1.\n\n%% 9. Extensions to HTTP/3\n%% If a setting is used for extension negotiation, the default value MUST be\n%% defined in such a fashion that the extension is disabled if the setting is\n%% omitted.\n\n%% 10. Security Considerations\n%% 10.3. Intermediary-Encapsulation Attacks\n%% Requests or responses containing invalid field names MUST be treated as malformed.\n%% Any request or response that contains a character not permitted in a field\n%% value MUST be treated as malformed.\n\n%% 10.5. Denial-of-Service Considerations\n%% Implementations SHOULD track the use of these features and set limits on\n%% their use. An endpoint MAY treat activity that is suspicious as a connection\n%% error of type H3_EXCESSIVE_LOAD, but false positives will result in disrupting\n%% valid connections and requests.\n\nreject_large_unknown_frame(Config) ->\n\tdoc(\"Large unknown frames may risk denial-of-service \"\n\t\t\"and should be rejected. (RFC9114 10.5)\"),\n\t#{conn := Conn} = do_connect(Config),\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\tcow_http3:encode_int(do_unknown_frame_type()),\n\t\tcow_http3:encode_int(16385)\n\t]),\n\t#{reason := h3_excessive_load} = do_wait_connection_closed(Conn),\n\tok.\n\n%% 10.5.1. Limits on Field Section Size\n%% An endpoint can use the SETTINGS_MAX_FIELD_SECTION_SIZE (Section 4.2.2)\n%% setting to advise peers of limits that might apply on the size of field\n%% sections.\n%%\n%% A server that receives a larger field section than it is willing to handle\n%% can send an HTTP 431 (Request Header Fields Too Large) status code\n%% ([RFC6585]).\n\n%% 10.6. Use of Compression\n%% Implementations communicating on a secure channel MUST NOT compress content\n%% that includes both confidential and attacker-controlled data unless separate\n%% compression contexts are used for each source of data. Compression MUST NOT be\n%% used if the source of data cannot be reliably determined.\n\n%% 10.9. Early Data\n%% The anti-replay mitigations in [HTTP-REPLAY] MUST be applied when using HTTP/3 with 0-RTT.\n\n%% 10.10. Migration\n%% Certain HTTP implementations use the client address for logging or\n%% access-control purposes. Since a QUIC client's address might change during a\n%% connection (and future versions might support simultaneous use of multiple\n%% addresses), such implementations will need to either actively retrieve the\n%% client's current address or addresses when they are relevant or explicitly\n%% accept that the original address might change. @todo Document this behavior.\n\n%% Appendix A. Considerations for Transitioning from HTTP/2\n%% A.1. Streams\n%% QUIC considers a stream closed when all data has been received and sent data\n%% has been acknowledged by the peer. HTTP/2 considers a stream closed when the\n%% frame containing the END_STREAM bit has been committed to the transport. As a\n%% result, the stream for an equivalent exchange could remain \"active\" for a\n%% longer period of time. HTTP/3 servers might choose to permit a larger number\n%% of concurrent client-initiated bidirectional streams to achieve equivalent\n%% concurrency to HTTP/2, depending on the expected usage patterns. @todo Document this.\n\n%% Helper functions.\n\n%% @todo Maybe have a function in cow_http3.\ndo_reserved_type() ->\n\t16#1f * (rand:uniform(148764065110560900) - 1) + 16#21.\n\ndo_connect(Config) ->\n\tdo_connect(Config, #{}).\n\ndo_connect(Config, Opts) ->\n\t{ok, Conn} = quicer:connect(\"localhost\", config(port, Config),\n\t\tOpts#{alpn => [\"h3\"], verify => none}, 5000),\n\t%% To make sure the connection is fully established we wait\n\t%% to receive the SETTINGS frame on the control stream.\n\t{ok, ControlRef, Settings} = do_wait_settings(Conn),\n\t#{\n\t\tconn => Conn,\n\t\tcontrol => ControlRef, %% This is the peer control stream.\n\t\tsettings => Settings\n\t}.\n\ndo_wait_settings(Conn) ->\n\treceive\n\t\t{quic, new_stream, StreamRef, #{flags := Flags}} ->\n\t\t\tok = quicer:setopt(StreamRef, active, true),\n\t\t\ttrue = quicer:is_unidirectional(Flags),\n\t\t\treceive {quic, <<\n\t\t\t\t0, %% Control stream.\n\t\t\t\tSettingsFrame/bits\n\t\t\t>>, StreamRef, _} ->\n\t\t\t\t{ok, {settings, Settings}, <<>>} = cow_http3:parse(SettingsFrame),\n\t\t\t\t{ok, StreamRef, Settings}\n\t\t\tafter 5000 ->\n\t\t\t\t{error, timeout}\n\t\t\tend\n\tafter 5000 ->\n\t\t{error, timeout}\n\tend.\n\ndo_receive_data(StreamRef) ->\n\treceive\n\t\t{quic, Data, StreamRef, _Flags} when is_binary(Data) ->\n\t\t\t{ok, Data}\n\tafter 5000 ->\n\t\t{error, timeout}\n\tend.\n\ndo_guess_int_encoding(Data) ->\n\tSizeWithLen = byte_size(Data) - 1,\n\tif\n\t\tSizeWithLen < 64 + 1 ->\n\t\t\t{0, 6};\n\t\tSizeWithLen < 16384 + 2 ->\n\t\t\t{1, 14};\n\t\tSizeWithLen < 1073741824 + 4 ->\n\t\t\t{2, 30};\n\t\tSizeWithLen < 4611686018427387904 + 8 ->\n\t\t\t{3, 62}\n\tend.\n\ndo_wait_peer_send_shutdown(StreamRef) ->\n\treceive\n\t\t{quic, peer_send_shutdown, StreamRef, undefined} ->\n\t\t\tok\n\tafter 5000 ->\n\t\t{error, timeout}\n\tend.\n\ndo_wait_stream_aborted(StreamRef) ->\n\treceive\n\t\t{quic, peer_send_aborted, StreamRef, Code} ->\n\t\t\tReason = cow_http3:code_to_error(Code),\n\t\t\t#{reason => Reason};\n\t\t{quic, peer_receive_aborted, StreamRef, Code} ->\n\t\t\tReason = cow_http3:code_to_error(Code),\n\t\t\t#{reason => Reason}\n\tafter 5000 ->\n\t\t{error, timeout}\n\tend.\n\ndo_wait_stream_closed(StreamRef) ->\n\treceive\n\t\t{quic, stream_closed, StreamRef, #{error := Error, is_conn_shutdown := false}} ->\n\t\t\t0 = Error,\n\t\t\tok\n\tafter 5000 ->\n\t\t{error, timeout}\n\tend.\n\ndo_receive_response(StreamRef) ->\n\t{ok, Data} = do_receive_data(StreamRef),\n\t{HLenEnc, HLenBits} = do_guess_int_encoding(Data),\n\t<<\n\t\t1, %% HEADERS frame.\n\t\tHLenEnc:2, HLen:HLenBits,\n\t\tEncodedResponse:HLen/bytes,\n\t\tRest/bits\n\t>> = Data,\n\t{ok, DecodedResponse, _DecData, _DecSt}\n\t\t= cow_qpack:decode_field_section(EncodedResponse, 0, cow_qpack:init(decoder)),\n\tHeaders = maps:from_list(DecodedResponse),\n\t#{<<\"content-length\">> := BodyLen} = Headers,\n\t{DLenEnc, DLenBits} = do_guess_int_encoding(Rest),\n\tBody = case Rest of\n\t\t<<>> ->\n\t\t\t<<>>;\n\t\t<<\n\t\t\t0, %% DATA frame.\n\t\t\tDLenEnc:2, DLen:DLenBits,\n\t\t\tBody0:DLen/bytes\n\t\t>> ->\n\t\t\tBodyLen = integer_to_binary(byte_size(Body0)),\n\t\t\tBody0\n\tend,\n\tok = do_wait_peer_send_shutdown(StreamRef),\n\t#{\n\t\theaders => Headers,\n\t\tbody => Body\n\t}.\n\ndo_wait_connection_closed(Conn) ->\n\treceive\n\t\t{quic, shutdown, Conn, {unknown_quic_status, Code}} ->\n\t\t\tReason = cow_http3:code_to_error(Code),\n\t\t\t#{reason => Reason}\n\tafter 5000 ->\n\t\t{error, timeout}\n\tend.\n\n-endif.\n"
  },
  {
    "path": "test/rfc9114_SUITE_data/client.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgVJakPYfQA1Hr6Gnq\nGYmpMfXpxUi2QwDBrZfw8dBcVqKhRANCAAQDHeeAvjwD7p+Mg1F+G9FBNy+7Wcms\nHEw4sGMzhUL4wjwsqKHpoiuQg3qUXXK0gamx0l77vFjrUc6X1al4+ZM5\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "test/rfc9114_SUITE_data/client.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBtTCCAVugAwIBAgIUeAPi9oyMIE/KRpsRdukfx2eMuuswCgYIKoZIzj0EAwIw\nIDELMAkGA1UEBhMCU0UxETAPBgNVBAoMCE5PQk9EWUFCMB4XDTIzMDcwNTEwMjIy\nMloXDTI0MTExNjEwMjIyMlowMTELMAkGA1UEBhMCU0UxETAPBgNVBAoMCE5PQk9E\nWUFCMQ8wDQYDVQQDDAZjbGllbnQwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQD\nHeeAvjwD7p+Mg1F+G9FBNy+7WcmsHEw4sGMzhUL4wjwsqKHpoiuQg3qUXXK0gamx\n0l77vFjrUc6X1al4+ZM5o2IwYDALBgNVHQ8EBAMCA4gwEQYDVR0RBAowCIIGY2xp\nZW50MB0GA1UdDgQWBBTnhPpO+rSIFAxvkwVjlkKOO2jOeDAfBgNVHSMEGDAWgBSD\nHw8A4XXG3jB1Atrqux7AUsf+KjAKBggqhkjOPQQDAgNIADBFAiEA2qf29EBp2hcL\nsEO7MM0ZLm4gnaMdcxtyneF3+c7Lg3cCIBFTVP8xHlhCJyb8ESV7S052VU0bKQFN\nioyoYtcycxuZ\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/rfc9114_SUITE_data/server.key",
    "content": "-----BEGIN PRIVATE KEY-----\nMIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgvykUYMOS2gW8XTTh\nHgmeJM36NT8GGTNXzzt4sIs0o9ahRANCAATnQOMkKbLFQCZY/cxf8otEJG2tVuG6\nQvLqUdERV2+gzE+4ROGDqbb2Jk1szyz4CfBMB4ZfLA/PdSiO+KrOeOcj\n-----END PRIVATE KEY-----\n"
  },
  {
    "path": "test/rfc9114_SUITE_data/server.pem",
    "content": "-----BEGIN CERTIFICATE-----\nMIIBtTCCAVugAwIBAgIUeAPi9oyMIE/KRpsRdukfx2eMuuowCgYIKoZIzj0EAwIw\nIDELMAkGA1UEBhMCU0UxETAPBgNVBAoMCE5PQk9EWUFCMB4XDTIzMDcwNTEwMjIy\nMloXDTI0MTExNjEwMjIyMlowMTELMAkGA1UEBhMCU0UxETAPBgNVBAoMCE5PQk9E\nWUFCMQ8wDQYDVQQDDAZzZXJ2ZXIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATn\nQOMkKbLFQCZY/cxf8otEJG2tVuG6QvLqUdERV2+gzE+4ROGDqbb2Jk1szyz4CfBM\nB4ZfLA/PdSiO+KrOeOcjo2IwYDALBgNVHQ8EBAMCA4gwEQYDVR0RBAowCIIGc2Vy\ndmVyMB0GA1UdDgQWBBS+Np5J8BtmWU534pm9hqhrG/EQ7zAfBgNVHSMEGDAWgBSD\nHw8A4XXG3jB1Atrqux7AUsf+KjAKBggqhkjOPQQDAgNIADBFAiEApRfjIEJfO1VH\nETgNG3/MzDayYScPocVn4v8U15ygEw8CIFUY3xMZzJ5AmiRe9PhIUgueOKQNMtds\nwdF9+097+Ey0\n-----END CERTIFICATE-----\n"
  },
  {
    "path": "test/rfc9204_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(rfc9204_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n\n-ifdef(COWBOY_QUICER).\n\n-include_lib(\"quicer/include/quicer.hrl\").\n\nall() ->\n\t[{group, h3}].\n\ngroups() ->\n\t%% @todo Enable parallel tests but for this issues in the\n\t%% QUIC accept loop need to be figured out (can't connect\n\t%% concurrently somehow, no backlog?).\n\t[{h3, [], ct_helper:all(?MODULE)}].\n\ninit_per_group(Name = h3, Config) ->\n\tcowboy_test:init_http3(Name, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))}\n\t}, Config).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_routes(_) -> [\n\t{\"localhost\", [\n\t\t{\"/\", hello_h, []}\n\t]}\n].\n\n%% Encoder.\n\n%% 2.1\n%% QPACK preserves the ordering of field lines within\n%% each field section. An encoder MUST emit field\n%% representations in the order they appear in the\n%% input field section.\n\n%% 2.1.1\n%% If the dynamic table does not contain enough room\n%% for a new entry without evicting other entries,\n%% and the entries that would be evicted are not evictable,\n%% the encoder MUST NOT insert that entry into the dynamic\n%% table (including duplicates of existing entries).\n%% In order to avoid this, an encoder that uses the\n%% dynamic table has to keep track of each dynamic\n%% table entry referenced by each field section until\n%% those representations are acknowledged by the decoder;\n%% see Section 4.4.1.\n\n%% 2.1.2\n%% The decoder specifies an upper bound on the number\n%% of streams that can be blocked using the\n%% SETTINGS_QPACK_BLOCKED_STREAMS setting; see Section 5.\n%% An encoder MUST limit the number of streams that could\n%% become blocked to the value of SETTINGS_QPACK_BLOCKED_STREAMS\n%% at all times. If a decoder encounters more blocked streams\n%% than it promised to support, it MUST treat this as a\n%% connection error of type QPACK_DECOMPRESSION_FAILED.\n\n%% 2.1.3\n%% To avoid these deadlocks, an encoder SHOULD NOT\n%% write an instruction unless sufficient stream and\n%% connection flow-control credit is available for\n%% the entire instruction.\n\n%% Decoder.\n\n%% 2.2\n%% The decoder MUST emit field lines in the order their\n%% representations appear in the encoded field section.\n\n%% 2.2.1\n%% While blocked, encoded field section data SHOULD\n%% remain in the blocked stream's flow-control window.\n\n%% If it encounters a Required Insert Count smaller than\n%% expected, it MUST treat this as a connection error of\n%% type QPACK_DECOMPRESSION_FAILED; see Section 2.2.3.\n\n%% If it encounters a Required Insert Count larger than\n%% expected, it MAY treat this as a connection error of\n%% type QPACK_DECOMPRESSION_FAILED.\n\n%% After the decoder finishes decoding a field section\n%% encoded using representations containing dynamic table\n%% references, it MUST emit a Section Acknowledgment\n%% instruction (Section 4.4.1).\n\n%% 2.2.2.2\n%% A decoder with a maximum dynamic table capacity\n%% (Section 3.2.3) equal to zero MAY omit sending Stream\n%% Cancellations, because the encoder cannot have any\n%% dynamic table references.\n\n%% 2.2.3\n%% If the decoder encounters a reference in a field line\n%% representation to a dynamic table entry that has already\n%% been evicted or that has an absolute index greater than\n%% or equal to the declared Required Insert Count (Section 4.5.1),\n%% it MUST treat this as a connection error of type\n%% QPACK_DECOMPRESSION_FAILED.\n\n%% If the decoder encounters a reference in an encoder\n%% instruction to a dynamic table entry that has already\n%% been evicted, it MUST treat this as a connection error\n%% of type QPACK_ENCODER_STREAM_ERROR.\n\n%% Static table.\n\n%% 3.1\n%% When the decoder encounters an invalid static table index\n%% in a field line representation, it MUST treat this as a\n%% connection error of type QPACK_DECOMPRESSION_FAILED.\n%%\n%% If this index is received on the encoder stream, this\n%% MUST be treated as a connection error of type\n%% QPACK_ENCODER_STREAM_ERROR.\n\n%% Dynamic table.\n\n%% 3.2\n%% The dynamic table can contain duplicate entries\n%% (i.e., entries with the same name and same value).\n%% Therefore, duplicate entries MUST NOT be treated\n%% as an error by the decoder.\n\n%% 3.2.2\n%% The encoder MUST NOT cause a dynamic table entry to be\n%% evicted unless that entry is evictable; see Section 2.1.1.\n\n%% It is an error if the encoder attempts to add an entry\n%% that is larger than the dynamic table capacity; the\n%% decoder MUST treat this as a connection error of type\n%% QPACK_ENCODER_STREAM_ERROR.\n\n%% 3.2.3\n%% The encoder MUST NOT set a dynamic table capacity that\n%% exceeds this maximum, but it can choose to use a lower\n%% dynamic table capacity; see Section 4.3.1.\n\n%% When the client's 0-RTT value of the SETTING is zero,\n%% the server MAY set it to a non-zero value in its SETTINGS\n%% frame. If the remembered value is non-zero, the server\n%% MUST send the same non-zero value in its SETTINGS frame.\n%% If it specifies any other value, or omits\n%% SETTINGS_QPACK_MAX_TABLE_CAPACITY from SETTINGS,\n%% the encoder must treat this as a connection error of\n%% type QPACK_DECODER_STREAM_ERROR.\n\n%% When the maximum table capacity is zero, the encoder\n%% MUST NOT insert entries into the dynamic table and\n%% MUST NOT send any encoder instructions on the encoder stream.\n\n%% Wire format.\n\n%% 4.1.1\n%% QPACK implementations MUST be able to decode integers\n%% up to and including 62 bits long.\n\n%% Encoder and decoder streams.\n\ndecoder_reject_multiple(Config) ->\n\tdoc(\"Endpoints must not create multiple decoder streams. (RFC9204 4.2)\"),\n\trfc9114_SUITE:do_critical_reject_multiple(Config, <<3>>).\n\nencoder_reject_multiple(Config) ->\n\tdoc(\"Endpoints must not create multiple encoder streams. (RFC9204 4.2)\"),\n\trfc9114_SUITE:do_critical_reject_multiple(Config, <<2>>).\n\n%% 4.2\n%% The sender MUST NOT close either of these streams,\n%% and the receiver MUST NOT request that the sender close\n%% either of these streams. Closure of either unidirectional\n%% stream type MUST be treated as a connection error of type\n%% H3_CLOSED_CRITICAL_STREAM.\n\ndecoder_local_closed_abort(Config) ->\n\tdoc(\"Endpoints must not close the decoder stream. (RFC9204 4.2)\"),\n\trfc9114_SUITE:do_critical_local_closed_abort(Config, <<3>>).\n\ndecoder_local_closed_graceful(Config) ->\n\tdoc(\"Endpoints must not close the decoder stream. (RFC9204 4.2)\"),\n\trfc9114_SUITE:do_critical_local_closed_graceful(Config, <<3>>).\n\ndecoder_remote_closed_abort(Config) ->\n\tdoc(\"Endpoints must not close the decoder stream. (RFC9204 4.2)\"),\n\t#{conn := Conn} = rfc9114_SUITE:do_connect(Config, #{peer_unidi_stream_count => 3}),\n\t{ok, #{decoder := StreamRef}} = do_wait_unidi_streams(Conn, #{}),\n\t%% Close the control stream.\n\tquicer:async_shutdown_stream(StreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0),\n\t%% The connection should have been closed.\n\t#{reason := h3_closed_critical_stream} = rfc9114_SUITE:do_wait_connection_closed(Conn),\n\tok.\n\nencoder_local_closed_abort(Config) ->\n\tdoc(\"Endpoints must not close the encoder stream. (RFC9204 4.2)\"),\n\trfc9114_SUITE:do_critical_local_closed_abort(Config, <<2>>).\n\nencoder_local_closed_graceful(Config) ->\n\tdoc(\"Endpoints must not close the encoder stream. (RFC9204 4.2)\"),\n\trfc9114_SUITE:do_critical_local_closed_graceful(Config, <<2>>).\n\nencoder_remote_closed_abort(Config) ->\n\tdoc(\"Endpoints must not close the encoder stream. (RFC9204 4.2)\"),\n\t#{conn := Conn} = rfc9114_SUITE:do_connect(Config, #{peer_unidi_stream_count => 3}),\n\t{ok, #{encoder := StreamRef}} = do_wait_unidi_streams(Conn, #{}),\n\t%% Close the control stream.\n\tquicer:async_shutdown_stream(StreamRef, ?QUIC_STREAM_SHUTDOWN_FLAG_ABORT, 0),\n\t%% The connection should have been closed.\n\t#{reason := h3_closed_critical_stream} = rfc9114_SUITE:do_wait_connection_closed(Conn),\n\tok.\n\ndo_wait_unidi_streams(_, Acc=#{decoder := _, encoder := _}) ->\n\t{ok, Acc};\ndo_wait_unidi_streams(Conn, Acc) ->\n\treceive\n\t\t{quic, new_stream, StreamRef, #{flags := Flags}} ->\n\t\t\tok = quicer:setopt(StreamRef, active, true),\n\t\t\ttrue = quicer:is_unidirectional(Flags),\n\t\t\treceive {quic, <<TypeValue>>, StreamRef, _} ->\n\t\t\t\tType = case TypeValue of\n\t\t\t\t\t2 -> encoder;\n\t\t\t\t\t3 -> decoder\n\t\t\t\tend,\n\t\t\t\tdo_wait_unidi_streams(Conn, Acc#{Type => StreamRef})\n\t\t\tafter 5000 ->\n\t\t\t\t{error, timeout}\n\t\t\tend\n\tafter 5000 ->\n\t\t{error, timeout}\n\tend.\n\n%% An endpoint MAY avoid creating an encoder stream if it will\n%% not be used (for example, if its encoder does not wish to\n%% use the dynamic table or if the maximum size of the dynamic\n%% table permitted by the peer is zero).\n\n%% An endpoint MAY avoid creating a decoder stream if its\n%% decoder sets the maximum capacity of the dynamic table to zero.\n\n%% An endpoint MUST allow its peer to create an encoder stream\n%% and a decoder stream even if the connection's settings\n%% prevent their use.\n\n%% Encoder instructions.\n\n%% 4.3.1\n%% The new capacity MUST be lower than or equal to the limit\n%% described in Section 3.2.3. In HTTP/3, this limit is the\n%% value of the SETTINGS_QPACK_MAX_TABLE_CAPACITY parameter\n%% (Section 5) received from the decoder. The decoder MUST\n%% treat a new dynamic table capacity value that exceeds this\n%% limit as a connection error of type QPACK_ENCODER_STREAM_ERROR.\n\n%% Reducing the dynamic table capacity can cause entries to be\n%% evicted; see Section 3.2.2. This MUST NOT cause the eviction\n%% of entries that are not evictable; see Section 2.1.1.\n\n%% Decoder instructions.\n\n%% 4.4.1\n%% If an encoder receives a Section Acknowledgment instruction\n%% referring to a stream on which every encoded field section\n%% with a non-zero Required Insert Count has already been\n%% acknowledged, this MUST be treated as a connection error\n%% of type QPACK_DECODER_STREAM_ERROR.\n\n%% 4.4.3\n%% An encoder that receives an Increment field equal to zero,\n%% or one that increases the Known Received Count beyond what\n%% the encoder has sent, MUST treat this as a connection error\n%% of type QPACK_DECODER_STREAM_ERROR.\n\n%% Field line representation.\n\n%% 4.5.1.1\n%% If the decoder encounters a value of EncodedInsertCount that\n%% could not have been produced by a conformant encoder, it MUST\n%% treat this as a connection error of type QPACK_DECOMPRESSION_FAILED.\n\n%% 4.5.1.2\n%% The value of Base MUST NOT be negative. Though the protocol\n%% might operate correctly with a negative Base using post-Base\n%% indexing, it is unnecessary and inefficient. An endpoint MUST\n%% treat a field block with a Sign bit of 1 as invalid if the\n%% value of Required Insert Count is less than or equal to the\n%% value of Delta Base.\n\n%% 4.5.4\n%% When the 'N' bit is set, the encoded field line MUST always\n%% be encoded with a literal representation. In particular,\n%% when a peer sends a field line that it received represented\n%% as a literal field line with the 'N' bit set, it MUST use a\n%% literal representation to forward this field line. This bit\n%% is intended for protecting field values that are not to be\n%% put at risk by compressing them; see Section 7.1 for more details.\n\n%% Configuration.\n\n%% 5\n%% SETTINGS_QPACK_MAX_TABLE_CAPACITY\n%% SETTINGS_QPACK_BLOCKED_STREAMS\n\n%% Security considerations.\n\n%% 7.1.2\n%% (security if used as a proxy merging many connections into one)\n%% An ideal solution segregates access to the dynamic table\n%% based on the entity that is constructing the message.\n%% Field values that are added to the table are attributed\n%% to an entity, and only the entity that created a particular\n%% value can extract that value.\n\n%% 7.1.3\n%% An intermediary MUST NOT re-encode a value that uses a\n%% literal representation with the 'N' bit set with another\n%% representation that would index it. If QPACK is used for\n%% re-encoding, a literal representation with the 'N' bit set\n%% MUST be used. If HPACK is used for re-encoding, the\n%% never-indexed literal representation (see Section 6.2.3\n%% of [RFC7541]) MUST be used.\n\n%% 7.4\n%% An implementation has to set a limit for the values it\n%% accepts for integers, as well as for the encoded length;\n%% see Section 4.1.1. In the same way, it has to set a limit\n%% to the length it accepts for string literals; see Section 4.1.2.\n%% These limits SHOULD be large enough to process the largest\n%% individual field the HTTP implementation can be configured\n%% to accept.\n\n%% If an implementation encounters a value larger than it is\n%% able to decode, this MUST be treated as a stream error of\n%% type QPACK_DECOMPRESSION_FAILED if on a request stream or\n%% a connection error of the appropriate type if on the\n%% encoder or decoder stream.\n\n-endif.\n"
  },
  {
    "path": "test/rfc9220_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(rfc9220_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n\nall() ->\n\t[{group, enabled}].\n\ngroups() ->\n\tTests = ct_helper:all(?MODULE),\n\t[{enabled, [], Tests}]. %% @todo Enable parallel when all is better.\n\ninit_per_group(Name = enabled, Config) ->\n\tcowboy_test:init_http3(Name, #{\n\t\tenable_connect_protocol => true,\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))}\n\t}, Config).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_routes(_) -> [\n\t{\"localhost\", [\n\t\t{\"/ws\", ws_echo, []}\n\t]}\n].\n\n% The SETTINGS_ENABLE_CONNECT_PROTOCOL SETTINGS Parameter.\n\n% The new parameter name is SETTINGS_ENABLE_CONNECT_PROTOCOL.  The\n% value of the parameter MUST be 0 or 1.\n\n%    Upon receipt of SETTINGS_ENABLE_CONNECT_PROTOCOL with a value of 1 a\n%    client MAY use the Extended CONNECT definition of this document when\n%    creating new streams.  Receipt of this parameter by a server does not\n%    have any impact.\n%% @todo ignore_client_enable_setting(Config) ->\n\nreject_handshake_when_disabled(Config0) ->\n\tdoc(\"Extended CONNECT requests MUST be rejected with a \"\n\t\t\"H3_MESSAGE_ERROR stream error when enable_connect_protocol=false. \"\n\t\t\"(RFC9220, RFC8441 4)\"),\n\tConfig = cowboy_test:init_http3(disabled, #{\n\t\tenable_connect_protocol => false,\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))}\n\t}, Config0),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 0.\n\t#{\n\t\tconn := Conn,\n\t\tsettings := Settings\n\t} = rfc9114_SUITE:do_connect(Config),\n\tcase Settings of\n\t\t#{enable_connect_protocol := false} -> ok;\n\t\t_ when map_size(Settings) =:= 0 -> ok\n\tend,\n\t%% Send a CONNECT :protocol request to upgrade the stream to Websocket.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef),\n\tok.\n\nreject_handshake_disabled_by_default(Config0) ->\n\tdoc(\"Extended CONNECT requests MUST be rejected with a \"\n\t\t\"H3_MESSAGE_ERROR stream error when enable_connect_protocol=false. \"\n\t\t\"(RFC9220, RFC8441 4)\"),\n\tConfig = cowboy_test:init_http3(disabled, #{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config0))}\n\t}, Config0),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 0.\n\t#{\n\t\tconn := Conn,\n\t\tsettings := Settings\n\t} = rfc9114_SUITE:do_connect(Config),\n\tcase Settings of\n\t\t#{enable_connect_protocol := false} -> ok;\n\t\t_ when map_size(Settings) =:= 0 -> ok\n\tend,\n\t%% Send a CONNECT :protocol request to upgrade the stream to Websocket.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef),\n\tok.\n\n% The Extended CONNECT Method.\n\naccept_uppercase_pseudo_header_protocol(Config) ->\n\tdoc(\"The :protocol pseudo header is case insensitive. (RFC9220, RFC8441 4, RFC9110 7.8)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send a CONNECT :protocol request to upgrade the stream to Websocket.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"WEBSOCKET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% Receive a 200 response.\n\t{ok, Data} = rfc9114_SUITE:do_receive_data(StreamRef),\n\t{HLenEnc, HLenBits} = rfc9114_SUITE:do_guess_int_encoding(Data),\n\t<<\n\t\t1, %% HEADERS frame.\n\t\tHLenEnc:2, HLen:HLenBits,\n\t\tEncodedResponse:HLen/bytes\n\t>> = Data,\n\t{ok, DecodedResponse, _DecData, _DecSt}\n\t\t= cow_qpack:decode_field_section(EncodedResponse, 0, cow_qpack:init(decoder)),\n\t#{<<\":status\">> := <<\"200\">>} = maps:from_list(DecodedResponse),\n\tok.\n\nreject_many_pseudo_header_protocol(Config) ->\n\tdoc(\"An extended CONNECT request containing more than one \"\n\t\t\"protocol component must be rejected with a H3_MESSAGE_ERROR \"\n\t\t\"stream error. (RFC9220, RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request with more than one :protocol pseudo-header.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":protocol\">>, <<\"mqtt\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef),\n\tok.\n\nreject_unknown_pseudo_header_protocol(Config) ->\n\tdoc(\"An extended CONNECT request containing more than one \"\n\t\t\"protocol component must be rejected with a 501 Not Implemented \"\n\t\t\"response. (RFC9220, RFC8441 4)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request with an unknown protocol.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"mqtt\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been rejected with a 501 Not Implemented.\n\t#{headers := #{<<\":status\">> := <<\"501\">>}} = rfc9114_SUITE:do_receive_response(StreamRef),\n\tok.\n\nreject_invalid_pseudo_header_protocol(Config) ->\n\tdoc(\"An extended CONNECT request with an invalid protocol \"\n\t\t\"component must be rejected with a 501 Not Implemented \"\n\t\t\"response. (RFC9220, RFC8441 4)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request with an invalid protocol.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket mqtt\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been rejected with a 501 Not Implemented.\n\t#{headers := #{<<\":status\">> := <<\"501\">>}} = rfc9114_SUITE:do_receive_response(StreamRef),\n\tok.\n\nreject_missing_pseudo_header_scheme(Config) ->\n\tdoc(\"An extended CONNECT request whtout a scheme component \"\n\t\t\"must be rejected with a H3_MESSAGE_ERROR stream error. \"\n\t\t\"(RFC9220, RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request without a :scheme pseudo-header.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef),\n\tok.\n\nreject_missing_pseudo_header_path(Config) ->\n\tdoc(\"An extended CONNECT request whtout a path component \"\n\t\t\"must be rejected with a H3_MESSAGE_ERROR stream error. \"\n\t\t\"(RFC9220, RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request without a :path pseudo-header.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef),\n\tok.\n\n% On requests bearing the :protocol pseudo-header, the :authority\n% pseudo-header field is interpreted according to Section 8.1.2.3 of\n% [RFC7540] instead of Section 8.3 of [RFC7540].  In particular the\n% server MUST not make a new TCP connection to the host and port\n% indicated by the :authority.\n\nreject_missing_pseudo_header_authority(Config) ->\n\tdoc(\"An extended CONNECT request whtout an authority component \"\n\t\t\"must be rejected with a H3_MESSAGE_ERROR stream error. \"\n\t\t\"(RFC9220, RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request without an :authority pseudo-header.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef),\n\tok.\n\n% Using Extended CONNECT To Bootstrap The WebSocket Protocol.\n\nreject_missing_pseudo_header_protocol(Config) ->\n\tdoc(\"An extended CONNECT request whtout a protocol component \"\n\t\t\"must be rejected with a H3_MESSAGE_ERROR stream error. \"\n\t\t\"(RFC9220, RFC9114 4.3.1, RFC9114 4.1.2)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request without a :protocol pseudo-header.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef),\n\tok.\n\n% The scheme of the Target URI [RFC7230] MUST be https for wss schemed\n% WebSockets. HTTP/3 does not provide support for ws schemed WebSockets.\n% The websocket URI is still used for proxy autoconfiguration.\n\nreject_connection_header(Config) ->\n\tdoc(\"An extended CONNECT request with a connection header \"\n\t\t\"must be rejected with a H3_MESSAGE_ERROR stream error. \"\n\t\t\"(RFC9220, RFC8441 4, RFC9114 4.2, RFC9114 4.5, RFC9114 4.1.2)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request with a connection header.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"connection\">>, <<\"upgrade\">>},\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef),\n\tok.\n\nreject_upgrade_header(Config) ->\n\tdoc(\"An extended CONNECT request with a upgrade header \"\n\t\t\"must be rejected with a H3_MESSAGE_ERROR stream error. \"\n\t\t\"(RFC9220, RFC8441 4, RFC9114 4.2, RFC9114 4.5, RFC9114 4.1.2)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send an extended CONNECT request with a upgrade header.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"upgrade\">>, <<\"websocket\">>},\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% The stream should have been aborted.\n\t#{reason := h3_message_error} = rfc9114_SUITE:do_wait_stream_aborted(StreamRef),\n\tok.\n\n%    After successfully processing the opening handshake the peers should\n%    proceed with The WebSocket Protocol [RFC6455] using the HTTP/2 stream\n%    from the CONNECT transaction as if it were the TCP connection\n%    referred to in [RFC6455].  The state of the WebSocket connection at\n%    this point is OPEN as defined by [RFC6455], Section 4.1.\n%% @todo I'm guessing we should test for things like RST_STREAM,\n%% closing the connection and others?\n\n% Examples.\n\naccept_handshake_when_enabled(Config) ->\n\tdoc(\"Confirm the example for Websocket over HTTP/3 works. (RFC9220, RFC8441 5.1)\"),\n\t%% Connect to server and confirm that SETTINGS_ENABLE_CONNECT_PROTOCOL = 1.\n\t#{conn := Conn, settings := Settings} = rfc9114_SUITE:do_connect(Config),\n\t#{enable_connect_protocol := true} = Settings,\n\t%% Send a CONNECT :protocol request to upgrade the stream to Websocket.\n\t{ok, StreamRef} = quicer:start_stream(Conn, #{}),\n\t{ok, EncodedRequest, _EncData, _EncSt} = cow_qpack:encode_field_section([\n\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":path\">>, <<\"/ws\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t], 0, cow_qpack:init(encoder)),\n\t{ok, _} = quicer:send(StreamRef, [\n\t\t<<1>>, %% HEADERS frame.\n\t\tcow_http3:encode_int(iolist_size(EncodedRequest)),\n\t\tEncodedRequest\n\t]),\n\t%% Receive a 200 response.\n\t{ok, Data} = rfc9114_SUITE:do_receive_data(StreamRef),\n\t{HLenEnc, HLenBits} = rfc9114_SUITE:do_guess_int_encoding(Data),\n\t<<\n\t\t1, %% HEADERS frame.\n\t\tHLenEnc:2, HLen:HLenBits,\n\t\tEncodedResponse:HLen/bytes\n\t>> = Data,\n\t{ok, DecodedResponse, _DecData, _DecSt}\n\t\t= cow_qpack:decode_field_section(EncodedResponse, 0, cow_qpack:init(decoder)),\n\t#{<<\":status\">> := <<\"200\">>} = maps:from_list(DecodedResponse),\n\t%% Masked text hello echoed back clear by the server.\n\tMask = 16#37fa213d,\n\tMaskedHello = ws_SUITE:do_mask(<<\"Hello\">>, Mask, <<>>),\n\t{ok, _} = quicer:send(StreamRef, cow_http3:data(\n\t\t<<1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary>>)),\n\t{ok, WsData} = rfc9114_SUITE:do_receive_data(StreamRef),\n\t<<\n\t\t0, %% DATA frame.\n\t\t0:2, 7:6, %% Length (2 bytes header + \"Hello\").\n\t\t1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\" %% Websocket frame.\n\t>> = WsData,\n\tok.\n\n%% Closing a Websocket stream.\n\n%    The HTTP/3 stream closure is also analogous to the TCP connection\n%    closure of [RFC6455]. Orderly TCP-level closures are represented\n%    as a FIN bit on the stream (Section 4.4 of [HTTP/3]). RST exceptions\n%    are represented with a stream error (Section 8 of [HTTP/3]) of type\n%    H3_REQUEST_CANCELLED (Section 8.1 of [HTTP/3]).\n\n%% @todo client close frame with FIN\n%% @todo server close frame with FIN\n%% @todo client other frame with FIN\n%% @todo server other frame with FIN\n%% @todo client close connection\n"
  },
  {
    "path": "test/security_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(security_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n-import(cowboy_test, [raw_open/1]).\n-import(cowboy_test, [raw_send/2]).\n-import(cowboy_test, [raw_recv_head/1]).\n-import(cowboy_test, [raw_recv/3]).\n\n%% ct.\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tTests = [nc_rand, nc_zero],\n\tH1Tests = [slowloris, slowloris_chunks],\n\tH2CTests = [\n\t\thttp2_cancel_flood,\n\t\thttp2_data_dribble,\n\t\thttp2_empty_frame_flooding_data,\n\t\thttp2_empty_frame_flooding_headers_continuation,\n\t\thttp2_empty_frame_flooding_push_promise,\n\t\thttp2_infinite_continuations,\n\t\thttp2_ping_flood,\n\t\thttp2_reset_flood,\n\t\thttp2_settings_flood,\n\t\thttp2_zero_length_header_leak\n\t],\n\t[\n\t\t{http, [parallel], Tests ++ H1Tests},\n\t\t{https, [parallel], Tests ++ H1Tests},\n\t\t{h2, [parallel], Tests},\n\t\t{h2c, [parallel], Tests ++ H2CTests},\n\t\t{h3, [], Tests},\n\t\t{http_compress, [parallel], Tests ++ H1Tests},\n\t\t{https_compress, [parallel], Tests ++ H1Tests},\n\t\t{h2_compress, [parallel], Tests},\n\t\t{h2c_compress, [parallel], Tests ++ H2CTests},\n\t\t{h3_compress, [], Tests}\n\t].\n\ninit_per_suite(Config) ->\n\tct_helper:create_static_dir(config(priv_dir, Config) ++ \"/static\"),\n\tConfig.\n\nend_per_suite(Config) ->\n\tct_helper:delete_static_dir(config(priv_dir, Config) ++ \"/static\").\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\n%% Routes.\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{\"localhost\", [\n\t\t{\"/\", hello_h, []},\n\t\t{\"/echo/:key\", echo_h, []},\n\t\t{\"/delay_hello\", delay_hello_h, 1000},\n\t\t{\"/long_polling\", long_polling_h, []},\n\t\t{\"/resp/:key[/:arg]\", resp_h, []}\n\t]}]).\n\n%% Tests.\n\nhttp2_cancel_flood(Config) ->\n\tdoc(\"Confirm that Cowboy detects the rapid reset attack. (CVE-2023-44487)\"),\n\tdo_http2_cancel_flood(Config, 1, 500),\n\tdo_http2_cancel_flood(Config, 10, 50),\n\tdo_http2_cancel_flood(Config, 500, 1),\n\tok.\n\ndo_http2_cancel_flood(Config, NumStreamsPerBatch, NumBatches) ->\n\t{ok, Socket} = rfc7540_SUITE:do_handshake(Config),\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/delay_hello\">>}\n\t]),\n\tAllStreamIDs = lists:seq(1, NumBatches * NumStreamsPerBatch * 2, 2),\n\t_ = lists:foldl(\n\t\tfun (_BatchNumber, AvailableStreamIDs) ->\n\t\t\t%% Take a bunch of IDs from the available stream IDs.\n\t\t\t%% Send HEADERS for all these and then cancel them.\n\t\t\t{IDs, RemainingStreamIDs} = lists:split(NumStreamsPerBatch, AvailableStreamIDs),\n\t\t\t_ = gen_tcp:send(Socket, [cow_http2:headers(ID, fin, HeadersBlock) || ID <- IDs]),\n\t\t\t_ = gen_tcp:send(Socket, [<<4:24, 3:8, 0:8, ID:32, 8:32>> || ID <- IDs]),\n\t\t\tRemainingStreamIDs\n\t\tend,\n\t\tAllStreamIDs,\n\t\tlists:seq(1, NumBatches, 1)),\n\t%% When Cowboy detects a flood it must close the connection.\n\tcase gen_tcp:recv(Socket, 17, 6000) of\n\t\t{ok, <<_:24, 7:8, 0:8, 0:32, _LastStreamId:32, 11:32>>} ->\n\t\t\t%% GOAWAY with error code 11 = ENHANCE_YOUR_CALM.\n\t\t\tok;\n\t\t%% We also accept the connection being closed immediately,\n\t\t%% which may happen because we send the GOAWAY right before closing.\n\t\t{error, closed} ->\n\t\t\tok\n\tend.\n\nhttp2_data_dribble(Config) ->\n\tdoc(\"Request a very large response then update the window 1 byte at a time. (CVE-2019-9511)\"),\n\t{ok, Socket} = rfc7540_SUITE:do_handshake(Config),\n\t%% Send a GET request for a very large response.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/resp/stream_body/loop\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a response with a few DATA frames draining the window.\n\t{ok, <<SkipLen:24, 1:8, _:8, 1:32>>} = gen_tcp:recv(Socket, 9, 1000),\n\t{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),\n\t{ok, <<16384:24, 0:8, 0:8, 1:32, _:16384/unit:8>>} = gen_tcp:recv(Socket, 9 + 16384, 1000),\n\t{ok, <<16384:24, 0:8, 0:8, 1:32, _:16384/unit:8>>} = gen_tcp:recv(Socket, 9 + 16384, 1000),\n\t{ok, <<16384:24, 0:8, 0:8, 1:32, _:16384/unit:8>>} = gen_tcp:recv(Socket, 9 + 16384, 1000),\n\t{ok, <<16383:24, 0:8, 0:8, 1:32, _:16383/unit:8>>} = gen_tcp:recv(Socket, 9 + 16383, 1000),\n\t%% Send WINDOW_UPDATE frames with a value of 1. The server should\n\t%% not attempt to send data until the window is over a configurable threshold.\n\tok = gen_tcp:send(Socket, [\n\t\tcow_http2:window_update(1),\n\t\tcow_http2:window_update(1, 1)\n\t]),\n\t{error, timeout} = gen_tcp:recv(Socket, 0, 1000),\n\tok.\n\nhttp2_empty_frame_flooding_data(Config) ->\n\tdoc(\"Confirm that Cowboy detects empty DATA frame flooding. (CVE-2019-9518)\"),\n\t{ok, Socket} = rfc7540_SUITE:do_handshake(Config),\n\t%% Send a POST request followed by many empty DATA frames.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/echo/read_body\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),\n\t_ = [gen_tcp:send(Socket, cow_http2:data(1, nofin, <<>>)) || _ <- lists:seq(1, 20000)],\n\t%% When Cowboy detects a flood it must close the connection.\n\t%% We skip WINDOW_UPDATE frames sent when Cowboy starts to read the body.\n\tcase gen_tcp:recv(Socket, 43, 6000) of\n\t\t{ok, <<_:26/unit:8, _:24, 7:8, _:72, 11:32>>} ->\n\t\t\tok;\n\t\t%% We also accept the connection being closed immediately,\n\t\t%% which may happen because we send the GOAWAY right before closing.\n\t\t{error, closed} ->\n\t\t\tok;\n\t\t%% At least on Windows this might also occur.\n\t\t{error, enotconn} ->\n\t\t\tok\n\tend.\n\nhttp2_empty_frame_flooding_headers_continuation(Config) ->\n\tdoc(\"Confirm that Cowboy detects empty HEADERS/CONTINUATION frame flooding. (CVE-2019-9518)\"),\n\t{ok, Socket} = rfc7540_SUITE:do_handshake(Config),\n\t%% Send many empty HEADERS/CONTINUATION frames before the headers.\n\tok = gen_tcp:send(Socket, <<0:24, 1:8, 0:9, 1:31>>),\n\t_ = [gen_tcp:send(Socket, <<0:24, 9:8, 0:9, 1:31>>) || _ <- lists:seq(1, 20000)],\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"POST\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tLen = iolist_size(HeadersBlock),\n\t_ = gen_tcp:send(Socket, [<<Len:24, 9:8, 0:5, 1:1, 0:1, 1:1, 0:1, 1:31>>, HeadersBlock]),\n\t%% When Cowboy detects a flood it must close the connection.\n\tcase gen_tcp:recv(Socket, 17, 6000) of\n\t\t{ok, <<_:24, 7:8, _:72, 11:32>>} ->\n\t\t\tok;\n\t\t%% We also accept the connection being closed immediately,\n\t\t%% which may happen because we send the GOAWAY right before closing.\n\t\t{error, closed} ->\n\t\t\tok;\n\t\t%% At least on Windows this might also occur.\n\t\t{error, enotconn} ->\n\t\t\tok\n\tend.\n\nhttp2_empty_frame_flooding_push_promise(Config) ->\n\tdoc(\"Confirm that Cowboy detects empty PUSH_PROMISE frame flooding. (CVE-2019-9518)\"),\n\t{ok, Socket} = rfc7540_SUITE:do_handshake(Config),\n\t%% Send a HEADERS frame to which we will attach a PUSH_PROMISE.\n\t%% We use nofin in order to keep the stream alive.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/long_polling\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),\n\t%% Send nofin PUSH_PROMISE frame without any data.\n\tok = gen_tcp:send(Socket, <<4:24, 5:8, 0:8, 0:1, 1:31, 0:1, 3:31>>),\n\t%% Receive a PROTOCOL_ERROR connection error.\n\t%%\n\t%% Cowboy rejects all PUSH_PROMISE frames therefore no flooding\n\t%% can take place.\n\t{ok, <<_:24, 7:8, _:72, 1:32>>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\nhttp2_infinite_continuations(Config) ->\n\tdoc(\"Confirm that Cowboy rejects CONTINUATION frames when the \"\n\t\t\"total size of HEADERS + CONTINUATION(s) exceeds the limit. (VU#421644)\"),\n\t{ok, Socket} = rfc7540_SUITE:do_handshake(Config),\n\t%% Send a HEADERS frame followed by a large number\n\t%% of continuation frames.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tHeadersBlockLen = iolist_size(HeadersBlock),\n\tok = gen_tcp:send(Socket, [\n\t\t%% HEADERS frame.\n\t\t<<\n\t\t\tHeadersBlockLen:24, 1:8, 0:5,\n\t\t\t0:1, %% END_HEADERS\n\t\t\t0:1,\n\t\t\t1:1, %% END_STREAM\n\t\t\t0:1,\n\t\t\t1:31 %% Stream ID.\n\t\t>>,\n\t\tHeadersBlock,\n\t\t%% CONTINUATION frames.\n\t\t[<<1024:24, 9:8, 0:8, 0:1, 1:31, 0:1024/unit:8>>\n\t\t\t|| _ <- lists:seq(1, 100)]\n\t]),\n\t%% Receive an ENHANCE_YOUR_CALM connection error.\n\t{ok, <<_:24, 7:8, _:72, 11:32>>} = gen_tcp:recv(Socket, 17, 6000),\n\tok.\n\n%% @todo http2_internal_data_buffering(Config) -> I do not know how to test this.\n%\tdoc(\"Request many very large responses, with a larger than necessary window size, \"\n%\t\t\"but do not attempt to read from the socket. (CVE-2019-9517)\"),\n\nhttp2_ping_flood(Config) ->\n\tdoc(\"Confirm that Cowboy detects PING floods. (CVE-2019-9512)\"),\n\t{ok, Socket} = rfc7540_SUITE:do_handshake(Config),\n\t%% Flood the server with PING frames.\n\t_ = [gen_tcp:send(Socket, cow_http2:ping(0)) || _ <- lists:seq(1, 20000)],\n\t%% Receive a number of PING ACK frames in return, following by the closing of the connection.\n\ttry\n\t\t[case gen_tcp:recv(Socket, 17, 6000) of\n\t\t\t{ok, <<8:24, 6:8, _:7, 1:1, _:32, 0:64>>} -> ok;\n\t\t\t{ok, <<_:24, 7:8, _:72, 11:32>>} -> throw(goaway);\n\t\t\t%% We also accept the connection being closed immediately,\n\t\t\t%% which may happen because we send the GOAWAY right before closing.\n\t\t\t{error, closed} -> throw(goaway)\n\t\tend || _ <- lists:seq(1, 20000)],\n\t\terror(flood_successful)\n\tcatch throw:goaway ->\n\t\tok\n\tend.\n\nhttp2_reset_flood(Config) ->\n\tdoc(\"Confirm that Cowboy detects reset floods. (CVE-2019-9514)\"),\n\t{ok, Socket} = rfc7540_SUITE:do_handshake(Config),\n\t%% Flood the server with HEADERS frames without a :method pseudo-header.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\t_ = [gen_tcp:send(Socket, cow_http2:headers(ID, fin, HeadersBlock)) || ID <- lists:seq(1, 100, 2)],\n\t%% Receive a number of RST_STREAM frames in return, following by the closing of the connection.\n\ttry\n\t\t[case gen_tcp:recv(Socket, 13, 6000) of\n\t\t\t{ok, <<_:24, 3:8, _:8, ID:32, 1:32>>} -> ok;\n\t\t\t{ok, <<_:24, 7:8, _:72>>} ->\n\t\t\t\t{ok, <<11:32>>} = gen_tcp:recv(Socket, 4, 1000),\n\t\t\t\tthrow(goaway);\n\t\t\t%% We also accept the connection being closed immediately,\n\t\t\t%% which may happen because we send the GOAWAY right before closing.\n\t\t\t{error, closed} ->\n\t\t\t\tthrow(goaway)\n\t\tend || ID <- lists:seq(1, 100, 2)],\n\t\terror(flood_successful)\n\tcatch throw:goaway ->\n\t\tok\n\tend.\n\n%% @todo If we ever implement the PRIORITY mechanism, this test should\n%% be implemented as well. CVE-2019-9513 https://www.kb.cert.org/vuls/id/605641/\n%% http2_resource_loop\n\nhttp2_settings_flood(Config) ->\n\tdoc(\"Confirm that Cowboy detects SETTINGS floods. (CVE-2019-9515)\"),\n\t{ok, Socket} = rfc7540_SUITE:do_handshake(Config),\n\t%% Flood the server with empty SETTINGS frames.\n\t_ = [gen_tcp:send(Socket, cow_http2:settings(#{})) || _ <- lists:seq(1, 20000)],\n\t%% Receive a number of SETTINGS ACK frames in return, following by the closing of the connection.\n\ttry\n\t\t[case gen_tcp:recv(Socket, 9, 6000) of\n\t\t\t{ok, <<0:24, 4:8, 0:7, 1:1, 0:32>>} -> ok;\n\t\t\t{ok, <<_:24, 7:8, _:40>>} ->\n\t\t\t\t{ok, <<_:32, 11:32>>} = gen_tcp:recv(Socket, 8, 1000),\n\t\t\t\tthrow(goaway);\n\t\t\t%% We also accept the connection being closed immediately,\n\t\t\t%% which may happen because we send the GOAWAY right before closing.\n\t\t\t{error, closed} ->\n\t\t\t\tthrow(goaway)\n\t\tend || _ <- lists:seq(1, 20000)],\n\t\terror(flood_successful)\n\tcatch throw:goaway ->\n\t\tok\n\tend.\n\nhttp2_zero_length_header_leak(Config) ->\n\tdoc(\"Confirm that Cowboy rejects HEADERS frame with a 0-length header name. (CVE-2019-9516)\"),\n\t{ok, Socket} = rfc7540_SUITE:do_handshake(Config),\n\t%% Send a GET request with a 0-length header name.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>},\n\t\t{<<>>, <<\"CVE-2019-9516\">>}\n\t]),\n\tok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a PROTOCOL_ERROR stream error.\n\t{ok, <<_:24, 3:8, _:8, 1:32, 1:32>>} = gen_tcp:recv(Socket, 13, 6000),\n\tok.\n\nnc_rand(Config) ->\n\tdoc(\"Throw random garbage at the server, then check if it's still up.\"),\n\tdo_nc(Config, \"/dev/urandom\").\n\nnc_zero(Config) ->\n\tdoc(\"Throw zeroes at the server, then check if it's still up.\"),\n\tdo_nc(Config, \"/dev/zero\").\n\ndo_nc(Config, Input) ->\n\tCat = os:find_executable(\"cat\"),\n\tNc = os:find_executable(\"nc\"),\n\tcase {Cat, Nc} of\n\t\t{false, _} ->\n\t\t\t{skip, \"The cat executable was not found.\"};\n\t\t{_, false} ->\n\t\t\t{skip, \"The nc executable was not found.\"};\n\t\t_ ->\n\t\t\tStrPort = integer_to_list(config(port, Config)),\n\t\t\t_ = [\n\t\t\t\tos:cmd(\"cat \" ++ Input ++ \" | nc localhost \" ++ StrPort)\n\t\t\t|| _ <- lists:seq(1, 100)],\n\t\t\tConnPid = gun_open(Config),\n\t\t\tRef = gun:get(ConnPid, \"/\"),\n\t\t\t{response, _, 200, _} = gun:await(ConnPid, Ref),\n\t\t\tok\n\tend.\n\nslowloris(Config) ->\n\tdoc(\"Send request headers one byte at a time. \"\n\t\t\"Confirm that the connection gets closed.\"),\n\tClient = raw_open(Config),\n\ttry\n\t\t[begin\n\t\t\tok = raw_send(Client, [C]),\n\t\t\ttimer:sleep(250)\n\t\tend || C <- \"GET / HTTP/1.1\\r\\nHost: localhost\\r\\n\"\n\t\t\t\"User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US)\\r\\n\"\n\t\t\t\"Cookie: name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\r\\n\\r\\n\"],\n\t\terror(failure)\n\tcatch error:{badmatch, _} ->\n\t\tok\n\tend.\n\nslowloris_chunks(Config) ->\n\tdoc(\"Send request headers one line at a time. \"\n\t\t\"Confirm that the connection gets closed.\"),\n\tClient = raw_open(Config),\n\tok = raw_send(Client, \"GET / HTTP/1.1\\r\\n\"),\n\ttimer:sleep(300),\n\tok = raw_send(Client, \"Host: localhost\\r\\n\"),\n\ttimer:sleep(300),\n\tData = raw_recv_head(Client),\n\t{'HTTP/1.1', 408, _, Rest} = cow_http:parse_status_line(Data),\n\t{Headers, _} = cow_http:parse_headers(Rest),\n\t{_, <<\"close\">>} = lists:keyfind(<<\"connection\">>, 1, Headers),\n\t{error, closed} = raw_recv(Client, 0, 1000).\n"
  },
  {
    "path": "test/static_handler_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(static_handler_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n\n%% Import useful functions from req_SUITE.\n%% @todo Maybe move these functions to cowboy_test.\n-import(req_SUITE, [do_get/2]).\n-import(req_SUITE, [do_get/3]).\n-import(req_SUITE, [do_maybe_h3_error3/1]).\n\n%% ct.\n\nall() ->\n\tcowboy_test:common_all() ++ [\n\t\t{group, http_no_sendfile},\n\t\t{group, h2c_no_sendfile}\n\t].\n\ngroups() ->\n\tAllTests = ct_helper:all(?MODULE),\n\t%% The directory tests are shared between dir and priv_dir options.\n\tDirTests = lists:usort([F || {F, 1} <- ?MODULE:module_info(exports),\n\t\tstring:substr(atom_to_list(F), 1, 4) =:= \"dir_\"\n\t]),\n\tOtherTests = AllTests -- DirTests,\n\tGroupTests = OtherTests ++ [\n\t\t{dir, [parallel], DirTests},\n\t\t{priv_dir, [parallel], DirTests}\n\t],\n\tGroupTestsNoParallel = OtherTests ++ [\n\t\t{dir, [], DirTests},\n\t\t{priv_dir, [], DirTests}\n\t],\n\t[\n\t\t{http, [parallel], GroupTests},\n\t\t{https, [parallel], GroupTests},\n\t\t{h2, [parallel], GroupTests},\n\t\t{h2c, [parallel], GroupTests},\n\t\t{h3, [], GroupTestsNoParallel}, %% @todo Enable parallel when it works better.\n\t\t{http_compress, [parallel], GroupTests},\n\t\t{https_compress, [parallel], GroupTests},\n\t\t{h2_compress, [parallel], GroupTests},\n\t\t{h2c_compress, [parallel], GroupTests},\n\t\t{h3_compress, [], GroupTestsNoParallel}, %% @todo Enable parallel when it works better.\n\t\t%% No real need to test sendfile disabled against https, h2 or h3.\n\t\t{http_no_sendfile, [parallel], GroupTests},\n\t\t{h2c_no_sendfile, [parallel], GroupTests}\n\t].\n\ninit_per_suite(Config) ->\n\t%% Two static folders are created: one in ct_helper's private directory,\n\t%% and one in the test run private directory.\n\tPrivDir = code:priv_dir(ct_helper) ++ \"/static\",\n\tStaticDir = config(priv_dir, Config) ++ \"/static\",\n\tct_helper:create_static_dir(PrivDir),\n\tct_helper:create_static_dir(StaticDir),\n\tinit_large_file(PrivDir ++ \"/large.bin\"),\n\tinit_large_file(StaticDir ++ \"/large.bin\"),\n\t%% Add a simple Erlang application archive containing one file\n\t%% in its priv directory.\n\ttrue = code:add_pathz(filename:join(\n\t\t[config(data_dir, Config), \"static_files_app.ez\", \"static_files_app\", \"ebin\"])),\n\tok = application:load(static_files_app),\n\t%% A special folder contains files of 1 character from 1 to 127\n\t%% excluding / and \\ as they are always rejected.\n\tCharDir = config(priv_dir, Config) ++ \"/char\",\n\tok = filelib:ensure_dir(CharDir ++ \"/file\"),\n\tChars0 = lists:flatten([case file:write_file(CharDir ++ [$/, C], [C]) of\n\t\tok -> C;\n\t\t{error, _} -> []\n\tend || C <- (lists:seq(1, 127) -- \"/\\\\\")]),\n\t%% Determine whether we are on a case insensitive filesystem and\n\t%% remove uppercase characters in that case. On case insensitive\n\t%% filesystems we end up overwriting the \"A\" file with the \"a\" contents.\n\t{CaseSensitive, Chars} = case file:read_file(CharDir ++ \"/A\") of\n\t\t{ok, <<\"A\">>} -> {true, Chars0};\n\t\t{ok, <<\"a\">>} -> {false, Chars0 -- \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"}\n\tend,\n\t[{static_dir, StaticDir}, {char_dir, CharDir},\n\t\t{chars, Chars}, {case_sensitive, CaseSensitive}|Config].\n\nend_per_suite(Config) ->\n\t%% Special directory.\n\tCharDir = config(char_dir, Config),\n\t_ = [file:delete(CharDir ++ [$/, C]) || C <- lists:seq(0, 127)],\n\t_ = file:del_dir(CharDir),\n\t%% Static directories.\n\tStaticDir = config(static_dir, Config),\n\tPrivDir = code:priv_dir(ct_helper) ++ \"/static\",\n\t%% This file is not created on Windows.\n\t_ = file:delete(StaticDir ++ \"/large.bin\"),\n\t_ = file:delete(PrivDir ++ \"/large.bin\"),\n\tct_helper:delete_static_dir(StaticDir),\n\tct_helper:delete_static_dir(PrivDir).\n\ninit_per_group(dir, Config) ->\n\t[{prefix, \"/dir\"}|Config];\ninit_per_group(priv_dir, Config) ->\n\t[{prefix, \"/priv_dir\"}|Config];\ninit_per_group(Name=http_no_sendfile, Config) ->\n\tcowboy_test:init_http(Name, #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tmiddlewares => [?MODULE, cowboy_router, cowboy_handler],\n\t\tsendfile => false\n\t}, [{flavor, vanilla}|Config]);\ninit_per_group(Name=h2c_no_sendfile, Config) ->\n\tConfig1 = cowboy_test:init_http(Name, #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tmiddlewares => [?MODULE, cowboy_router, cowboy_handler],\n\t\tsendfile => false\n\t}, [{flavor, vanilla}|Config]),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_per_group(Name=h3, Config) ->\n\tcowboy_test:init_http3(Name, #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tmiddlewares => [?MODULE, cowboy_router, cowboy_handler]\n\t}, [{flavor, vanilla}|Config]);\ninit_per_group(Name=h3_compress, Config) ->\n\tcowboy_test:init_http3(Name, #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tmiddlewares => [?MODULE, cowboy_router, cowboy_handler],\n\t\tstream_handlers => [cowboy_compress_h, cowboy_stream_h]\n\t}, [{flavor, vanilla}|Config]);\ninit_per_group(Name, Config) ->\n\tConfig1 = cowboy_test:init_common_groups(Name, Config, ?MODULE),\n\tOpts = ranch:get_protocol_options(Name),\n\tok = ranch:set_protocol_options(Name, Opts#{\n\t\tmiddlewares => [?MODULE, cowboy_router, cowboy_handler]\n\t}),\n\tConfig1.\n\nend_per_group(dir, _) ->\n\tok;\nend_per_group(priv_dir, _) ->\n\tok;\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\n%% Large file.\n\ninit_large_file(Filename) ->\n\tcase os:type() of\n\t\t{unix, _} ->\n\t\t\t\"\" = os:cmd(\"truncate -s 32M \" ++ Filename),\n\t\t\tok;\n\t\t{win32, _} ->\n\t\t\tSize = 32*1024*1024,\n\t\t\tok = file:write_file(Filename, <<0:Size/unit:8>>)\n\tend.\n\n%% Routes.\n\ninit_dispatch(Config) ->\n\tcowboy_router:compile([{'_', [\n\t\t{\"/priv_dir/[...]\", cowboy_static, {priv_dir, ct_helper, \"static\"}},\n\t\t{\"/dir/[...]\", cowboy_static, {dir, config(static_dir, Config)}},\n\t\t{\"/priv_file/style.css\", cowboy_static, {priv_file, ct_helper, \"static/style.css\"}},\n\t\t{\"/file/style.css\", cowboy_static, {file, config(static_dir, Config) ++ \"/style.css\"}},\n\t\t{\"/index\", cowboy_static, {file, config(static_dir, Config) ++ \"/index.html\"}},\n\t\t{\"/mime/all/[...]\", cowboy_static, {priv_dir, ct_helper, \"static\",\n\t\t\t[{mimetypes, cow_mimetypes, all}]}},\n\t\t{\"/mime/custom/[...]\", cowboy_static, {priv_dir, ct_helper, \"static\",\n\t\t\t[{mimetypes, ?MODULE, do_mime_custom}]}},\n\t\t{\"/mime/crash/[...]\", cowboy_static, {priv_dir, ct_helper, \"static\",\n\t\t\t[{mimetypes, ?MODULE, do_mime_crash}]}},\n\t\t{\"/mime/hardcode/binary-form\", cowboy_static, {priv_file, ct_helper, \"static/file.cowboy\",\n\t\t\t[{mimetypes, <<\"application/vnd.ninenines.cowboy+xml;v=1\">>}]}},\n\t\t{\"/mime/hardcode/tuple-form\", cowboy_static, {priv_file, ct_helper, \"static/file.cowboy\",\n\t\t\t[{mimetypes, {<<\"application\">>, <<\"vnd.ninenines.cowboy+xml\">>, [{<<\"v\">>, <<\"1\">>}]}}]}},\n\t\t{\"/charset/custom/[...]\", cowboy_static, {priv_dir, ct_helper, \"static\",\n\t\t\t[{charset, ?MODULE, do_charset_custom}]}},\n\t\t{\"/charset/crash/[...]\", cowboy_static, {priv_dir, ct_helper, \"static\",\n\t\t\t[{charset, ?MODULE, do_charset_crash}]}},\n\t\t{\"/charset/hardcode/[...]\", cowboy_static, {priv_file, ct_helper, \"static/index.html\",\n\t\t\t[{charset, <<\"utf-8\">>}]}},\n\t\t{\"/etag/custom\", cowboy_static, {file, config(static_dir, Config) ++ \"/style.css\",\n\t\t\t[{etag, ?MODULE, do_etag_custom}]}},\n\t\t{\"/etag/crash\", cowboy_static, {file, config(static_dir, Config) ++ \"/style.css\",\n\t\t\t[{etag, ?MODULE, do_etag_crash}]}},\n\t\t{\"/etag/disable\", cowboy_static, {file, config(static_dir, Config) ++ \"/style.css\",\n\t\t\t[{etag, false}]}},\n\t\t{\"/bad\", cowboy_static, bad},\n\t\t{\"/bad/priv_dir/app/[...]\", cowboy_static, {priv_dir, bad_app, \"static\"}},\n\t\t{\"/bad/priv_dir/no-priv/[...]\", cowboy_static, {priv_dir, cowboy, \"static\"}},\n\t\t{\"/bad/priv_dir/path/[...]\", cowboy_static, {priv_dir, ct_helper, \"bad\"}},\n\t\t{\"/bad/priv_dir/route\", cowboy_static, {priv_dir, ct_helper, \"static\"}},\n\t\t{\"/bad/dir/path/[...]\", cowboy_static, {dir, \"/bad/path\"}},\n\t\t{\"/bad/dir/route\", cowboy_static, {dir, config(static_dir, Config)}},\n\t\t{\"/bad/priv_file/app\", cowboy_static, {priv_file, bad_app, \"static/style.css\"}},\n\t\t{\"/bad/priv_file/no-priv\", cowboy_static, {priv_file, cowboy, \"static/style.css\"}},\n\t\t{\"/bad/priv_file/path\", cowboy_static, {priv_file, ct_helper, \"bad/style.css\"}},\n\t\t{\"/bad/file/path\", cowboy_static, {file, \"/bad/path/style.css\"}},\n\t\t{\"/bad/options\", cowboy_static, {priv_file, ct_helper, \"static/style.css\", bad}},\n\t\t{\"/bad/options/mime\", cowboy_static, {priv_file, ct_helper, \"static/style.css\", [{mimetypes, bad}]}},\n\t\t{\"/bad/options/charset\", cowboy_static, {priv_file, ct_helper, \"static/style.css\", [{charset, bad}]}},\n\t\t{\"/bad/options/etag\", cowboy_static, {priv_file, ct_helper, \"static/style.css\", [{etag, true}]}},\n\t\t{\"/unknown/option\", cowboy_static, {priv_file, ct_helper, \"static/style.css\", [{bad, option}]}},\n\t\t{\"/char/[...]\", cowboy_static, {dir, config(char_dir, Config)}},\n\t\t{\"/ez_priv_file/index.html\", cowboy_static, {priv_file, static_files_app, \"www/index.html\"}},\n\t\t{\"/bad/ez_priv_file/index.php\", cowboy_static, {priv_file, static_files_app, \"www/index.php\"}},\n\t\t{\"/ez_priv_dir/[...]\", cowboy_static, {priv_dir, static_files_app, \"www\"}},\n\t\t{\"/bad/ez_priv_dir/[...]\", cowboy_static, {priv_dir, static_files_app, \"cgi-bin\"}}\n\t]}]).\n\n%% Middleware interface to silence expected errors.\n\nexecute(Req=#{path := Path}, Env) ->\n\tcase Path of\n\t\t<<\"/bad/priv_dir/app/\", _/bits>> -> ct_helper:ignore(cowboy_static, priv_path, 2);\n\t\t<<\"/bad/priv_file/app\">> -> ct_helper:ignore(cowboy_static, priv_path, 2);\n\t\t<<\"/bad/priv_dir/route\">> -> ct_helper:ignore(cowboy_static, escape_reserved, 1);\n\t\t<<\"/bad/dir/route\">> -> ct_helper:ignore(cowboy_static, escape_reserved, 1);\n\t\t<<\"/bad\">> -> ct_helper:ignore(cowboy_static, init_opts, 2);\n\t\t<<\"/bad/options\">> -> ct_helper:ignore(cowboy_static, content_types_provided, 2);\n\t\t<<\"/bad/options/mime\">> -> ct_helper:ignore(cowboy_rest, normalize_content_types, 2);\n\t\t<<\"/bad/options/etag\">> -> ct_helper:ignore(cowboy_static, generate_etag, 2);\n\t\t<<\"/bad/options/charset\">> -> ct_helper:ignore(cowboy_static, charsets_provided, 2);\n\t\t_ -> ok\n\tend,\n\t{ok, Req, Env}.\n\n%% Internal functions.\n\n-spec do_charset_crash(_) -> no_return().\ndo_charset_crash(_) ->\n\tct_helper_error_h:ignore(?MODULE, do_charset_crash, 1),\n\texit(crash).\n\ndo_charset_custom(Path) ->\n\tcase filename:extension(Path) of\n\t\t<<\".cowboy\">> -> <<\"utf-32\">>;\n\t\t<<\".html\">> -> <<\"utf-16\">>;\n\t\t_ -> <<\"utf-8\">>\n\tend.\n\n-spec do_etag_crash(_, _, _) -> no_return().\ndo_etag_crash(_, _, _) ->\n\tct_helper_error_h:ignore(?MODULE, do_etag_crash, 3),\n\texit(crash).\n\ndo_etag_custom(_, _, _) ->\n\t{strong, <<\"etag\">>}.\n\n-spec do_mime_crash(_) -> no_return().\ndo_mime_crash(_) ->\n\tct_helper_error_h:ignore(?MODULE, do_mime_crash, 1),\n\texit(crash).\n\ndo_mime_custom(Path) ->\n\tcase filename:extension(Path) of\n\t\t<<\".cowboy\">> -> <<\"application/vnd.ninenines.cowboy+xml;v=1\">>;\n\t\t<<\".txt\">> -> <<\"text/plain\">>;\n\t\t_ -> {<<\"application\">>, <<\"octet-stream\">>, []}\n\tend.\n\n%% Tests.\n\nbad(Config) ->\n\tdoc(\"Bad cowboy_static options: not a tuple.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/bad\", Config)),\n\tok.\n\nbad_dir_path(Config) ->\n\tdoc(\"Bad cowboy_static options: wrong path.\"),\n\t{404, _, _} = do_get(\"/bad/dir/path/style.css\", Config),\n\tok.\n\nbad_dir_route(Config) ->\n\tdoc(\"Bad cowboy_static options: missing [...] in route.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/bad/dir/route\", Config)),\n\tok.\n\nbad_file_in_priv_dir_in_ez_archive(Config) ->\n\tdoc(\"Get a missing file from a priv_dir stored in Erlang application .ez archive.\"),\n\t{404, _, _} = do_get(\"/ez_priv_dir/index.php\", Config),\n\tok.\n\nbad_file_path(Config) ->\n\tdoc(\"Bad cowboy_static options: wrong path.\"),\n\t{404, _, _} = do_get(\"/bad/file/path\", Config),\n\tok.\n\nbad_options(Config) ->\n\tdoc(\"Bad cowboy_static extra options: not a list.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/bad/options\", Config)),\n\tok.\n\nbad_options_charset(Config) ->\n\tdoc(\"Bad cowboy_static extra options: invalid charset option.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/bad/options/charset\", Config)),\n\tok.\n\nbad_options_etag(Config) ->\n\tdoc(\"Bad cowboy_static extra options: invalid etag option.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/bad/options/etag\", Config)),\n\tok.\n\nbad_options_mime(Config) ->\n\tdoc(\"Bad cowboy_static extra options: invalid mimetypes option.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/bad/options/mime\", Config)),\n\tok.\n\nbad_priv_dir_app(Config) ->\n\tdoc(\"Bad cowboy_static options: wrong application name.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/bad/priv_dir/app/style.css\", Config)),\n\tok.\n\nbad_priv_dir_in_ez_archive(Config) ->\n\tdoc(\"Bad cowboy_static options: priv_dir path missing from Erlang application .ez archive.\"),\n\t{404, _, _} = do_get(\"/bad/ez_priv_dir/index.html\", Config),\n\tok.\n\nbad_priv_dir_no_priv(Config) ->\n\tdoc(\"Bad cowboy_static options: application has no priv directory.\"),\n\t{404, _, _} = do_get(\"/bad/priv_dir/no-priv/style.css\", Config),\n\tok.\n\nbad_priv_dir_path(Config) ->\n\tdoc(\"Bad cowboy_static options: wrong path.\"),\n\t{404, _, _} = do_get(\"/bad/priv_dir/path/style.css\", Config),\n\tok.\n\nbad_priv_dir_route(Config) ->\n\tdoc(\"Bad cowboy_static options: missing [...] in route.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/bad/priv_dir/route\", Config)),\n\tok.\n\nbad_priv_file_app(Config) ->\n\tdoc(\"Bad cowboy_static options: wrong application name.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/bad/priv_file/app\", Config)),\n\tok.\n\nbad_priv_file_in_ez_archive(Config) ->\n\tdoc(\"Bad cowboy_static options: priv_file path missing from Erlang application .ez archive.\"),\n\t{404, _, _} = do_get(\"/bad/ez_priv_file/index.php\", Config),\n\tok.\n\nbad_priv_file_no_priv(Config) ->\n\tdoc(\"Bad cowboy_static options: application has no priv directory.\"),\n\t{404, _, _} = do_get(\"/bad/priv_file/no-priv\", Config),\n\tok.\n\nbad_priv_file_path(Config) ->\n\tdoc(\"Bad cowboy_static options: wrong path.\"),\n\t{404, _, _} = do_get(\"/bad/priv_file/path\", Config),\n\tok.\n\ndir_cowboy(Config) ->\n\tdoc(\"Get a .cowboy file.\"),\n\t{200, Headers, <<\"File with custom extension.\\n\">>}\n\t\t= do_get(config(prefix, Config) ++ \"/file.cowboy\", Config),\n\t{_, <<\"application/octet-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ndir_css(Config) ->\n\tdoc(\"Get a .css file.\"),\n\t{200, Headers, <<\"body{color:red}\\n\">>}\n\t\t= do_get(config(prefix, Config) ++ \"/style.css\", Config),\n\t{_, <<\"text/css\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ndir_css_urlencoded(Config) ->\n\tdoc(\"Get a .css file with the extension dot urlencoded.\"),\n\t{200, Headers, <<\"body{color:red}\\n\">>}\n\t\t= do_get(config(prefix, Config) ++ \"/style%2ecss\", Config),\n\t{_, <<\"text/css\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ndir_dot_file(Config) ->\n\tdoc(\"Get a file with extra dot segments in the path.\"),\n\t%% All these are equivalent.\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/./style.css\", Config),\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/././style.css\", Config),\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/./././style.css\", Config),\n\t{200, _, _} = do_get(\"/./priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(\"/././priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(\"/./././priv_dir/style.css\", Config),\n\tok.\n\ndir_dotdot_file(Config) ->\n\tdoc(\"Get a file with extra dotdot segments in the path.\"),\n\t%% All these are equivalent.\n\t{200, _, _} = do_get(\"/../priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(\"/../../priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(\"/../../../priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/../priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/../../priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/../../../priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(\"/../priv_dir/../priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(\"/../../priv_dir/../../priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(\"/../../../priv_dir/../../../priv_dir/style.css\", Config),\n\t%% Try with non-existing segments, which may correspond to real folders.\n\t{200, _, _} = do_get(\"/anything/../priv_dir/style.css\", Config),\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/anything/../style.css\", Config),\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/directory/../style.css\", Config),\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/static/../style.css\", Config),\n\t%% Try with segments corresponding to real files. It works because\n\t%% URI normalization happens before looking at the filesystem.\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/style.css/../style.css\", Config),\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/style.css/../../priv_dir/style.css\", Config),\n\t%% Try to fool the server to accept segments corresponding to real folders.\n\t{404, _, _} = do_get(config(prefix, Config) ++ \"/../static/style.css\", Config),\n\t{404, _, _} = do_get(config(prefix, Config) ++ \"/directory/../../static/style.css\", Config),\n\tok.\n\ndir_empty_file(Config) ->\n\tdoc(\"Get an empty .txt file.\"),\n\t{200, _, <<>>} = do_get(config(prefix, Config) ++ \"/empty.txt\", Config),\n\tok.\n\ndir_error_directory(Config) ->\n\tdoc(\"Try to get a directory.\"),\n\t{403, _, _} = do_get(config(prefix, Config) ++ \"/directory\", Config),\n\tok.\n\ndir_error_directory_slash(Config) ->\n\tdoc(\"Try to get a directory with an extra slash in the path.\"),\n\t{403, _, _} = do_get(config(prefix, Config) ++ \"/directory/\", Config),\n\tok.\n\ndir_error_doesnt_exist(Config) ->\n\tdoc(\"Try to get a file that does not exist.\"),\n\t{404, Headers, _} = do_get(config(prefix, Config) ++ \"/not.found\", Config),\n\tfalse = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ndir_error_dot(Config) ->\n\tdoc(\"Try to get a file named '.'.\"),\n\t{403, _, _} = do_get(config(prefix, Config) ++ \"/.\", Config),\n\tok.\n\ndir_error_dot_urlencoded(Config) ->\n\tdoc(\"Try to get a file named '.' percent encoded.\"),\n\t{403, _, _} = do_get(config(prefix, Config) ++ \"/%2e\", Config),\n\tok.\n\ndir_error_dotdot(Config) ->\n\tdoc(\"Try to get a file named '..'.\"),\n\t{404, _, _} = do_get(config(prefix, Config) ++ \"/..\", Config),\n\tok.\n\ndir_error_dotdot_urlencoded(Config) ->\n\tdoc(\"Try to get a file named '..' percent encoded.\"),\n\t{404, _, _} = do_get(config(prefix, Config) ++ \"/%2e%2e\", Config),\n\tok.\n\ndir_error_empty(Config) ->\n\tdoc(\"Try to get the configured directory.\"),\n\t{403, _, _} = do_get(config(prefix, Config) ++ \"\", Config),\n\tok.\n\ndir_error_slash(Config) ->\n\t%% I know the description isn't that good considering / has a meaning in URIs.\n\tdoc(\"Try to get a file named '/'.\"),\n\t{403, _, _} = do_get(config(prefix, Config) ++ \"//\", Config),\n\tok.\n\ndir_error_reserved_urlencoded(Config) ->\n\tdoc(\"Try to get a file named '/' or '\\\\' or 'NUL' percent encoded.\"),\n\t{400, _, _} = do_get(config(prefix, Config) ++ \"/%2f\", Config),\n\t{400, _, _} = do_get(config(prefix, Config) ++ \"/%5c\", Config),\n\t{400, _, _} = do_get(config(prefix, Config) ++ \"/%00\", Config),\n\tok.\n\ndir_error_slash_urlencoded_dotdot_file(Config) ->\n\tdoc(\"Try to use a percent encoded slash to access an existing file.\"),\n\t{200, _, _} = do_get(config(prefix, Config) ++ \"/directory/../style.css\", Config),\n\t{400, _, _} = do_get(config(prefix, Config) ++ \"/directory%2f../style.css\", Config),\n\tok.\n\ndir_error_unreadable(Config) ->\n\tcase os:type() of\n\t\t{win32, _} ->\n\t\t\t{skip, \"ACL not enabled by default under MSYS2.\"};\n\t\t{unix, _} ->\n\t\t\tdoc(\"Try to get a file that can't be read.\"),\n\t\t\t{403, _, _} = do_get(config(prefix, Config) ++ \"/unreadable\", Config),\n\t\t\tok\n\tend.\n\ndir_html(Config) ->\n\tdoc(\"Get a .html file.\"),\n\t{200, Headers, <<\"<html><body>Hello!</body></html>\\n\">>}\n\t\t= do_get(config(prefix, Config) ++ \"/index.html\", Config),\n\t{_, <<\"text/html\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ndir_large_file(Config) ->\n\tdoc(\"Get a large file.\"),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, config(prefix, Config) ++ \"/large.bin\",\n\t\t[{<<\"accept-encoding\">>, <<\"gzip\">>}]),\n\t{response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref),\n\t{_, <<\"application/octet-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, RespHeaders),\n\tSize = 32*1024*1024,\n\t{ok, Size} = do_dir_large_file(ConnPid, Ref, 0),\n\tok.\n\ndo_dir_large_file(ConnPid, Ref, N) ->\n\treceive\n\t\t{gun_data, ConnPid, Ref, nofin, Data} ->\n\t\t\tdo_dir_large_file(ConnPid, Ref, N + byte_size(Data));\n\t\t{gun_data, ConnPid, Ref, fin, Data} ->\n\t\t\t{ok, N + byte_size(Data)};\n\t\t{gun_error, ConnPid, Ref, Reason} ->\n\t\t\t{error, Reason};\n\t\t{gun_error, ConnPid, Reason} ->\n\t\t\t{error, Reason}\n\tafter 5000 ->\n\t\t{error, timeout}\n\tend.\n\ndir_text(Config) ->\n\tdoc(\"Get a .txt file. The extension is unknown by default.\"),\n\t{200, Headers, <<\"Timeless space.\\n\">>}\n\t\t= do_get(config(prefix, Config) ++ \"/plain.txt\", Config),\n\t{_, <<\"application/octet-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ndir_unknown(Config) ->\n\tdoc(\"Get a file with no extension.\"),\n\t{200, Headers, <<\"File with no extension.\\n\">>}\n\t\t= do_get(config(prefix, Config) ++ \"/unknown\", Config),\n\t{_, <<\"application/octet-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\netag_crash(Config) ->\n\tdoc(\"Get a file with a crashing etag function.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/etag/crash\", Config)),\n\tok.\n\netag_custom(Config) ->\n\tdoc(\"Get a file with custom Etag function and make sure it is used.\"),\n\t{200, Headers, _} = do_get(\"/etag/custom\", Config),\n\t{_, <<\"\\\"etag\\\"\">>} = lists:keyfind(<<\"etag\">>, 1, Headers),\n\tok.\n\netag_default(Config) ->\n\tdoc(\"Get a file twice and make sure the Etag matches.\"),\n\t{200, Headers1, _} = do_get(\"/dir/style.css\", Config),\n\t{200, Headers2, _} = do_get(\"/dir/style.css\", Config),\n\t{_, Etag} = lists:keyfind(<<\"etag\">>, 1, Headers1),\n\t{_, Etag} = lists:keyfind(<<\"etag\">>, 1, Headers2),\n\tok.\n\netag_default_change(Config) ->\n\tdoc(\"Get a file, modify it, get it again and make sure the Etag doesn't match.\"),\n\t%% We set the file to the current time first, then to a time in the past.\n\tok = file:change_time(config(static_dir, Config) ++ \"/index.html\",\n\t\tcalendar:universal_time()),\n\t{200, Headers1, _} = do_get(\"/dir/index.html\", Config),\n\t{_, Etag1} = lists:keyfind(<<\"etag\">>, 1, Headers1),\n\tok = file:change_time(config(static_dir, Config) ++ \"/index.html\",\n\t\t{{2019, 1, 1}, {1, 1, 1}}),\n\t{200, Headers2, _} = do_get(\"/dir/index.html\", Config),\n\t{_, Etag2} = lists:keyfind(<<\"etag\">>, 1, Headers2),\n\ttrue = Etag1 =/= Etag2,\n\tok.\n\netag_disable(Config) ->\n\tdoc(\"Get a file with disabled Etag and make sure no Etag is provided.\"),\n\t{200, Headers, _} = do_get(\"/etag/disable\", Config),\n\tfalse = lists:keyfind(<<\"etag\">>, 1, Headers),\n\tok.\n\nfile(Config) ->\n\tdoc(\"Get a file with hardcoded route.\"),\n\t{200, Headers, <<\"body{color:red}\\n\">>} = do_get(\"/file/style.css\", Config),\n\t{_, <<\"text/css\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nif_match(Config) ->\n\tdoc(\"Get a file with If-Match matching.\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-match\">>, <<\"\\\"etag\\\"\">>}\n\t], Config),\n\tok.\n\nif_match_fail(Config) ->\n\tdoc(\"Get a file with If-Match not matching.\"),\n\t{412, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-match\">>, <<\"\\\"invalid\\\"\">>}\n\t], Config),\n\tok.\n\nif_match_invalid(Config) ->\n\tdoc(\"Try to get a file with an invalid If-Match header.\"),\n\t{400, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-match\">>, <<\"bad input\">>}\n\t], Config),\n\tok.\n\nif_match_list(Config) ->\n\tdoc(\"Get a file with If-Match matching.\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-match\">>, <<\"\\\"invalid\\\", \\\"etag\\\", \\\"cowboy\\\"\">>}\n\t], Config),\n\tok.\n\nif_match_list_fail(Config) ->\n\tdoc(\"Get a file with If-Match not matching.\"),\n\t{412, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-match\">>, <<\"\\\"invalid\\\", W/\\\"etag\\\", \\\"cowboy\\\"\">>}\n\t], Config),\n\tok.\n\nif_match_weak(Config) ->\n\tdoc(\"Try to get a file with a weak If-Match header.\"),\n\t{412, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-match\">>, <<\"W/\\\"etag\\\"\">>}\n\t], Config),\n\tok.\n\nif_match_wildcard(Config) ->\n\tdoc(\"Get a file with a wildcard If-Match.\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-match\">>, <<\"*\">>}\n\t], Config),\n\tok.\n\nif_modified_since(Config) ->\n\tdoc(\"Get a file with If-Modified-Since in the past.\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-modified-since\">>, <<\"Sat, 29 Oct 1994 19:43:31 GMT\">>}\n\t], Config),\n\tok.\n\nif_modified_since_fail(Config) ->\n\tdoc(\"Get a file with If-Modified-Since equal to file modification time.\"),\n\tLastModified = filelib:last_modified(config(static_dir, Config) ++ \"/style.css\"),\n\t{304, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-modified-since\">>, httpd_util:rfc1123_date(LastModified)}\n\t], Config),\n\tok.\n\nif_modified_since_future(Config) ->\n\tdoc(\"Get a file with If-Modified-Since in the future.\"),\n\t{{Year, _, _}, {_, _, _}} = calendar:universal_time(),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-modified-since\">>, [\n\t\t\t<<\"Sat, 29 Oct \">>,\n\t\t\tinteger_to_binary(Year + 1),\n\t\t\t<<\" 19:43:31 GMT\">>]}\n\t], Config),\n\tok.\n\nif_modified_since_if_none_match(Config) ->\n\tdoc(\"Get a file with both If-Modified-Since and If-None-Match headers.\"\n\t\t\"If-None-Match takes precedence and If-Modified-Since is ignored. (RFC7232 3.3)\"),\n\tLastModified = filelib:last_modified(config(static_dir, Config) ++ \"/style.css\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-modified-since\">>, httpd_util:rfc1123_date(LastModified)},\n\t\t{<<\"if-none-match\">>, <<\"\\\"not-etag\\\"\">>}\n\t], Config),\n\tok.\n\nif_modified_since_invalid(Config) ->\n\tdoc(\"Get a file with an invalid If-Modified-Since header.\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-modified-since\">>, <<\"\\\"not a date\\\"\">>}\n\t], Config),\n\tok.\n\nif_none_match(Config) ->\n\tdoc(\"Get a file with If-None-Match not matching.\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-none-match\">>, <<\"\\\"not-etag\\\"\">>}\n\t], Config),\n\tok.\n\nif_none_match_fail(Config) ->\n\tdoc(\"Get a file with If-None-Match matching.\"),\n\t{304, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-none-match\">>, <<\"\\\"etag\\\"\">>}\n\t], Config),\n\tok.\n\nif_none_match_invalid(Config) ->\n\tdoc(\"Try to get a file with an invalid If-None-Match header.\"),\n\t{400, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-none-match\">>, <<\"bad input\">>}\n\t], Config),\n\tok.\n\nif_none_match_list(Config) ->\n\tdoc(\"Get a file with If-None-Match not matching.\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-none-match\">>, <<\"\\\"invalid\\\", W/\\\"not-etag\\\", \\\"cowboy\\\"\">>}\n\t], Config),\n\tok.\n\nif_none_match_list_fail(Config) ->\n\tdoc(\"Get a file with If-None-Match matching.\"),\n\t{304, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-none-match\">>, <<\"\\\"invalid\\\", \\\"etag\\\", \\\"cowboy\\\"\">>}\n\t], Config),\n\tok.\n\nif_none_match_weak(Config) ->\n\tdoc(\"Try to get a file with a weak If-None-Match header matching.\"),\n\t{304, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-none-match\">>, <<\"W/\\\"etag\\\"\">>}\n\t], Config),\n\tok.\n\nif_none_match_wildcard(Config) ->\n\tdoc(\"Try to get a file with a wildcard If-None-Match.\"),\n\t{304, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-none-match\">>, <<\"*\">>}\n\t], Config),\n\tok.\n\nif_unmodified_since(Config) ->\n\tdoc(\"Get a file with If-Unmodified-Since equal to file modification time.\"),\n\tLastModified = filelib:last_modified(config(static_dir, Config) ++ \"/style.css\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-unmodified-since\">>, httpd_util:rfc1123_date(LastModified)}\n\t], Config),\n\tok.\n\nif_unmodified_since_fail(Config) ->\n\tdoc(\"Get a file with If-Unmodified-Since in the past.\"),\n\t{412, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-unmodified-since\">>, <<\"Sat, 29 Oct 1994 19:43:31 GMT\">>}\n\t], Config),\n\tok.\n\nif_unmodified_since_future(Config) ->\n\tdoc(\"Get a file with If-Unmodified-Since in the future.\"),\n\t{{Year, _, _}, {_, _, _}} = calendar:universal_time(),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-unmodified-since\">>, [\n\t\t\t<<\"Sat, 29 Oct \">>,\n\t\t\tinteger_to_binary(Year + 1),\n\t\t\t<<\" 19:43:31 GMT\">>]}\n\t], Config),\n\tok.\n\nif_unmodified_since_if_match(Config) ->\n\tdoc(\"Get a file with both If-Unmodified-Since and If-Match headers.\"\n\t\t\"If-Match takes precedence and If-Unmodified-Since is ignored. (RFC7232 3.4)\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-unmodified-since\">>, <<\"Sat, 29 Oct 1994 19:43:31 GMT\">>},\n\t\t{<<\"if-match\">>, <<\"\\\"etag\\\"\">>}\n\t], Config),\n\tok.\n\nif_unmodified_since_invalid(Config) ->\n\tdoc(\"Get a file with an invalid If-Unmodified-Since header.\"),\n\t{200, _, _} = do_get(\"/etag/custom\", [\n\t\t{<<\"if-unmodified-since\">>, <<\"\\\"not a date\\\"\">>}\n\t], Config),\n\tok.\n\nindex_file(Config) ->\n\tdoc(\"Get an index file.\"),\n\t{200, Headers, <<\"<html><body>Hello!</body></html>\\n\">>} = do_get(\"/index\", Config),\n\t{_, <<\"text/html\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nindex_file_slash(Config) ->\n\tdoc(\"Get an index file with extra slash.\"),\n\t{200, Headers, <<\"<html><body>Hello!</body></html>\\n\">>} = do_get(\"/index/\", Config),\n\t{_, <<\"text/html\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nlast_modified(Config) ->\n\tdoc(\"Get a file, modify it, get it again and make sure Last-Modified changes.\"),\n\t%% We set the file to the current time first, then to a time in the past.\n\tok = file:change_time(config(static_dir, Config) ++ \"/file.cowboy\",\n\t\tcalendar:universal_time()),\n\t{200, Headers1, _} = do_get(\"/dir/file.cowboy\", Config),\n\t{_, LastModified1} = lists:keyfind(<<\"last-modified\">>, 1, Headers1),\n\tok = file:change_time(config(static_dir, Config) ++ \"/file.cowboy\",\n\t\t{{2019, 1, 1}, {1, 1, 1}}),\n\t{200, Headers2, _} = do_get(\"/dir/file.cowboy\", Config),\n\t{_, LastModified2} = lists:keyfind(<<\"last-modified\">>, 1, Headers2),\n\ttrue = LastModified1 =/= LastModified2,\n\tok.\n\nmime_all_cowboy(Config) ->\n\tdoc(\"Get a .cowboy file. The extension is unknown.\"),\n\t{200, Headers, _} = do_get(\"/mime/all/file.cowboy\", Config),\n\t{_, <<\"application/octet-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nmime_all_css(Config) ->\n\tdoc(\"Get a .css file.\"),\n\t{200, Headers, _} = do_get(\"/mime/all/style.css\", Config),\n\t{_, <<\"text/css\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nmime_all_txt(Config) ->\n\tdoc(\"Get a .txt file.\"),\n\t{200, Headers, _} = do_get(\"/mime/all/plain.txt\", Config),\n\t{_, <<\"text/plain\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nmime_all_uppercase(Config) ->\n\tdoc(\"Get an uppercase .TXT file.\"),\n\t{200, Headers, _} = do_get(\"/mime/all/UPPER.TXT\", Config),\n\t{_, <<\"text/plain\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nmime_crash(Config) ->\n\tdoc(\"Get a file with a crashing mimetype function.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/mime/crash/style.css\", Config)),\n\tok.\n\nmime_custom_cowboy(Config) ->\n\tdoc(\"Get a .cowboy file.\"),\n\t{200, Headers, _} = do_get(\"/mime/custom/file.cowboy\", Config),\n\t{_, <<\"application/vnd.ninenines.cowboy+xml;v=1\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nmime_custom_css(Config) ->\n\tdoc(\"Get a .css file. The extension is unknown.\"),\n\t{200, Headers, _} = do_get(\"/mime/custom/style.css\", Config),\n\t{_, <<\"application/octet-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nmime_custom_txt(Config) ->\n\tdoc(\"Get a .txt file.\"),\n\t{200, Headers, _} = do_get(\"/mime/custom/plain.txt\", Config),\n\t{_, <<\"text/plain\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nmime_hardcode_binary(Config) ->\n\tdoc(\"Get a .cowboy file with hardcoded route and media type in binary form.\"),\n\t{200, Headers, _} = do_get(\"/mime/hardcode/binary-form\", Config),\n\t{_, <<\"application/vnd.ninenines.cowboy+xml;v=1\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nmime_hardcode_tuple(Config) ->\n\tdoc(\"Get a .cowboy file with hardcoded route and media type in tuple form.\"),\n\t{200, Headers, _} = do_get(\"/mime/hardcode/tuple-form\", Config),\n\t{_, <<\"application/vnd.ninenines.cowboy+xml;v=1\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharset_crash(Config) ->\n\tdoc(\"Get a file with a crashing charset function.\"),\n\t{500, _, _} = do_maybe_h3_error3(do_get(\"/charset/crash/style.css\", Config)),\n\tok.\n\ncharset_custom_cowboy(Config) ->\n\tdoc(\"Get a .cowboy file.\"),\n\t{200, Headers, _} = do_get(\"/charset/custom/file.cowboy\", Config),\n\t{_, <<\"application/octet-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharset_custom_css(Config) ->\n\tdoc(\"Get a .css file.\"),\n\t{200, Headers, _} = do_get(\"/charset/custom/style.css\", Config),\n\t{_, <<\"text/css; charset=utf-8\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharset_custom_html(Config) ->\n\tdoc(\"Get a .html file.\"),\n\t{200, Headers, _} = do_get(\"/charset/custom/index.html\", Config),\n\t{_, <<\"text/html; charset=utf-16\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\ncharset_hardcode_binary(Config) ->\n\tdoc(\"Get a .html file with hardcoded route and charset.\"),\n\t{200, Headers, _} = do_get(\"/charset/hardcode\", Config),\n\t{_, <<\"text/html; charset=utf-8\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\npriv_dir_in_ez_archive(Config) ->\n\tdoc(\"Get a file from a priv_dir stored in Erlang application .ez archive.\"),\n\t{200, Headers, <<\"<h1>It works!</h1>\\n\">>} = do_get(\"/ez_priv_dir/index.html\", Config),\n\t{_, <<\"text/html\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\npriv_file(Config) ->\n\tdoc(\"Get a file with hardcoded route.\"),\n\t{200, Headers, <<\"body{color:red}\\n\">>} = do_get(\"/priv_file/style.css\", Config),\n\t{_, <<\"text/css\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\npriv_file_in_ez_archive(Config) ->\n\tdoc(\"Get a file stored in Erlang application .ez archive.\"),\n\t{200, Headers, <<\"<h1>It works!</h1>\\n\">>} = do_get(\"/ez_priv_file/index.html\", Config),\n\t{_, <<\"text/html\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nrange_request(Config) ->\n\tdoc(\"Confirm that range requests are enabled.\"),\n\t{206, Headers, <<\"less space.\\n\">>} = do_get(\"/dir/plain.txt\",\n\t\t[{<<\"range\">>, <<\"bytes=4-\">>}], Config),\n\t{_, <<\"bytes\">>} = lists:keyfind(<<\"accept-ranges\">>, 1, Headers),\n\t{_, <<\"bytes 4-15/16\">>} = lists:keyfind(<<\"content-range\">>, 1, Headers),\n\t{_, <<\"application/octet-stream\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n\nunicode_basic_latin(Config) ->\n\tdoc(\"Get a file with non-urlencoded characters from Unicode Basic Latin block.\"),\n\t%% Excluding the dot which has a special meaning in URLs\n\t%% when they are the only content in a path segment,\n\t%% and is tested as part of filenames in other test cases.\n\tChars0 =\n\t\t\"abcdefghijklmnopqrstuvwxyz\"\n\t\t\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\t\t\"0123456789\"\n\t\t\":@-_~!$&'()*+,;=\",\n\tChars1 = case config(case_sensitive, Config) of\n\t\tfalse -> Chars0 -- \"ABCDEFGHIJKLMNOPQRSTUVWXYZ\";\n\t\ttrue -> Chars0\n\tend,\n\t%% Remove the characters for which we have no corresponding file.\n\tChars = Chars1 -- (Chars1 -- config(chars, Config)),\n\t_ = [case do_get(\"/char/\" ++ [C], Config) of\n\t\t{200, _, << C >>} -> ok;\n\t\tError -> exit({error, C, Error})\n\tend || C <- Chars],\n\tok.\n\nunicode_basic_error(Config) ->\n\tdoc(\"Try to get a file with invalid non-urlencoded characters from Unicode Basic Latin block.\"),\n\tExclude = case config(protocol, Config) of\n\t\t%% Some characters trigger different errors in HTTP/1.1\n\t\t%% because they are used for the protocol.\n\t\t%%\n\t\t%% # and ? indicate fragment and query components\n\t\t%% and are therefore not part of the path.\n\t\thttp -> \"\\r\\s#?\";\n\t\thttp2 -> \"#?\";\n\t\thttp3 -> \"#?\"\n\tend,\n\t_ = [case do_get(\"/char/\" ++ [C], Config) of\n\t\t{400, _, _} -> ok;\n\t\tError -> exit({error, C, Error})\n\tend || C <- (config(chars, Config) -- Exclude) --\n\t\t\"abcdefghijklmnopqrstuvwxyz\"\n\t\t\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\t\t\"0123456789\"\n\t\t\":@-_~!$&'()*+,;=\"\n\t],\n\tok.\n\nunicode_basic_latin_urlencoded(Config) ->\n\tdoc(\"Get a file with urlencoded characters from Unicode Basic Latin block.\"),\n\t_ = [case do_get(lists:flatten([\"/char/%\", io_lib:format(\"~2.16.0b\", [C])]), Config) of\n\t\t{200, _, << C >>} -> ok;\n\t\tError -> exit({error, C, Error})\n\tend || C <- config(chars, Config)],\n\tok.\n\nunknown_option(Config) ->\n\tdoc(\"Get a file configured with unknown extra options.\"),\n\t{200, Headers, <<\"body{color:red}\\n\">>} = do_get(\"/unknown/option\", Config),\n\t{_, <<\"text/css\">>} = lists:keyfind(<<\"content-type\">>, 1, Headers),\n\tok.\n"
  },
  {
    "path": "test/stream_handler_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(stream_handler_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n-import(cowboy_test, [gun_down/1]).\n\n%% ct.\n\nall() ->\n\tcowboy_test:common_all().\n\ngroups() ->\n\tcowboy_test:common_groups(ct_helper:all(?MODULE)).\n\n%% We set this module as a logger in order to silence expected errors.\ninit_per_group(Name = http, Config) ->\n\tcowboy_test:init_http(Name, init_plain_opts(), Config);\ninit_per_group(Name = https, Config) ->\n\tcowboy_test:init_https(Name, init_plain_opts(), Config);\ninit_per_group(Name = h2, Config) ->\n\tcowboy_test:init_http2(Name, init_plain_opts(), Config);\ninit_per_group(Name = h2c, Config) ->\n\tConfig1 = cowboy_test:init_http(Name, init_plain_opts(), Config),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_per_group(Name = h3, Config) ->\n\tcowboy_test:init_http3(Name, init_plain_opts(), Config);\ninit_per_group(Name = http_compress, Config) ->\n\tcowboy_test:init_http(Name, init_compress_opts(), Config);\ninit_per_group(Name = https_compress, Config) ->\n\tcowboy_test:init_https(Name, init_compress_opts(), Config);\ninit_per_group(Name = h2_compress, Config) ->\n\tcowboy_test:init_http2(Name, init_compress_opts(), Config);\ninit_per_group(Name = h2c_compress, Config) ->\n\tConfig1 = cowboy_test:init_http(Name, init_compress_opts(), Config),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_per_group(Name = h3_compress, Config) ->\n\tcowboy_test:init_http3(Name, init_compress_opts(), Config).\n\nend_per_group(Name, _) ->\n\tcowboy_test:stop_group(Name).\n\ninit_plain_opts() ->\n\t#{\n\t\tlogger => ?MODULE,\n\t\tstream_handlers => [stream_handler_h]\n\t}.\n\ninit_compress_opts() ->\n\t#{\n\t\tlogger => ?MODULE,\n\t\tstream_handlers => [cowboy_compress_h, stream_handler_h]\n\t}.\n\n%% Logger function silencing the expected crashes.\n\nerror(\"Unhandled exception \" ++ _, [error, crash|_]) ->\n\tok;\nerror(Format, Args) ->\n\terror_logger:error_msg(Format, Args).\n\n%% Tests.\n\ncrash_in_init(Config) ->\n\tdoc(\"Confirm an error is sent when a stream handler crashes in init/3.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"crash_in_init\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t%% Confirm terminate/3 is NOT called. We have no state to give to it.\n\treceive {Self, Pid, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,\n\t%% Confirm early_error/5 is called in HTTP/1.1's case.\n\t%% HTTP/2 and HTTP/3 do not send a response back so there is no early_error call.\n\tcase config(protocol, Config) of\n\t\thttp -> receive {Self, Pid, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end;\n\t\thttp2 -> ok;\n\t\thttp3 -> ok\n\tend,\n\tdo_await_internal_error(ConnPid, Ref, Config).\n\ndo_await_internal_error(ConnPid, Ref, Config) ->\n\tProtocol = config(protocol, Config),\n\tcase {Protocol, gun:await(ConnPid, Ref)} of\n\t\t{http, {response, fin, 500, _}} -> ok;\n\t\t{http2, {error, {stream_error, {stream_error, internal_error, _}}}} -> ok;\n\t\t{http3, {error, {stream_error, {stream_error, h3_internal_error, _}}}} -> ok\n\tend.\n\ncrash_in_data(Config) ->\n\tdoc(\"Confirm an error is sent when a stream handler crashes in data/4.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"content-length\">>, <<\"6\">>},\n\t\t{<<\"x-test-case\">>, <<\"crash_in_data\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t%% Send data to make the stream handler crash.\n\tgun:data(ConnPid, Ref, fin, <<\"Hello!\">>),\n\t%% Confirm terminate/3 is called, indicating the stream ended.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\tdo_await_internal_error(ConnPid, Ref, Config).\n\ncrash_in_info(Config) ->\n\tdoc(\"Confirm an error is sent when a stream handler crashes in info/3.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"crash_in_info\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t%% Send a message to make the stream handler crash.\n\tStreamID = case config(protocol, Config) of\n\t\thttp3 -> 0;\n\t\t_ -> 1\n\tend,\n\tPid ! {{Pid, StreamID}, crash},\n\t%% Confirm terminate/3 is called, indicating the stream ended.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\tdo_await_internal_error(ConnPid, Ref, Config).\n\ncrash_in_terminate(Config) ->\n\tdoc(\"Confirm the state is correct when a stream handler crashes in terminate/3.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\t%% Do a first request.\n\tRef1 = gun:get(ConnPid, \"/hello_world\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"crash_in_terminate\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t%% Confirm terminate/3 is called.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% Receive the response.\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref1),\n\t{ok, <<\"Hello world!\">>} = gun:await_body(ConnPid, Ref1),\n\t%% Do a second request to make sure the connection state is still good.\n\tRef2 = gun:get(ConnPid, \"/hello_world\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"crash_in_terminate\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called. The pid shouldn't change.\n\treceive {Self, Pid, init, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% Confirm terminate/3 is called.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% Receive the second response.\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref2),\n\t{ok, <<\"Hello world!\">>} = gun:await_body(ConnPid, Ref2),\n\tok.\n\n%% @todo The callbacks ARE used for HTTP/2 and HTTP/3 CONNECT/TRACE requests.\ncrash_in_early_error(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_crash_in_early_error(Config);\n\t\thttp2 -> doc(\"The callback early_error/5 is not currently used for HTTP/2.\");\n\t\thttp3 -> doc(\"The callback early_error/5 is not currently used for HTTP/3.\")\n\tend.\n\ndo_crash_in_early_error(Config) ->\n\tdoc(\"Confirm an error is sent when a stream handler crashes in early_error/5.\"\n\t\t\"The connection is kept open by Cowboy.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef1 = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"crash_in_early_error\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t%% Confirm terminate/3 is NOT called. We have no state to give to it.\n\treceive {Self, Pid, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,\n\t%% Confirm early_error/5 is called.\n\treceive {Self, Pid, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% Receive a 500 error response.\n\t{response, fin, 500, _} = gun:await(ConnPid, Ref1),\n\t%% This error is not fatal. We should be able to repeat it on the same connection.\n\tRef2 = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"crash_in_early_error\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\treceive {Self, Pid, init, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% Confirm terminate/3 is NOT called. We have no state to give to it.\n\treceive {Self, Pid, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,\n\t%% Confirm early_error/5 is called.\n\treceive {Self, Pid, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% Receive a 500 error response.\n\t{response, fin, 500, _} = gun:await(ConnPid, Ref2),\n\tok.\n\n%% @todo The callbacks ARE used for HTTP/2 and HTTP/3 CONNECT/TRACE requests.\ncrash_in_early_error_fatal(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_crash_in_early_error_fatal(Config);\n\t\thttp2 -> doc(\"The callback early_error/5 is not currently used for HTTP/2.\");\n\t\thttp3 -> doc(\"The callback early_error/5 is not currently used for HTTP/3.\")\n\tend.\n\ndo_crash_in_early_error_fatal(Config) ->\n\tdoc(\"Confirm an error is sent when a stream handler crashes in early_error/5.\"\n\t\t\"The error was fatal and the connection is closed by Cowboy.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"host\">>, <<\"host:port\">>},\n\t\t{<<\"x-test-case\">>, <<\"crash_in_early_error_fatal\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is NOT called. The error occurs before we reach this step.\n\treceive {Self, _, init, _, _, _} -> error(init) after 1000 -> ok end,\n\t%% Confirm terminate/3 is NOT called. We have no state to give to it.\n\treceive {Self, _, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,\n\t%% Confirm early_error/5 is called.\n\treceive {Self, _, early_error, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% Receive a 400 error response. We do not send a 500 when\n\t%% early_error/5 crashes, we send the original error.\n\t{response, fin, 400, _} = gun:await(ConnPid, Ref),\n\t%% Confirm the connection gets closed.\n\tgun_down(ConnPid).\n\nearly_error_stream_error_reason(Config) ->\n\tdoc(\"Confirm that the stream_error given to early_error/5 is consistent between protocols.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\t%% We must use different solutions to hit early_error with a stream_error\n\t%% reason in both protocols.\n\t{Method, Headers, Status, Error} = case config(protocol, Config) of\n\t\thttp -> {<<\"GET\">>, [{<<\"host\">>, <<\"host:port\">>}], 400, protocol_error};\n\t\thttp2 -> {<<\"TRACE\">>, [], 501, no_error};\n\t\thttp3 -> {<<\"TRACE\">>, [], 501, h3_no_error}\n\tend,\n\tRef = gun:request(ConnPid, Method, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"early_error_stream_error_reason\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t|Headers], <<>>),\n\t%% Confirm init/3 is NOT called. The error occurs before we reach this step.\n\treceive {Self, _, init, _, _, _} -> error(init) after 1000 -> ok end,\n\t%% Confirm terminate/3 is NOT called. We have no state to give to it.\n\treceive {Self, _, terminate, _, _, _} -> error(terminate) after 1000 -> ok end,\n\t%% Confirm early_error/5 is called.\n\tReason = receive {Self, _, early_error, _, R, _, _, _} -> R after 1000 -> error(timeout) end,\n\t%% Confirm that the Reason is a {stream_error, Reason, Human}.\n\t{stream_error, Error, HumanReadable} = Reason,\n\ttrue = is_atom(HumanReadable),\n\t%% Receive a 400 or 501 error response.\n\t{response, fin, Status, _} = gun:await(ConnPid, Ref),\n\tok.\n\nflow_after_body_fully_read(Config) ->\n\tdoc(\"A flow command may be returned even after the body was read fully.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:post(ConnPid, \"/long_polling\", [\n\t\t{<<\"x-test-case\">>, <<\"flow_after_body_fully_read\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t], <<\"Hello world!\">>),\n\t%% Receive a 200 response, sent after the second flow command,\n\t%% confirming that the flow command was accepted.\n\t{response, _, 200, _} = gun:await(ConnPid, Ref),\n\tgun:close(ConnPid).\n\nset_options_ignore_unknown(Config) ->\n\tdoc(\"Confirm that unknown options are ignored when using the set_options commands.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"set_options_ignore_unknown\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t%% Confirm terminate/3 is called, indicating the stream ended.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% Confirm the response is sent.\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, _} = gun:await_body(ConnPid, Ref),\n\tok.\n\nshutdown_on_stream_stop(Config) ->\n\tdoc(\"Confirm supervised processes are shutdown when stopping the stream.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"shutdown_on_stream_stop\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t%% Receive the pid of the newly started process and monitor it.\n\tSpawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,\n\tMRef = monitor(process, Spawn),\n\tSpawn ! {Self, ready},\n\t%% Confirm terminate/3 is called, indicating the stream ended.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% We should receive a DOWN message soon after (or before) because the stream\n\t%% handler is stopping the stream immediately after the process started.\n\treceive {'DOWN', MRef, process, Spawn, shutdown} -> ok after 1000 -> error(timeout) end,\n\t%% The response is still sent.\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, _} = gun:await_body(ConnPid, Ref),\n\tok.\n\nshutdown_on_socket_close(Config) ->\n\tdoc(\"Confirm supervised processes are shutdown when the socket closes.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\t_ = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"shutdown_on_socket_close\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t%% Receive the pid of the newly started process and monitor it.\n\tSpawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,\n\tMRef = monitor(process, Spawn),\n\tSpawn ! {Self, ready},\n\t%% Close the socket.\n\tok = gun:close(ConnPid),\n\tProtocol = config(protocol, Config),\n\ttry\n\t\t%% Confirm terminate/3 is called, indicating the stream ended.\n\t\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t\t%% Confirm we receive a DOWN message for the child process.\n\t\treceive {'DOWN', MRef, process, Spawn, shutdown} -> ok after 1000 -> error(timeout) end,\n\t\tok\n\tcatch error:timeout when Protocol =:= http3 ->\n\t\t%% @todo Figure out why this happens. Could be a timing issue\n\t\t%%       or a legitimate bug. I suspect that the server just\n\t\t%%       doesn't receive the GOAWAY frame from Gun because\n\t\t%%       Gun is too quick to close the connection.\n\t\tshutdown_on_socket_close(Config)\n\tend.\n\nshutdown_timeout_on_stream_stop(Config) ->\n\tdoc(\"Confirm supervised processes are killed \"\n\t\t\"when the shutdown timeout triggers after stopping the stream.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"shutdown_timeout_on_stream_stop\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t%% Receive the pid of the newly started process and monitor it.\n\tSpawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,\n\tMRef = monitor(process, Spawn),\n\tSpawn ! {Self, ready},\n\t%% Confirm terminate/3 is called, indicating the stream ended.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% We should NOT receive a DOWN message immediately.\n\treceive {'DOWN', MRef, process, Spawn, killed} -> error(killed) after 1500 -> ok end,\n\t%% We should received it now.\n\treceive {'DOWN', MRef, process, Spawn, killed} -> ok after 1000 -> error(timeout) end,\n\t%% The response is still sent.\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t{ok, _} = gun:await_body(ConnPid, Ref),\n\tok.\n\nshutdown_timeout_on_socket_close(Config) ->\n\tdoc(\"Confirm supervised processes are killed \"\n\t\t\"when the shutdown timeout triggers after the socket has closed.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\t_ = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"shutdown_timeout_on_socket_close\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t%% Receive the pid of the newly started process and monitor it.\n\tSpawn = receive {Self, Pid, spawned, S} -> S after 1000 -> error(timeout) end,\n\tMRef = monitor(process, Spawn),\n\tSpawn ! {Self, ready},\n\t%% Close the socket.\n\tok = gun:close(ConnPid),\n\tProtocol = config(protocol, Config),\n\ttry\n\t\t%% Confirm terminate/3 is called, indicating the stream ended.\n\t\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t\t%% We should NOT receive a DOWN message immediately.\n\t\treceive {'DOWN', MRef, process, Spawn, killed} -> error(killed) after 1500 -> ok end,\n\t\t%% We should receive it now.\n\t\treceive {'DOWN', MRef, process, Spawn, killed} -> ok after 1000 -> error(timeout) end,\n\t\tok\n\tcatch error:timeout when Protocol =:= http3 ->\n\t\t%% @todo Figure out why this happens. Could be a timing issue\n\t\t%%       or a legitimate bug. I suspect that the server just\n\t\t%%       doesn't receive the GOAWAY frame from Gun because\n\t\t%%       Gun is too quick to close the connection.\n\t\tshutdown_timeout_on_socket_close(Config)\n\tend.\n\nswitch_protocol_after_headers(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_switch_protocol_after_response(\n\t\t\t<<\"switch_protocol_after_headers\">>, Config);\n\t\thttp2 -> doc(\"The switch_protocol command is not currently supported for HTTP/2.\");\n\t\thttp3 -> doc(\"The switch_protocol command is not currently supported for HTTP/3.\")\n\tend.\n\nswitch_protocol_after_headers_data(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_switch_protocol_after_response(\n\t\t\t<<\"switch_protocol_after_headers_data\">>, Config);\n\t\thttp2 -> doc(\"The switch_protocol command is not currently supported for HTTP/2.\");\n\t\thttp3 -> doc(\"The switch_protocol command is not currently supported for HTTP/3.\")\n\tend.\n\nswitch_protocol_after_response(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_switch_protocol_after_response(\n\t\t\t<<\"switch_protocol_after_response\">>, Config);\n\t\thttp2 -> doc(\"The switch_protocol command is not currently supported for HTTP/2.\");\n\t\thttp3 -> doc(\"The switch_protocol command is not currently supported for HTTP/3.\")\n\tend.\n\ndo_switch_protocol_after_response(TestCase, Config) ->\n\tdoc(\"The 101 informational response must not be sent when a response \"\n\t\t\"has already been sent before the switch_protocol is returned.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, TestCase},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called and receive the response.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t{response, nofin, 200, Headers} = gun:await(ConnPid, Ref),\n\tGzipped =\n\t\tlists:keyfind(<<\"content-encoding\">>, 1, Headers)\n\t\t\t=:= {<<\"content-encoding\">>, <<\"gzip\">>},\n\tcase TestCase of\n\t\t<<\"switch_protocol_after_headers\">> ->\n\t\t\tok;\n\t\t_ ->\n\t\t\t<<\"{}\">> = case gun:await_body(ConnPid, Ref) of\n\t\t\t\t{ok, Body} when Gzipped ->\n\t\t\t\t\tzlib:gunzip(Body);\n\t\t\t\t{ok, Body} ->\n\t\t\t\t\tBody\n\t\t\tend,\n\t\t\tok\n\tend,\n\t{error, _} = gun:await(ConnPid, Ref),\n\t%% Confirm terminate/3 is called.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% Confirm takeover/7 is called.\n\treceive {Self, Pid, takeover, _, _, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,\n\tok.\n\nterminate_on_socket_close(Config) ->\n\tdoc(\"Confirm terminate/3 is called when the socket gets closed brutally.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"terminate_on_socket_close\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called and receive the beginning of the response.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref),\n\t%% Close the socket.\n\tok = gun:close(ConnPid),\n\t%% Confirm terminate/3 is called.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\tok.\n\nterminate_on_stop(Config) ->\n\tdoc(\"Confirm terminate/3 is called after stop is returned.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"terminate_on_stop\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called and receive the response.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t{response, fin, 204, _} = gun:await(ConnPid, Ref),\n\t%% Confirm the stream is still alive even though we\n\t%% received the response fully, and tell it to stop.\n\tStreamID = case config(protocol, Config) of\n\t\thttp -> 1;\n\t\thttp2 -> 1;\n\t\thttp3 -> 0\n\tend,\n\tPid ! {{Pid, StreamID}, please_stop},\n\treceive {Self, Pid, info, _, please_stop, _} -> ok after 1000 -> error(timeout) end,\n\t%% Confirm terminate/3 is called.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\tok.\n\nterminate_on_switch_protocol(Config) ->\n\tcase config(protocol, Config) of\n\t\thttp -> do_terminate_on_switch_protocol(Config);\n\t\thttp2 -> doc(\"The switch_protocol command is not currently supported for HTTP/2.\");\n\t\thttp3 -> doc(\"The switch_protocol command is not currently supported for HTTP/3.\")\n\tend.\n\ndo_terminate_on_switch_protocol(Config) ->\n\tdoc(\"Confirm terminate/3 is called after switch_protocol is returned.\"),\n\tSelf = self(),\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, \"/long_polling\", [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-case\">>, <<\"terminate_on_switch_protocol\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(Self)}\n\t]),\n\t%% Confirm init/3 is called and receive the response.\n\tPid = receive {Self, P, init, _, _, _} -> P after 1000 -> error(timeout) end,\n\t{inform, 101, _} = gun:await(ConnPid, Ref),\n\t%% Confirm terminate/3 is called.\n\treceive {Self, Pid, terminate, _, _, _} -> ok after 1000 -> error(timeout) end,\n\t%% Confirm takeover/7 is called.\n\treceive {Self, Pid, takeover, _, _, _, _, _, _, _} -> ok after 1000 -> error(timeout) end,\n\tok.\n"
  },
  {
    "path": "test/sys_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(sys_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(ct_helper, [get_parent_pid/1]).\n-import(ct_helper, [get_remote_pid_tcp/1]).\n-import(ct_helper, [get_remote_pid_tls/1]).\n-import(ct_helper, [is_process_down/1]).\n\nall() ->\n\t[{group, sys}].\n\ngroups() ->\n\t[{sys, [parallel], ct_helper:all(?MODULE)}].\n\ninit_per_suite(Config) ->\n\tProtoOpts = #{\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tlogger => ?MODULE\n\t},\n\t%% Clear listener.\n\t{ok, _} = cowboy:start_clear(clear, [{port, 0}], ProtoOpts),\n\tClearPort = ranch:get_port(clear),\n\t%% TLS listener.\n\tTLSOpts = ct_helper:get_certs_from_ets(),\n\t{ok, _} = cowboy:start_tls(tls, TLSOpts ++ [{port, 0}], ProtoOpts),\n\tTLSPort = ranch:get_port(tls),\n\t[\n\t\t{clear_port, ClearPort},\n\t\t%% @todo Add the h2 stuff to the opts.\n\t\t{tls_opts, TLSOpts},\n\t\t{tls_port, TLSPort}\n\t|Config].\n\nend_per_suite(_) ->\n\tok = cowboy:stop_listener(clear),\n\tok = cowboy:stop_listener(tls).\n\ninit_dispatch(_) ->\n\tcowboy_router:compile([{\"[...]\", [\n\t\t{\"/\", hello_h, []},\n\t\t{\"/loop\", long_polling_sys_h, []},\n\t\t{\"/ws\", ws_echo, []}\n\t]}]).\n\n%% Logger function silencing the expected warnings.\n\nerror(Format, Args) ->\n\terror_logger:error_msg(Format, Args).\n\nwarning(\"Received EXIT signal \" ++ _, [{'EXIT', _, {shutdown, ?MODULE}}|_]) ->\n\tok;\nwarning(Format, Args) ->\n\terror_logger:warning_msg(Format, Args).\n\n%% proc_lib.\n\nproc_lib_initial_call_clear(Config) ->\n\tdoc(\"Confirm that clear connection processes are started using proc_lib.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{cowboy_clear, _, _} = proc_lib:initial_call(Pid),\n\tok.\n\nproc_lib_initial_call_tls(Config) ->\n\tdoc(\"Confirm that TLS connection processes are started using proc_lib.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config), config(tls_opts, Config)),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tls(Socket),\n\t{cowboy_tls, _, _} = proc_lib:initial_call(Pid),\n\tok.\n\n%% System messages.\n%%\n%% Plain system messages are received as {system, From, Msg}.\n%% The content and meaning of this message are not interpreted by\n%% the receiving process module. When a system message is received,\n%% function handle_system_msg/6 is called to handle the request.\n\nbad_system_from_h1(Config) ->\n\tdoc(\"h1: Sending a system message with a bad From value results in a process crash.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tct_helper_error_h:ignore(Pid, gen, reply, 2),\n\tPid ! {system, bad, get_state},\n\t{error, closed} = gen_tcp:recv(Socket, 0, 1000),\n\tfalse = is_process_alive(Pid),\n\tok.\n\nbad_system_from_h2(Config) ->\n\tdoc(\"h2: Sending a system message with a bad From value results in a process crash.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\t%% Skip the SETTINGS frame.\n\t{ok, <<_,_,_,4,_/bits>>} = ssl:recv(Socket, 0, 1000),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tls(Socket),\n\tct_helper_error_h:ignore(Pid, gen, reply, 2),\n\tPid ! {system, bad, get_state},\n\t{error, closed} = ssl:recv(Socket, 0, 1000),\n\tfalse = is_process_alive(Pid),\n\tok.\n\nbad_system_from_ws(Config) ->\n\tdoc(\"ws: Sending a system message with a bad From value results in a process crash.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tct_helper_error_h:ignore(Pid, gen, reply, 2),\n\tPid ! {system, bad, get_state},\n\t{error, closed} = gen_tcp:recv(Socket, 0, 1000),\n\tfalse = is_process_alive(Pid),\n\tok.\n\nbad_system_from_loop(Config) ->\n\tdoc(\"loop: Sending a system message with a bad From value results in a process crash.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tSupPid = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(SupPid),\n\tct_helper_error_h:ignore(Pid, gen, reply, 2),\n\tPid ! {system, bad, get_state},\n\t{ok, \"HTTP/1.1 500 \"} = gen_tcp:recv(Socket, 13, 1000),\n\tfalse = is_process_alive(Pid),\n\tok.\n\nbad_system_message_h1(Config) ->\n\tdoc(\"h1: Sending a system message with a bad Request value results in an error.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tRef = make_ref(),\n\tPid ! {system, {self(), Ref}, hello},\n\treceive\n\t\t{Ref, {error, {unknown_system_msg, hello}}} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\nbad_system_message_h2(Config) ->\n\tdoc(\"h2: Sending a system message with a bad Request value results in an error.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\t%% Skip the SETTINGS frame.\n\t{ok, <<_,_,_,4,_/bits>>} = ssl:recv(Socket, 0, 1000),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tls(Socket),\n\tRef = make_ref(),\n\tPid ! {system, {self(), Ref}, hello},\n\treceive\n\t\t{Ref, {error, {unknown_system_msg, hello}}} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\nbad_system_message_ws(Config) ->\n\tdoc(\"ws: Sending a system message with a bad Request value results in an error.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tRef = make_ref(),\n\tPid ! {system, {self(), Ref}, hello},\n\treceive\n\t\t{Ref, {error, {unknown_system_msg, hello}}} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\nbad_system_message_loop(Config) ->\n\tdoc(\"loop: Sending a system message with a bad Request value results in an error.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tSupPid = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(SupPid),\n\tRef = make_ref(),\n\tPid ! {system, {self(), Ref}, hello},\n\treceive\n\t\t{Ref, {error, {unknown_system_msg, hello}}} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\ngood_system_message_h1(Config) ->\n\tdoc(\"h1: System messages are handled properly.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tRef = make_ref(),\n\tPid ! {system, {self(), Ref}, get_state},\n\treceive\n\t\t{Ref, Result} when element(1, Result) =/= error ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\ngood_system_message_h2(Config) ->\n\tdoc(\"h2: System messages are handled properly.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\t%% Skip the SETTINGS frame.\n\t{ok, <<_,_,_,4,_/bits>>} = ssl:recv(Socket, 0, 1000),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tls(Socket),\n\tRef = make_ref(),\n\tPid ! {system, {self(), Ref}, get_state},\n\treceive\n\t\t{Ref, Result} when element(1, Result) =/= error ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\ngood_system_message_ws(Config) ->\n\tdoc(\"ws: System messages are handled properly.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tRef = make_ref(),\n\tPid ! {system, {self(), Ref}, get_state},\n\treceive\n\t\t{Ref, Result} when element(1, Result) =/= error ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\ngood_system_message_loop(Config) ->\n\tdoc(\"loop: System messages are handled properly.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tSupPid = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(SupPid),\n\tRef = make_ref(),\n\tPid ! {system, {self(), Ref}, get_state},\n\treceive\n\t\t{Ref, Result} when element(1, Result) =/= error ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\n%% 'EXIT'.\n%%\n%% Shutdown messages. If the process traps exits, it must be able\n%% to handle a shutdown request from its parent, the supervisor.\n%% The message {'EXIT', Parent, Reason} from the parent is an order\n%% to terminate. The process must terminate when this message is\n%% received, normally with the same Reason as Parent.\n\ntrap_exit_parent_exit_h1(Config) ->\n\tdoc(\"h1: A process trapping exits must stop when receiving \"\n\t\t\"an 'EXIT' message from its parent.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tParent = get_parent_pid(Pid),\n\tPid ! {'EXIT', Parent, {shutdown, ?MODULE}},\n\t{error, closed} = gen_tcp:recv(Socket, 0, 1000),\n\ttrue = is_process_down(Pid),\n\tok.\n\ntrap_exit_parent_exit_h2(Config) ->\n\tdoc(\"h2: A process trapping exits must stop when receiving \"\n\t\t\"an 'EXIT' message from its parent.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\t%% Skip the SETTINGS frame.\n\t{ok, <<_,_,_,4,_/bits>>} = ssl:recv(Socket, 0, 1000),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tls(Socket),\n\tParent = get_parent_pid(Pid),\n\tPid ! {'EXIT', Parent, {shutdown, ?MODULE}},\n\t{error, closed} = ssl:recv(Socket, 0, 1000),\n\ttrue = is_process_down(Pid),\n\tok.\n\ntrap_exit_parent_exit_ws(Config) ->\n\tdoc(\"ws: A process trapping exits must stop when receiving \"\n\t\t\"an 'EXIT' message from its parent.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tParent = get_parent_pid(Pid),\n\tPid ! {'EXIT', Parent, {shutdown, ?MODULE}},\n\t{error, closed} = gen_tcp:recv(Socket, 0, 1000),\n\ttrue = is_process_down(Pid),\n\tok.\n\ntrap_exit_parent_exit_loop(Config) ->\n\tdoc(\"loop: A process trapping exits must stop when receiving \"\n\t\t\"an 'EXIT' message from its parent.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tParent = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(Parent),\n\tPid ! {'EXIT', Parent, {shutdown, ?MODULE}},\n\t%% We exit normally but didn't send a response.\n\t{ok, \"HTTP/1.1 204 \"} = gen_tcp:recv(Socket, 13, 1000),\n\ttrue = is_process_down(Pid),\n\tok.\n\ntrap_exit_other_exit_h1(Config) ->\n\tdoc(\"h1: A process trapping exits must ignore \"\n\t\t\"'EXIT' messages from unknown processes.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tPid ! {'EXIT', self(), {shutdown, ?MODULE}},\n\tok = gen_tcp:send(Socket,\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, \"HTTP/1.1 200 \"} = gen_tcp:recv(Socket, 13, 1000),\n\ttrue = is_process_alive(Pid),\n\tok.\n\ntrap_exit_other_exit_h2(Config) ->\n\tdoc(\"h2: A process trapping exits must ignore \"\n\t\t\"'EXIT' messages from unknown processes.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\tdo_http2_handshake(Socket),\n\tPid = get_remote_pid_tls(Socket),\n\tPid ! {'EXIT', self(), {shutdown, ?MODULE}},\n\t%% Send a HEADERS frame as a request.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = ssl:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a HEADERS frame as a response.\n\t{ok, << _:24, 1:8, _:40 >>} = ssl:recv(Socket, 9, 6000),\n\ttrue = is_process_alive(Pid),\n\tok.\n\ntrap_exit_other_exit_ws(Config) ->\n\tdoc(\"ws: A process trapping exits must ignore \"\n\t\t\"'EXIT' messages from unknown processes.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tPid ! {'EXIT', self(), {shutdown, ?MODULE}},\n\t%% The process stays alive.\n\t{error, timeout} = gen_tcp:recv(Socket, 0, 1000),\n\ttrue = is_process_alive(Pid),\n\tok.\n\ntrap_exit_other_exit_loop(Config) ->\n\tdoc(\"loop: A process trapping exits must ignore \"\n\t\t\"'EXIT' messages from unknown processes.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tParent = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(Parent),\n\tPid ! {'EXIT', self(), {shutdown, ?MODULE}},\n\t%% The process stays alive.\n\t{ok, \"HTTP/1.1 299 \"} = gen_tcp:recv(Socket, 13, 1000),\n\ttrue = is_process_alive(Pid),\n\tok.\n\n%% get_modules.\n%%\n%% If the modules used to implement the process change dynamically\n%% during runtime, the process must understand one more message.\n%% An example is the gen_event processes. The message is\n%% {_Label, {From, Ref}, get_modules}. The reply to this message is\n%% From ! {Ref, Modules}, where Modules is a list of the currently\n%% active modules in the process.\n%%\n%% For example:\n%%\n%%   1> application:start(sasl).\n%%   ok\n%%   2> gen:call(alarm_handler, self(), get_modules).\n%%   {ok,[alarm_handler]}\n%%   3> whereis(alarm_handler) ! {'$gen', {self(), make_ref()}, get_modules}.\n%%   {'$gen',{<0.61.0>,#Ref<0.2900144977.374865921.142102>},\n%%           get_modules}\n%%   4> flush().\n%%   Shell got {#Ref<0.2900144977.374865921.142102>,[alarm_handler]}\n%%\n%% Cowboy's connection processes change dynamically: it starts with\n%% cowboy_clear or cowboy_tls, then becomes cowboy_http or cowboy_http2\n%% and may then become or involve cowboy_websocket. On top of that\n%% it has various callback modules in the form of stream handlers.\n\n%% @todo\n%get_modules_h1(Config) ->\n%get_modules_h2(Config) ->\n%get_modules_ws(Config) ->\n%get_modules_loop(Config) ->\n\n%% @todo On top of this we will want to make the supervisor calls\n%% in ranch_conns_sup return dynamic instead of a list of modules.\n\n%% sys:change_code/4,5.\n%%\n%% We do not actually change the module code, we just ensure that\n%% calling this function does not crash the process. The function\n%% Module:system_code_change/4 will be called within the process.\n\nsys_change_code_h1(Config) ->\n\tdoc(\"h1: The sys:change_code/4 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tok = sys:suspend(Pid),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, timeout} = gen_tcp:recv(Socket, 13, 500),\n\tok = sys:change_code(Pid, cowboy_http, undefined, undefined),\n\tok = sys:resume(Pid),\n\t{ok, \"HTTP/1.1 200 \"} = gen_tcp:recv(Socket, 13, 500),\n\tok.\n\nsys_change_code_h2(Config) ->\n\tdoc(\"h2: The sys:change_code/4 function works as expected.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\tdo_http2_handshake(Socket),\n\tPid = get_remote_pid_tls(Socket),\n\t%% Suspend the process and try to get a request in. The\n\t%% response will not come back until we resume the process.\n\tok = sys:suspend(Pid),\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = ssl:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a HEADERS frame as a response.\n\t{error, timeout} = ssl:recv(Socket, 9, 500),\n\tok = sys:change_code(Pid, cowboy_http2, undefined, undefined),\n\tok = sys:resume(Pid),\n\t{ok, << _:24, 1:8, _:40 >>} = ssl:recv(Socket, 9, 6000),\n\tok.\n\nsys_change_code_ws(Config) ->\n\tdoc(\"ws: The sys:change_code/4 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tok = sys:suspend(Pid),\n\tMask = 16#37fa213d,\n\tMaskedHello = ws_SUITE:do_mask(<<\"Hello\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\t{error, timeout} = gen_tcp:recv(Socket, 0, 500),\n\tok = sys:change_code(Pid, cowboy_websocket, undefined, undefined),\n\tok = sys:resume(Pid),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nsys_change_code_loop(Config) ->\n\tdoc(\"loop: The sys:change_code/4 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tSupPid = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(SupPid),\n\t%% The process sends a response 500ms after initializing.\n\t%% We expect to not receive it until we resume it.\n\tok = sys:suspend(Pid),\n\t{error, timeout} = gen_tcp:recv(Socket, 13, 1000),\n\tok = sys:change_code(Pid, cowboy_loop, undefined, undefined),\n\tok = sys:resume(Pid),\n\t{ok, \"HTTP/1.1 299 \"} = gen_tcp:recv(Socket, 13, 500),\n\tok.\n\n%% sys:get_state/1,2.\n%%\n%% None of the modules implement Module:system_get_state/1\n%% at this time so sys:get_state/1,2 returns the Misc value.\n\nsys_get_state_h1(Config) ->\n\tdoc(\"h1: The sys:get_state/1 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tState = sys:get_state(Pid),\n\tstate = element(1, State),\n\tok.\n\nsys_get_state_h2(Config) ->\n\tdoc(\"h2: The sys:get_state/1 function works as expected.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\t%% Skip the SETTINGS frame.\n\t{ok, <<_,_,_,4,_/bits>>} = ssl:recv(Socket, 0, 1000),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tls(Socket),\n\t{State, Buffer} = sys:get_state(Pid),\n\tstate = element(1, State),\n\ttrue = is_binary(Buffer),\n\tok.\n\nsys_get_state_ws(Config) ->\n\tdoc(\"ws: The sys:get_state/1 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{State, undefined, ParseState} = sys:get_state(Pid),\n\tstate = element(1, State),\n\tcase element(1, ParseState) of\n\t\tps_header -> ok;\n\t\tps_payload -> ok\n\tend.\n\nsys_get_state_loop(Config) ->\n\tdoc(\"loop: The sys:get_state/1 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tSupPid = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(SupPid),\n\t{Req, Env, long_polling_sys_h, undefined, infinity} = sys:get_state(Pid),\n\t#{pid := _, streamid := _} = Req,\n\t#{dispatch := _} = Env,\n\tok.\n\n%% sys:get_status/1,2.\n\nsys_get_status_h1(Config) ->\n\tdoc(\"h1: The sys:get_status/1 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{status, Pid, {module, cowboy_http}, _} = sys:get_status(Pid),\n\tok.\n\nsys_get_status_h2(Config) ->\n\tdoc(\"h2: The sys:get_status/1 function works as expected.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\t%% Skip the SETTINGS frame.\n\t{ok, <<_,_,_,4,_/bits>>} = ssl:recv(Socket, 0, 1000),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tls(Socket),\n\t{status, Pid, {module, cowboy_http2}, _} = sys:get_status(Pid),\n\tok.\n\nsys_get_status_ws(Config) ->\n\tdoc(\"ws: The sys:get_status/1 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{status, Pid, {module, cowboy_websocket}, _} = sys:get_status(Pid),\n\tok.\n\nsys_get_status_loop(Config) ->\n\tdoc(\"loop: The sys:get_status/1 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tSupPid = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(SupPid),\n\t{status, Pid, {module, cowboy_loop}, _} = sys:get_status(Pid),\n\tok.\n\n%% sys:replace_state/2,3.\n%%\n%% None of the modules implement Module:system_replace_state/2\n%% at this time so sys:replace_state/2,3 handles the Misc value.\n%%\n%% We don't actually replace the state, we only care about\n%% whether the call executes as expected.\n\nsys_replace_state_h1(Config) ->\n\tdoc(\"h1: The sys:replace_state/2 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tState = sys:replace_state(Pid, fun(S) -> S end),\n\tstate = element(1, State),\n\tok.\n\nsys_replace_state_h2(Config) ->\n\tdoc(\"h2: The sys:replace_state/2 function works as expected.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\t%% Skip the SETTINGS frame.\n\t{ok, <<_,_,_,4,_/bits>>} = ssl:recv(Socket, 0, 1000),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tls(Socket),\n\t{State, Buffer} = sys:replace_state(Pid, fun(S) -> S end),\n\tstate = element(1, State),\n\ttrue = is_binary(Buffer),\n\tok.\n\nsys_replace_state_ws(Config) ->\n\tdoc(\"ws: The sys:replace_state/2 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{State, undefined, ParseState} = sys:replace_state(Pid, fun(S) -> S end),\n\tstate = element(1, State),\n\tcase element(1, ParseState) of\n\t\tps_header -> ok;\n\t\tps_payload -> ok\n\tend.\n\nsys_replace_state_loop(Config) ->\n\tdoc(\"loop: The sys:replace_state/2 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tSupPid = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(SupPid),\n\t{Req, Env, long_polling_sys_h, undefined, infinity} = sys:replace_state(Pid, fun(S) -> S end),\n\t#{pid := _, streamid := _} = Req,\n\t#{dispatch := _} = Env,\n\tok.\n\n%% sys:suspend/1 and sys:resume/1.\n\nsys_suspend_and_resume_h1(Config) ->\n\tdoc(\"h1: The sys:suspend/1 and sys:resume/1 functions work as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tok = sys:suspend(Pid),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET / HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{error, timeout} = gen_tcp:recv(Socket, 13, 500),\n\tok = sys:resume(Pid),\n\t{ok, \"HTTP/1.1 200 \"} = gen_tcp:recv(Socket, 13, 500),\n\tok.\n\nsys_suspend_and_resume_h2(Config) ->\n\tdoc(\"h2: The sys:suspend/1 and sys:resume/1 functions work as expected.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\tdo_http2_handshake(Socket),\n\tPid = get_remote_pid_tls(Socket),\n\t%% Suspend the process and try to get a request in. The\n\t%% response will not come back until we resume the process.\n\tok = sys:suspend(Pid),\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/\">>}\n\t]),\n\tok = ssl:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\t%% Receive a HEADERS frame as a response.\n\t{error, timeout} = ssl:recv(Socket, 9, 500),\n\tok = sys:resume(Pid),\n\t{ok, << _:24, 1:8, _:40 >>} = ssl:recv(Socket, 9, 6000),\n\tok.\n\nsys_suspend_and_resume_ws(Config) ->\n\tdoc(\"ws: The sys:suspend/1 and sys:resume/1 functions work as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tok = sys:suspend(Pid),\n\tMask = 16#37fa213d,\n\tMaskedHello = ws_SUITE:do_mask(<<\"Hello\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\t{error, timeout} = gen_tcp:recv(Socket, 0, 500),\n\tok = sys:resume(Pid),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nsys_suspend_and_resume_loop(Config) ->\n\tdoc(\"loop: The sys:suspend/1 and sys:resume/1 functions work as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tSupPid = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(SupPid),\n\t%% The process sends a response 500ms after initializing.\n\t%% We expect to not receive it until we resume it.\n\tok = sys:suspend(Pid),\n\t{error, timeout} = gen_tcp:recv(Socket, 13, 1000),\n\tok = sys:resume(Pid),\n\t{ok, \"HTTP/1.1 299 \"} = gen_tcp:recv(Socket, 13, 500),\n\tok.\n\n%% sys:terminate/2,3.\n%%\n%% The callback Module:system_terminate/4 is used in all cases.\n\nsys_terminate_h1(Config) ->\n\tdoc(\"h1: The sys:terminate/2,3 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tok = sys:terminate(Pid, {shutdown, ?MODULE}),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 500),\n\tok.\n\nsys_terminate_h2(Config) ->\n\tdoc(\"h2: The sys:terminate/2,3 function works as expected.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\t%% Skip the SETTINGS frame.\n\t{ok, <<_,_,_,4,_/bits>>} = ssl:recv(Socket, 0, 1000),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tls(Socket),\n\tok = sys:terminate(Pid, {shutdown, ?MODULE}),\n\t{error, closed} = ssl:recv(Socket, 0, 500),\n\tok.\n\nsys_terminate_ws(Config) ->\n\tdoc(\"ws: The sys:terminate/2,3 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tok = sys:terminate(Pid, {shutdown, ?MODULE}),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 500),\n\tok.\n\nsys_terminate_loop(Config) ->\n\tdoc(\"loop: The sys:terminate/2,3 function works as expected.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config), [{active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tSupPid = get_remote_pid_tcp(Socket),\n\t[{_, Pid, _, _}] = supervisor:which_children(SupPid),\n\t%% We stop the process normally and therefore get a 204.\n\tok = sys:terminate(Pid, {shutdown, ?MODULE}),\n\t{ok, \"HTTP/1.1 204 \"} = gen_tcp:recv(Socket, 13, 500),\n\tok.\n\n%% @todo Debugging functionality from sys.\n%%\n%% The functions make references to a debug structure.\n%% The debug structure is a list of dbg_opt(), which is\n%% an internal data type used by the function handle_system_msg/6.\n%% No debugging is performed if it is an empty list.\n%%\n%% Cowboy currently does not implement sys debugging.\n%%\n%% The following functions are concerned:\n%%\n%% * sys:install/2,3\n%% * sys:log/2,3\n%% * sys:log_to_file/2,3\n%% * sys:no_debug/1,2\n%% * sys:remove/2,3\n%% * sys:statistics/2,3\n%% * sys:trace/2,3\n%% * call debug_options/1\n%% * call get_debug/3\n%% * call handle_debug/4\n%% * call print_log/1\n\n%% supervisor.\n%%\n%% The connection processes act as supervisors by default\n%% so they must handle the supervisor messages.\n\n%% supervisor:count_children/1.\n\nsupervisor_count_children_h1(Config) ->\n\tdoc(\"h1: The function supervisor:count_children/1 must work.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t%% No request was sent so there's no children.\n\tCounts1 = supervisor:count_children(Pid),\n\t1 = proplists:get_value(specs, Counts1),\n\t0 = proplists:get_value(active, Counts1),\n\t0 = proplists:get_value(supervisors, Counts1),\n\t0 = proplists:get_value(workers, Counts1),\n\t%% Send a request, observe that a children exists.\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\tCounts2 = supervisor:count_children(Pid),\n\t1 = proplists:get_value(specs, Counts2),\n\t1 = proplists:get_value(active, Counts2),\n\t0 = proplists:get_value(supervisors, Counts2),\n\t1 = proplists:get_value(workers, Counts2),\n\tok.\n\nsupervisor_count_children_h2(Config) ->\n\tdoc(\"h2: The function supervisor:count_children/1 must work.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\tdo_http2_handshake(Socket),\n\tPid = get_remote_pid_tls(Socket),\n\t%% No request was sent so there's no children.\n\tCounts1 = supervisor:count_children(Pid),\n\t1 = proplists:get_value(specs, Counts1),\n\t0 = proplists:get_value(active, Counts1),\n\t0 = proplists:get_value(supervisors, Counts1),\n\t0 = proplists:get_value(workers, Counts1),\n\t%% Send a request, observe that a children exists.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/loop\">>}\n\t]),\n\tok = ssl:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\ttimer:sleep(100),\n\tCounts2 = supervisor:count_children(Pid),\n\t1 = proplists:get_value(specs, Counts2),\n\t1 = proplists:get_value(active, Counts2),\n\t0 = proplists:get_value(supervisors, Counts2),\n\t1 = proplists:get_value(workers, Counts2),\n\tok.\n\nsupervisor_count_children_ws(Config) ->\n\tdoc(\"ws: The function supervisor:count_children/1 must work. \"\n\t\t\"Websocket connections never have children.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\tCounts = supervisor:count_children(Pid),\n\t1 = proplists:get_value(specs, Counts),\n\t0 = proplists:get_value(active, Counts),\n\t0 = proplists:get_value(supervisors, Counts),\n\t0 = proplists:get_value(workers, Counts),\n\tok.\n\n%% supervisor:delete_child/2.\n\nsupervisor_delete_child_not_found_h1(Config) ->\n\tdoc(\"h1: The function supervisor:delete_child/2 must return {error, not_found}.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t%% When no children exist.\n\t{error, not_found} = supervisor:delete_child(Pid, cowboy_http),\n\t%% When a child exists.\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\t{error, not_found} = supervisor:delete_child(Pid, cowboy_http),\n\tok.\n\nsupervisor_delete_child_not_found_h2(Config) ->\n\tdoc(\"h2: The function supervisor:delete_child/2 must return {error, not_found}.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\tdo_http2_handshake(Socket),\n\tPid = get_remote_pid_tls(Socket),\n\t%% When no children exist.\n\t{error, not_found} = supervisor:delete_child(Pid, cowboy_http2),\n\t%% When a child exists.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/loop\">>}\n\t]),\n\tok = ssl:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\ttimer:sleep(100),\n\t{error, not_found} = supervisor:delete_child(Pid, cowboy_http2),\n\tok.\n\nsupervisor_delete_child_not_found_ws(Config) ->\n\tdoc(\"ws: The function supervisor:delete_child/2 must return {error, not_found}.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{error, not_found} = supervisor:delete_child(Pid, cowboy_websocket),\n\tok.\n\n%% supervisor:get_childspec/2.\n\nsupervisor_get_childspec_not_found_h1(Config) ->\n\tdoc(\"h1: The function supervisor:get_childspec/2 must return {error, not_found}.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t%% When no children exist.\n\t{error, not_found} = supervisor:get_childspec(Pid, cowboy_http),\n\t%% When a child exists.\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\t{error, not_found} = supervisor:get_childspec(Pid, cowboy_http),\n\tok.\n\nsupervisor_get_childspec_not_found_h2(Config) ->\n\tdoc(\"h2: The function supervisor:get_childspec/2 must return {error, not_found}.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\tdo_http2_handshake(Socket),\n\tPid = get_remote_pid_tls(Socket),\n\t%% When no children exist.\n\t{error, not_found} = supervisor:get_childspec(Pid, cowboy_http2),\n\t%% When a child exists.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/loop\">>}\n\t]),\n\tok = ssl:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\ttimer:sleep(100),\n\t{error, not_found} = supervisor:get_childspec(Pid, cowboy_http2),\n\tok.\n\nsupervisor_get_childspec_not_found_ws(Config) ->\n\tdoc(\"ws: The function supervisor:get_childspec/2 must return {error, not_found}.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{error, not_found} = supervisor:get_childspec(Pid, cowboy_websocket),\n\tok.\n\n%% supervisor:restart_child/2.\n\nsupervisor_restart_child_not_found_h1(Config) ->\n\tdoc(\"h1: The function supervisor:restart_child/2 must return {error, not_found}.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t%% When no children exist.\n\t{error, not_found} = supervisor:restart_child(Pid, cowboy_http),\n\t%% When a child exists.\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\t{error, not_found} = supervisor:restart_child(Pid, cowboy_http),\n\tok.\n\nsupervisor_restart_child_not_found_h2(Config) ->\n\tdoc(\"h2: The function supervisor:restart_child/2 must return {error, not_found}.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\tdo_http2_handshake(Socket),\n\tPid = get_remote_pid_tls(Socket),\n\t%% When no children exist.\n\t{error, not_found} = supervisor:restart_child(Pid, cowboy_http2),\n\t%% When a child exists.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/loop\">>}\n\t]),\n\tok = ssl:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\ttimer:sleep(100),\n\t{error, not_found} = supervisor:restart_child(Pid, cowboy_http2),\n\tok.\n\nsupervisor_restart_child_not_found_ws(Config) ->\n\tdoc(\"ws: The function supervisor:restart_child/2 must return {error, not_found}.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{error, not_found} = supervisor:restart_child(Pid, cowboy_websocket),\n\tok.\n\n%% supervisor:start_child/2 must return {error, start_child_disabled}\n\nsupervisor_start_child_not_found_h1(Config) ->\n\tdoc(\"h1: The function supervisor:start_child/2 must return {error, start_child_disabled}.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{error, start_child_disabled} = supervisor:start_child(Pid, #{\n\t\tid => error,\n\t\tstart => {error, error, []}\n\t}),\n\tok.\n\nsupervisor_start_child_not_found_h2(Config) ->\n\tdoc(\"h2: The function supervisor:start_child/2 must return {error, start_child_disabled}.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\tdo_http2_handshake(Socket),\n\tPid = get_remote_pid_tls(Socket),\n\t{error, start_child_disabled} = supervisor:start_child(Pid, #{\n\t\tid => error,\n\t\tstart => {error, error, []}\n\t}),\n\tok.\n\nsupervisor_start_child_not_found_ws(Config) ->\n\tdoc(\"ws: The function supervisor:start_child/2 must return {error, start_child_disabled}.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{error, start_child_disabled} = supervisor:start_child(Pid, #{\n\t\tid => error,\n\t\tstart => {error, error, []}\n\t}),\n\tok.\n\n%% supervisor:terminate_child/2.\n\nsupervisor_terminate_child_not_found_h1(Config) ->\n\tdoc(\"h1: The function supervisor:terminate_child/2 must return {error, not_found}.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t%% When no children exist.\n\t{error, not_found} = supervisor:terminate_child(Pid, cowboy_http),\n\t%% When a child exists.\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\t{error, not_found} = supervisor:terminate_child(Pid, cowboy_http),\n\tok.\n\nsupervisor_terminate_child_not_found_h2(Config) ->\n\tdoc(\"h2: The function supervisor:terminate_child/2 must return {error, not_found}.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\tdo_http2_handshake(Socket),\n\tPid = get_remote_pid_tls(Socket),\n\t%% When no children exist.\n\t{error, not_found} = supervisor:terminate_child(Pid, cowboy_http2),\n\t%% When a child exists.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/loop\">>}\n\t]),\n\tok = ssl:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\ttimer:sleep(100),\n\t{error, not_found} = supervisor:terminate_child(Pid, cowboy_http2),\n\tok.\n\nsupervisor_terminate_child_not_found_ws(Config) ->\n\tdoc(\"ws: The function supervisor:terminate_child/2 must return {error, not_found}.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t{error, not_found} = supervisor:terminate_child(Pid, cowboy_websocket),\n\tok.\n\n%% supervisor:which_children/1.\n%%\n%% @todo The list of modules returned is probably wrong. This will\n%% need to be corrected when get_modules gets implemented.\n\nsupervisor_which_children_h1(Config) ->\n\tdoc(\"h1: The function supervisor:which_children/1 must work.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[{active, false}]),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t%% No request was sent so there's no children.\n\t[] = supervisor:which_children(Pid),\n\t%% Send a request, observe that a children exists.\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /loop HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"\\r\\n\"),\n\ttimer:sleep(100),\n\t[{cowboy_http, Child, worker, [cowboy_http]}] = supervisor:which_children(Pid),\n\ttrue = is_pid(Child),\n\tok.\n\nsupervisor_which_children_h2(Config) ->\n\tdoc(\"h2: The function supervisor:which_children/1 must work.\"),\n\t{ok, Socket} = ssl:connect(\"localhost\", config(tls_port, Config),\n\t\t[{alpn_advertised_protocols, [<<\"h2\">>]},\n\t\t\t{active, false}, binary|config(tls_opts, Config)]),\n\tdo_http2_handshake(Socket),\n\tPid = get_remote_pid_tls(Socket),\n\t%% No request was sent so there's no children.\n\t[] = supervisor:which_children(Pid),\n\t%% Send a request, observe that a children exists.\n\t{HeadersBlock, _} = cow_hpack:encode([\n\t\t{<<\":method\">>, <<\"GET\">>},\n\t\t{<<\":scheme\">>, <<\"https\">>},\n\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t{<<\":path\">>, <<\"/loop\">>}\n\t]),\n\tok = ssl:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),\n\ttimer:sleep(100),\n\t[{cowboy_http2, Child, worker, [cowboy_http2]}] = supervisor:which_children(Pid),\n\ttrue = is_pid(Child),\n\tok.\n\nsupervisor_which_children_ws(Config) ->\n\tdoc(\"ws: The function supervisor:which_children/1 must work. \"\n\t\t\"Websocket connections never have children.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(clear_port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 5000),\n\t{ok, {http_response, {1, 1}, 101, _}, _} = erlang:decode_packet(http, Handshake, []),\n\ttimer:sleep(100),\n\tPid = get_remote_pid_tcp(Socket),\n\t[] = supervisor:which_children(Pid),\n\tok.\n\n%% Internal.\n\ndo_http2_handshake(Socket) ->\n\tok = ssl:send(Socket, \"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\"),\n\t{ok, <<_,_,_,4,_/bits>>} = ssl:recv(Socket, 0, 1000),\n\tok = ssl:send(Socket, [cow_http2:settings(#{}), cow_http2:settings_ack()]),\n\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = ssl:recv(Socket, 9, 1000),\n\tok.\n"
  },
  {
    "path": "test/tracer_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(tracer_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n-import(cowboy_test, [gun_down/1]).\n\n%% ct.\n\nsuite() ->\n\t[{timetrap, 30000}].\n\n%% We initialize trace patterns here. Appropriate would be in\n%% init_per_suite/1, but this works just as well.\nall() ->\n\t%% @todo Implement these tests for HTTP/3.\n\tcowboy_test:common_all() -- [{group, h3}, {group, h3_compress}].\n\ninit_per_suite(Config) ->\n\tcowboy_tracer_h:set_trace_patterns(),\n\tConfig.\n\nend_per_suite(_) ->\n\tok.\n\n%% We want tests for each group to execute sequentially\n%% because we need to modify the protocol options. Groups\n%% can run in parallel however.\ngroups() ->\n\tTests = ct_helper:all(?MODULE),\n\t[\n\t\t{http, [], Tests},\n\t\t{https, [], Tests},\n\t\t{h2, [], Tests},\n\t\t{h2c, [], Tests},\n\t\t{http_compress, [], Tests},\n\t\t{https_compress, [], Tests},\n\t\t{h2_compress, [], Tests},\n\t\t{h2c_compress, [], Tests}\n\t].\n\ninit_per_group(Name = http, Config) ->\n\tcowboy_test:init_http(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = https, Config) ->\n\tcowboy_test:init_http(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = h2, Config) ->\n\tcowboy_test:init_http2(Name, init_plain_opts(Config), Config);\ninit_per_group(Name = h2c, Config) ->\n\tConfig1 = cowboy_test:init_http(Name, init_plain_opts(Config), Config),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_per_group(Name = http_compress, Config) ->\n\tcowboy_test:init_http(Name, init_compress_opts(Config), Config);\ninit_per_group(Name = https_compress, Config) ->\n\tcowboy_test:init_http(Name, init_compress_opts(Config), Config);\ninit_per_group(Name = h2_compress, Config) ->\n\tcowboy_test:init_http2(Name, init_compress_opts(Config), Config);\ninit_per_group(Name = h2c_compress, Config) ->\n\tConfig1 = cowboy_test:init_http(Name, init_compress_opts(Config), Config),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2}).\n\nend_per_group(Name, _) ->\n\tcowboy:stop_listener(Name).\n\ninit_plain_opts(Config) ->\n\t#{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))},\n\t\tstream_handlers => [cowboy_tracer_h, cowboy_stream_h]\n\t}.\n\ninit_compress_opts(Config) ->\n\t#{\n\t\tenv => #{dispatch => cowboy_router:compile(init_routes(Config))},\n\t\tstream_handlers => [cowboy_tracer_h, cowboy_compress_h, cowboy_stream_h]\n\t}.\n\ninit_routes(_) -> [\n\t{\"localhost\", [\n\t\t{\"/\", hello_h, []},\n\t\t{\"/longer/hello/path\", hello_h, []}\n\t]}\n].\n\ndo_get(Path, Config) ->\n\t%% Perform a GET request.\n\tConnPid = gun_open(Config),\n\tRef = gun:get(ConnPid, Path, [\n\t\t{<<\"accept-encoding\">>, <<\"gzip\">>},\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\t{response, nofin, 200, _Headers} = gun:await(ConnPid, Ref),\n\t{ok, _Body} = gun:await_body(ConnPid, Ref),\n\tgun:close(ConnPid).\n\n%% We only care about cowboy_req:reply/4 calls and init/terminate events.\ndo_tracer_callback(Pid) ->\n\tfun\n\t\t(Event, _) when Event =:= init; Event =:= terminate ->\n\t\t\tPid ! Event,\n\t\t\t0;\n\t\t(Event={trace_ts, _, call, {cowboy_req, reply, _}, _}, State) ->\n\t\t\tPid ! Event,\n\t\t\tPid ! {state, State},\n\t\t\tState + 1;\n\t\t(_, State) ->\n\t\t\tState + 1\n\tend.\n\n%% Tests.\n\ninit(Config) ->\n\tdoc(\"Ensure the init event is triggered.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [fun(_,_,_) -> true end]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\tinit ->\n\t\t\tok\n\tend.\n\nterminate(Config) ->\n\tdoc(\"Ensure the terminate event is triggered.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [fun(_,_,_) -> true end]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\tterminate ->\n\t\t\tok\n\tend.\n\nstate(Config) ->\n\tdoc(\"Ensure the returned state is used.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [fun(_,_,_) -> true end]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\t{state, St} ->\n\t\t\ttrue = St > 0,\n\t\t\tok\n\tend.\n\nempty(Config) ->\n\tdoc(\"Empty match specs unconditionally enable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => []\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend.\n\npredicate_true(Config) ->\n\tdoc(\"Predicate function returns true, unconditionally enable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [fun(_,_,_) -> true end]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend.\n\npredicate_false(Config) ->\n\tdoc(\"Predicate function returns false, unconditionally disable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [fun(_,_,_) -> false end]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\tMsg when element(1, Msg) =:= trace_ts ->\n\t\t\terror(Msg)\n\tafter 100 ->\n\t\tok\n\tend.\n\nmethod(Config) ->\n\tdoc(\"Method is the same as the request's, enable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{method, <<\"GET\">>}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend.\n\nmethod_no_match(Config) ->\n\tdoc(\"Method is different from the request's, disable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{method, <<\"POST\">>}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\tMsg when element(1, Msg) =:= trace_ts ->\n\t\t\terror(Msg)\n\tafter 100 ->\n\t\tok\n\tend.\n\nhost(Config) ->\n\tdoc(\"Host is the same as the request's, enable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{host, <<\"localhost\">>}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend.\n\nhost_no_match(Config) ->\n\tdoc(\"Host is different from the request's, disable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{host, <<\"ninenines.eu\">>}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\tMsg when element(1, Msg) =:= trace_ts ->\n\t\t\terror(Msg)\n\tafter 100 ->\n\t\tok\n\tend.\n\npath(Config) ->\n\tdoc(\"Path is the same as the request's, enable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{path, <<\"/longer/hello/path\">>}]\n\t}),\n\tdo_get(\"/longer/hello/path\", Config),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend.\n\npath_no_match(Config) ->\n\tdoc(\"Path is different from the request's, disable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{path, <<\"/some/other/path\">>}]\n\t}),\n\tdo_get(\"/longer/hello/path\", Config),\n\treceive\n\t\tMsg when element(1, Msg) =:= trace_ts ->\n\t\t\terror(Msg)\n\tafter 100 ->\n\t\tok\n\tend.\n\npath_start(Config) ->\n\tdoc(\"Start of path is the same as request's, enable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{path_start, <<\"/longer/hello\">>}]\n\t}),\n\tdo_get(\"/longer/hello/path\", Config),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend.\n\npath_start_no_match(Config) ->\n\tdoc(\"Start of path is different from the request's, disable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{path_start, <<\"/shorter/hello\">>}]\n\t}),\n\tdo_get(\"/longer/hello/path\", Config),\n\treceive\n\t\tMsg when element(1, Msg) =:= trace_ts ->\n\t\t\terror(Msg)\n\tafter 100 ->\n\t\tok\n\tend.\n\nheader_defined(Config) ->\n\tdoc(\"Header is defined in the request, enable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{header, <<\"accept-encoding\">>}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend.\n\nheader_defined_no_match(Config) ->\n\tdoc(\"Header is not defined in the request, disable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{header, <<\"accept-language\">>}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\tMsg when element(1, Msg) =:= trace_ts ->\n\t\t\terror(Msg)\n\tafter 100 ->\n\t\tok\n\tend.\n\nheader_value(Config) ->\n\tdoc(\"Header value is the same as the request's, enable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{header, <<\"accept-encoding\">>, <<\"gzip\">>}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend.\n\nheader_value_no_match(Config) ->\n\tdoc(\"Header value is different from the request's, disable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{header, <<\"accept-encoding\">>, <<\"nope\">>}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\tMsg when element(1, Msg) =:= trace_ts ->\n\t\t\terror(Msg)\n\tafter 100 ->\n\t\tok\n\tend.\n\npeer_ip(Config) ->\n\tdoc(\"Peer IP is the same as the request's, enable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{peer_ip, {127, 0, 0, 1}}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend.\n\npeer_ip_no_match(Config) ->\n\tdoc(\"Peer IP is different from the request's, disable tracing.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [{peer_ip, {8, 8, 8, 8}}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\tMsg when element(1, Msg) =:= trace_ts ->\n\t\t\terror(Msg)\n\tafter 100 ->\n\t\tok\n\tend.\n\nmissing_callback(Config) ->\n\tdoc(\"Ensure the request is still processed if the callback is not provided.\"),\n\tRef = config(ref, Config),\n\tOpts0 = ranch:get_protocol_options(Ref),\n\tOpts = maps:remove(tracer_callback, Opts0),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_match_specs => [{method, <<\"GET\">>}]\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\tMsg when element(1, Msg) =:= trace_ts ->\n\t\t\terror(Msg)\n\tafter 100 ->\n\t\tok\n\tend.\n\nmissing_match_specs(Config) ->\n\tdoc(\"Ensure the request is still processed if match specs are not provided.\"),\n\tRef = config(ref, Config),\n\tOpts0 = ranch:get_protocol_options(Ref),\n\tOpts = maps:remove(tracer_match_specs, Opts0),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self())\n\t}),\n\tdo_get(\"/\", Config),\n\treceive\n\t\tMsg when element(1, Msg) =:= trace_ts ->\n\t\t\terror(Msg)\n\tafter 100 ->\n\t\tok\n\tend.\n\ntwo_matching_requests(Config) ->\n\tdoc(\"Perform two requests that enable tracing on the same connection.\"),\n\tRef = config(ref, Config),\n\tOpts = ranch:get_protocol_options(Ref),\n\tranch:set_protocol_options(Ref, Opts#{\n\t\ttracer_callback => do_tracer_callback(self()),\n\t\ttracer_match_specs => [fun(_,_,_) -> true end]\n\t}),\n\t%% Perform a GET request.\n\tConnPid = gun_open(Config),\n\tRef1 = gun:get(ConnPid, \"/\", []),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref1),\n\t{ok, _} = gun:await_body(ConnPid, Ref1),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend,\n\t%% Perform a second GET request on the same connection.\n\tRef2 = gun:get(ConnPid, \"/\", []),\n\t{response, nofin, 200, _} = gun:await(ConnPid, Ref2),\n\t{ok, _} = gun:await_body(ConnPid, Ref2),\n\treceive\n\t\t{trace_ts, _, call, {cowboy_req, reply, [200, _, _, _]}, _} ->\n\t\t\tok\n\tend,\n\tgun:close(ConnPid).\n"
  },
  {
    "path": "test/ws_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(ws_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n\n%% ct.\n\nall() ->\n\t[{group, ws}].\n\ngroups() ->\n\t[{ws, [parallel], ct_helper:all(?MODULE)}].\n\ninit_per_group(Name, Config) ->\n\tcowboy_test:init_http(Name, #{\n\t\tenv => #{dispatch => init_dispatch()}\n\t}, Config).\n\nend_per_group(Listener, _Config) ->\n\tcowboy:stop_listener(Listener).\n\n%% Dispatch configuration.\n\ninit_dispatch() ->\n\tcowboy_router:compile([\n\t\t{\"localhost\", [\n\t\t\t{\"/ws_echo\", ws_echo, []},\n\t\t\t{\"/ws_echo_timer\", ws_echo_timer, []},\n\t\t\t{\"/ws_init\", ws_init_h, #{}},\n\t\t\t{\"/ws_init_shutdown\", ws_init_shutdown, []},\n\t\t\t{\"/ws_send_many\", ws_send_many, [\n\t\t\t\t{sequence, [\n\t\t\t\t\t{text, <<\"one\">>},\n\t\t\t\t\t{text, <<\"two\">>},\n\t\t\t\t\t{text, <<\"seven!\">>}]}\n\t\t\t]},\n\t\t\t{\"/ws_send_close\", ws_send_many, [\n\t\t\t\t{sequence, [\n\t\t\t\t\t{text, <<\"send\">>},\n\t\t\t\t\tclose,\n\t\t\t\t\t{text, <<\"won't be received\">>}]}\n\t\t\t]},\n\t\t\t{\"/ws_send_close_payload\", ws_send_many, [\n\t\t\t\t{sequence, [\n\t\t\t\t\t{text, <<\"send\">>},\n\t\t\t\t\t{close, 1001, <<\"some text!\">>},\n\t\t\t\t\t{text, <<\"won't be received\">>}]}\n\t\t\t]},\n\t\t\t{\"/ws_subprotocol\", ws_subprotocol, []},\n\t\t\t{\"/terminate\", ws_terminate_h, []},\n\t\t\t{\"/ws_timeout_hibernate\", ws_timeout_hibernate, []},\n\t\t\t{\"/ws_timeout_cancel\", ws_timeout_cancel, []},\n\t\t\t{\"/ws_max_frame_size\", ws_max_frame_size, []},\n\t\t\t{\"/ws_deflate_opts\", ws_deflate_opts_h, []},\n\t\t\t{\"/ws_dont_validate_utf8\", ws_dont_validate_utf8_h, []},\n\t\t\t{\"/ws_ping\", ws_ping_h, []}\n\t\t]}\n\t]).\n\n%% Tests.\n\nunlimited_connections(Config) ->\n\tdoc(\"Websocket connections are not limited. The connections \"\n\t\t\"are removed from the count after the handshake completes.\"),\n\tcase os:type() of\n\t\t{win32, _} ->\n\t\t\t{skip, \"Tests that use too many sockets are disabled on Windows \"\n\t\t\t\t\"to prevent intermittent failures.\"};\n\t\t{unix, _} ->\n\t\t\tcase list_to_integer(os:cmd(\"printf `ulimit -n`\")) of\n\t\t\t\tLimit when Limit > 6100 ->\n\t\t\t\t\tdo_unlimited_connections(Config);\n\t\t\t\t_ ->\n\t\t\t\t\t{skip, \"`ulimit -n` reports a limit too low for this test.\"}\n\t\t\tend\n\tend.\n\ndo_unlimited_connections(Config) ->\n\t_ = [begin\n\t\tspawn_link(fun() -> do_connect_and_loop(Config) end),\n\t\ttimer:sleep(1)\n\tend || _ <- lists:seq(1, 3000)],\n\ttimer:sleep(1000),\n\t%% We have at least 3000 client and 3000 server sockets.\n\ttrue = length(erlang:ports()) > 6000,\n\t%% Ranch thinks we have no connections.\n\t0 = ranch_server:count_connections(ws),\n\tok.\n\ndo_connect_and_loop(Config) ->\n\t{ok, Socket, _} = do_handshake(\"/ws_echo\", Config),\n\tdo_loop(Socket).\n\ndo_loop(Socket) ->\n\t%% Masked text hello echoed back clear by the server.\n\tMask = 16#37fa213d,\n\tMaskedHello = do_mask(<<\"Hello\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\ttimer:sleep(1000),\n\tdo_loop(Socket).\n\nws0(Config) ->\n\tdoc(\"Websocket version 0 (hixie-76 draft) is no longer supported.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket,\n\t\t\"GET /ws_echo_timer HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Upgrade: WebSocket\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-Websocket-Key1: Y\\\" 4 1Lj!957b8@0H756!i\\r\\n\"\n\t\t\"Sec-Websocket-Key2: 1711 M;4\\\\74  80<6\\r\\n\"\n\t\t\"\\r\\n\"),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),\n\t{ok, {http_response, {1, 1}, 400, _}, _} = erlang:decode_packet(http, Handshake, []),\n\tok.\n\nws7(Config) ->\n\tdoc(\"Websocket version 7 (draft) is supported.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET /ws_echo_timer HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"Sec-WebSocket-Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 7\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),\n\t{ok, {http_response, {1, 1}, 101, _}, Rest} = erlang:decode_packet(http, Handshake, []),\n\t[Headers, <<>>] = do_decode_headers(erlang:decode_packet(httph, Rest, []), []),\n\t{_, \"Upgrade\"} = lists:keyfind('Connection', 1, Headers),\n\t{_, \"websocket\"} = lists:keyfind('Upgrade', 1, Headers),\n\t{_, \"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\"} = lists:keyfind(\"sec-websocket-accept\", 1, Headers),\n\tdo_ws_version(Socket).\n\nws8(Config) ->\n\tdoc(\"Websocket version 8 (draft) is supported.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET /ws_echo_timer HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"Sec-WebSocket-Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 8\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),\n\t{ok, {http_response, {1, 1}, 101, _}, Rest} = erlang:decode_packet(http, Handshake, []),\n\t[Headers, <<>>] = do_decode_headers(erlang:decode_packet(httph, Rest, []), []),\n\t{_, \"Upgrade\"} = lists:keyfind('Connection', 1, Headers),\n\t{_, \"websocket\"} = lists:keyfind('Upgrade', 1, Headers),\n\t{_, \"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\"} = lists:keyfind(\"sec-websocket-accept\", 1, Headers),\n\tdo_ws_version(Socket).\n\nws13(Config) ->\n\tdoc(\"Websocket version 13 (RFC) is supported.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_echo_timer\", Config),\n\tdo_ws_version(Socket).\n\ndo_ws_version(Socket) ->\n\t%% Masked text hello echoed back clear by the server.\n\tMask = 16#37fa213d,\n\tMaskedHello = do_mask(<<\"Hello\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Empty binary frame echoed back.\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 0:7, 0:32 >>),\n\t{ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Masked binary hello echoed back clear by the server.\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\t{ok, << 1:1, 0:3, 2:4, 0:1, 5:7, \"Hello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Frames sent on timer by the handler.\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 14:7, \"websocket_init\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, \"websocket_handle\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, \"websocket_handle\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, \"websocket_handle\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Client-initiated ping/pong.\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>),\n\t{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Client-initiated close.\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>),\n\t{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_deflate_max_frame_size_close(Config) ->\n\tdoc(\"Server closes connection when decompressed frame size exceeds max_frame_size option\"),\n\t%% max_frame_size is set to 8 bytes in ws_max_frame_size.\n\t{ok, Socket, Headers} = do_handshake(\"/ws_max_frame_size\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate\\r\\n\", Config),\n\t{_, \"permessage-deflate\"} = lists:keyfind(\"sec-websocket-extensions\", 1, Headers),\n\tMask = 16#11223344,\n\tZ = zlib:open(),\n\tzlib:deflateInit(Z, best_compression, deflated, -15, 8, default),\n\tCompressedData0 = iolist_to_binary(zlib:deflate(Z, <<0:800>>, sync)),\n\tCompressedData = binary:part(CompressedData0, 0, byte_size(CompressedData0) - 4),\n\tMaskedData = do_mask(CompressedData, Mask, <<>>),\n\tLen = byte_size(MaskedData),\n\ttrue = Len < 8,\n\tok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, Len:7, Mask:32, MaskedData/binary >>),\n\t{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1009:16 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_deflate_opts_client_context_takeover(Config) ->\n\tdoc(\"Handler is configured with client context takeover enabled.\"),\n\t{ok, _, Headers1} = do_handshake(\"/ws_deflate_opts?client_context_takeover\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate\\r\\n\", Config),\n\t{_, \"permessage-deflate\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers1),\n\t{ok, _, Headers2} = do_handshake(\"/ws_deflate_opts?client_context_takeover\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover\\r\\n\", Config),\n\t{_, \"permessage-deflate; client_no_context_takeover\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers2),\n\tok.\n\nws_deflate_opts_client_no_context_takeover(Config) ->\n\tdoc(\"Handler is configured with client context takeover disabled.\"),\n\t{ok, _, Headers1} = do_handshake(\"/ws_deflate_opts?client_no_context_takeover\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate\\r\\n\", Config),\n\t{_, \"permessage-deflate; client_no_context_takeover\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers1),\n\t{ok, _, Headers2} = do_handshake(\"/ws_deflate_opts?client_no_context_takeover\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate; client_no_context_takeover\\r\\n\", Config),\n\t{_, \"permessage-deflate; client_no_context_takeover\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers2),\n\tok.\n\n%% We must send client_max_window_bits to indicate we support it.\nws_deflate_opts_client_max_window_bits(Config) ->\n\tdoc(\"Handler is configured with client max window bits.\"),\n\t{ok, _, Headers} = do_handshake(\"/ws_deflate_opts?client_max_window_bits\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\\r\\n\", Config),\n\t{_, \"permessage-deflate; client_max_window_bits=9\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers),\n\tok.\n\nws_deflate_opts_client_max_window_bits_override(Config) ->\n\tdoc(\"Handler is configured with client max window bits.\"),\n\t{ok, _, Headers1} = do_handshake(\"/ws_deflate_opts?client_max_window_bits\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=8\\r\\n\", Config),\n\t{_, \"permessage-deflate; client_max_window_bits=8\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers1),\n\t{ok, _, Headers2} = do_handshake(\"/ws_deflate_opts?client_max_window_bits\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=12\\r\\n\", Config),\n\t{_, \"permessage-deflate; client_max_window_bits=9\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers2),\n\tok.\n\n%% @todo This might be better in an rfc7692_SUITE.\n%%\n%%   7.1.2.2\n%%   If a received extension negotiation offer doesn't have the\n%%   \"client_max_window_bits\" extension parameter, the corresponding\n%%   extension negotiation response to the offer MUST NOT include the\n%%   \"client_max_window_bits\" extension parameter.\nws_deflate_opts_client_max_window_bits_only_in_server(Config) ->\n\tdoc(\"Handler is configured with non-default client max window bits but \"\n\t\t\"client doesn't send the parameter; compression is disabled.\"),\n\t{ok, _, Headers} = do_handshake(\"/ws_deflate_opts?client_max_window_bits\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate\\r\\n\", Config),\n\tfalse = lists:keyfind(\"sec-websocket-extensions\", 1, Headers),\n\tok.\n\nws_deflate_opts_server_context_takeover(Config) ->\n\tdoc(\"Handler is configured with server context takeover enabled.\"),\n\t{ok, _, Headers1} = do_handshake(\"/ws_deflate_opts?server_context_takeover\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate\\r\\n\", Config),\n\t{_, \"permessage-deflate\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers1),\n\t{ok, _, Headers2} = do_handshake(\"/ws_deflate_opts?server_context_takeover\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover\\r\\n\", Config),\n\t{_, \"permessage-deflate; server_no_context_takeover\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers2),\n\tok.\n\nws_deflate_opts_server_no_context_takeover(Config) ->\n\tdoc(\"Handler is configured with server context takeover disabled.\"),\n\t{ok, _, Headers1} = do_handshake(\"/ws_deflate_opts?server_no_context_takeover\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate\\r\\n\", Config),\n\t{_, \"permessage-deflate; server_no_context_takeover\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers1),\n\t{ok, _, Headers2} = do_handshake(\"/ws_deflate_opts?server_no_context_takeover\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover\\r\\n\", Config),\n\t{_, \"permessage-deflate; server_no_context_takeover\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers2),\n\tok.\n\nws_deflate_opts_server_max_window_bits(Config) ->\n\tdoc(\"Handler is configured with server max window bits.\"),\n\t{ok, _, Headers} = do_handshake(\"/ws_deflate_opts?server_max_window_bits\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate\\r\\n\", Config),\n\t{_, \"permessage-deflate; server_max_window_bits=9\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers),\n\tok.\n\nws_deflate_opts_server_max_window_bits_override(Config) ->\n\tdoc(\"Handler is configured with server max window bits.\"),\n\t{ok, _, Headers1} = do_handshake(\"/ws_deflate_opts?server_max_window_bits\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=8\\r\\n\", Config),\n\t{_, \"permessage-deflate; server_max_window_bits=8\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers1),\n\t{ok, _, Headers2} = do_handshake(\"/ws_deflate_opts?server_max_window_bits\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=12\\r\\n\", Config),\n\t{_, \"permessage-deflate; server_max_window_bits=9\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers2),\n\tok.\n\nws_deflate_opts_zlevel(Config) ->\n\tdoc(\"Handler is configured with zlib level.\"),\n\tdo_ws_deflate_opts_z(\"/ws_deflate_opts?level\", Config).\n\nws_deflate_opts_zmemlevel(Config) ->\n\tdoc(\"Handler is configured with zlib mem_level.\"),\n\tdo_ws_deflate_opts_z(\"/ws_deflate_opts?mem_level\", Config).\n\nws_deflate_opts_zstrategy(Config) ->\n\tdoc(\"Handler is configured with zlib strategy.\"),\n\tdo_ws_deflate_opts_z(\"/ws_deflate_opts?strategy\", Config).\n\ndo_ws_deflate_opts_z(Path, Config) ->\n\t{ok, Socket, Headers} = do_handshake(Path,\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate\\r\\n\", Config),\n\t{_, \"permessage-deflate\"} = lists:keyfind(\"sec-websocket-extensions\", 1, Headers),\n\t%% Send and receive a compressed \"Hello\" frame.\n\tMask = 16#11223344,\n\tCompressedHello = << 242, 72, 205, 201, 201, 7, 0 >>,\n\tMaskedHello = do_mask(CompressedHello, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32, MaskedHello/binary >>),\n\t{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, CompressedHello/binary >>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Client-initiated close.\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>),\n\t{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_dont_validate_utf8(Config) ->\n\tdoc(\"Handler is configured with UTF-8 validation disabled.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_dont_validate_utf8\", Config),\n\t%% Send an invalid UTF-8 text frame and receive it back.\n\tMask = 16#37fa213d,\n\tMaskedInvalid = do_mask(<<255, 255, 255, 255>>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, <<1:1, 0:3, 1:4, 1:1, 4:7, Mask:32, MaskedInvalid/binary>>),\n\t{ok, <<1:1, 0:3, 1:4, 0:1, 4:7, 255, 255, 255, 255>>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_first_frame_with_handshake(Config) ->\n\tdoc(\"Client sends the first frame immediately with the handshake. \"\n\t\t\"This is invalid according to the protocol but we still want \"\n\t\t\"to accept it if the handshake is successful.\"),\n\tMask = 16#37fa213d,\n\tMaskedHello = do_mask(<<\"Hello\">>, Mask, <<>>),\n\t{ok, Socket, _} = do_handshake(\"/ws_echo\", \"\",\n\t\t<<1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary>>,\n\t\tConfig),\n\t{ok, <<1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\">>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\n%% @todo Move these tests to ws_handler_SUITE.\nws_init_return_ok(Config) ->\n\tdoc(\"Handler does nothing.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_init?ok\", Config),\n\t%% The handler does nothing; nothing should happen here.\n\t{error, timeout} = gen_tcp:recv(Socket, 0, 1000),\n\tok.\n\nws_init_return_ok_hibernate(Config) ->\n\tdoc(\"Handler does nothing; hibernates.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_init?ok_hibernate\", Config),\n\t%% The handler does nothing; nothing should happen here.\n\t{error, timeout} = gen_tcp:recv(Socket, 0, 1000),\n\tok.\n\nws_init_return_reply(Config) ->\n\tdoc(\"Handler sends a text frame just after the handshake.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_init?reply\", Config),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_init_return_reply_hibernate(Config) ->\n\tdoc(\"Handler sends a text frame just after the handshake and then hibernates.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_init?reply_hibernate\", Config),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_init_return_reply_close(Config) ->\n\tdoc(\"Handler closes immediately after the handshake.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_init?reply_close\", Config),\n\t{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_init_return_reply_close_hibernate(Config) ->\n\tdoc(\"Handler closes immediately after the handshake, then attempts to hibernate.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_init?reply_close_hibernate\", Config),\n\t{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_init_return_reply_many(Config) ->\n\tdoc(\"Handler sends many frames just after the handshake.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_init?reply_many\", Config),\n\t%% We catch all frames at once and check them directly.\n\t{ok, <<\n\t\t1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\",\n\t\t1:1, 0:3, 2:4, 0:1, 5:7, \"World\" >>} = gen_tcp:recv(Socket, 14, 6000),\n\tok.\n\nws_init_return_reply_many_hibernate(Config) ->\n\tdoc(\"Handler sends many frames just after the handshake and then hibernates.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_init?reply_many_hibernate\", Config),\n\t%% We catch all frames at once and check them directly.\n\t{ok, <<\n\t\t1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\",\n\t\t1:1, 0:3, 2:4, 0:1, 5:7, \"World\" >>} = gen_tcp:recv(Socket, 14, 6000),\n\tok.\n\nws_init_return_reply_many_close(Config) ->\n\tdoc(\"Handler sends many frames including a close frame just after the handshake.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_init?reply_many_close\", Config),\n\t%% We catch all frames at once and check them directly.\n\t{ok, <<\n\t\t1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\",\n\t\t1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\nws_init_return_reply_many_close_hibernate(Config) ->\n\tdoc(\"Handler sends many frames including a close frame just after the handshake and then hibernates.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_init?reply_many_close_hibernate\", Config),\n\t%% We catch all frames at once and check them directly.\n\t{ok, <<\n\t\t1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\",\n\t\t1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 9, 6000),\n\tok.\n\nws_init_shutdown_before_handshake(Config) ->\n\tdoc(\"Handler stops before Websocket handshake.\"),\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET /ws_init_shutdown HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\"\n\t\t\"\\r\\n\"]),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),\n\t{ok, {http_response, {1, 1}, 403, _}, _Rest} = erlang:decode_packet(http, Handshake, []),\n\tok.\n\nws_max_frame_size_close(Config) ->\n\tdoc(\"Server closes connection when frame size exceeds max_frame_size option\"),\n\t%% max_frame_size is set to 8 bytes in ws_max_frame_size.\n\t{ok, Socket, _} = do_handshake(\"/ws_max_frame_size\", Config),\n\tMask = 16#11223344,\n\tMaskedHello = do_mask(<<\"HelloHello\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 10:7, Mask:32, MaskedHello/binary >>),\n\t{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1009:16 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_max_frame_size_final_fragment_close(Config) ->\n\tdoc(\"Server closes connection when final fragmented frame \"\n\t\t\"exceeds max_frame_size option\"),\n\t%% max_frame_size is set to 8 bytes in ws_max_frame_size.\n\t{ok, Socket, _} = do_handshake(\"/ws_max_frame_size\", Config),\n\tMask = 16#11223344,\n\tMaskedHello = do_mask(<<\"Hello\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 0:1, 0:3, 2:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 0:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\t{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1009:16 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_max_frame_size_intermediate_fragment_close(Config) ->\n\tdoc(\"Server closes connection when intermediate fragmented frame \"\n\t\t\"exceeds max_frame_size option\"),\n\t%% max_frame_size is set to 8 bytes in ws_max_frame_size.\n\t{ok, Socket, _} = do_handshake(\"/ws_max_frame_size\", Config),\n\tMask = 16#11223344,\n\tMaskedHello = do_mask(<<\"Hello\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 0:1, 0:3, 2:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\tok = gen_tcp:send(Socket, << 0:1, 0:3, 0:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 0:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\t{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1009:16 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_ping(Config) ->\n\tdoc(\"Server initiated pings can receive a pong in response.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_ping\", Config),\n\t%% Receive a server-sent ping.\n\t{ok, << 1:1, 0:3, 9:4, 0:1, 0:7 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Send a pong back with a 0 mask.\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 10:4, 1:1, 0:7, 0:32 >>),\n\t%% Receive a text frame as a result.\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 4:7, \"OK!!\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_send_close(Config) ->\n\tdoc(\"Server-initiated close frame ends the connection.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_send_close\", Config),\n\t%% We catch all frames at once and check them directly.\n\t{ok, <<\n\t\t1:1, 0:3, 1:4, 0:1, 4:7, \"send\",\n\t\t1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 8, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_send_close_payload(Config) ->\n\tdoc(\"Server-initiated close frame with payload ends the connection.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_send_close_payload\", Config),\n\t%% We catch all frames at once and check them directly.\n\t{ok, <<\n\t\t1:1, 0:3, 1:4, 0:1, 4:7, \"send\",\n\t\t1:1, 0:3, 8:4, 0:1, 12:7, 1001:16, \"some text!\" >>} = gen_tcp:recv(Socket, 20, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_send_many(Config) ->\n\tdoc(\"Server sends many frames in a single reply.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_send_many\", Config),\n\t%% We catch all frames at once and check them directly.\n\t{ok, <<\n\t\t1:1, 0:3, 1:4, 0:1, 3:7, \"one\",\n\t\t1:1, 0:3, 1:4, 0:1, 3:7, \"two\",\n\t\t1:1, 0:3, 1:4, 0:1, 6:7, \"seven!\" >>} = gen_tcp:recv(Socket, 18, 6000),\n\tok.\n\nws_single_bytes(Config) ->\n\tdoc(\"Client sends a text frame one byte at a time.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_echo\", Config),\n\t%% We sleep between sends to make sure only one byte is sent.\n\tok = gen_tcp:send(Socket, << 16#81 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#85 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#37 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#fa >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#21 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#3d >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#7f >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#9f >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#4d >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#51 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#58 >>),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_subprotocol(Config) ->\n\tdoc(\"Websocket sub-protocol negotiation.\"),\n\t{ok, _, Headers} = do_handshake(\"/ws_subprotocol\",\n\t\t\"Sec-WebSocket-Protocol: foo, bar\\r\\n\", Config),\n\t{_, \"foo\"} = lists:keyfind(\"sec-websocket-protocol\", 1, Headers),\n\tok.\n\nws_text_fragments(Config) ->\n\tdoc(\"Client sends fragmented text frames.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_echo\", Config),\n\t%% Send two \"Hello\" over two fragments and two sends.\n\tMask = 16#37fa213d,\n\tMaskedHello = do_mask(<<\"Hello\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 0:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 0:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 10:7, \"HelloHello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Send three \"Hello\" over three fragments and one send.\n\tok = gen_tcp:send(Socket, [\n\t\t<< 0:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>,\n\t\t<< 0:1, 0:3, 0:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>,\n\t\t<< 1:1, 0:3, 0:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>]),\n\t{ok, << 1:1, 0:3, 1:4, 0:1, 15:7, \"HelloHelloHello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_timeout_hibernate(Config) ->\n\tdoc(\"Server-initiated close on timeout with hibernating process.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_timeout_hibernate\", Config),\n\t{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_timeout_no_cancel(Config) ->\n\tdoc(\"Server-initiated timeout is not influenced by reception of Erlang messages.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_timeout_cancel\", Config),\n\t{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_timeout_reset(Config) ->\n\tdoc(\"Server-initiated timeout is reset when client sends more data.\"),\n\t{ok, Socket, _} = do_handshake(\"/ws_timeout_cancel\", Config),\n\t%% Send and receive back a frame a few times.\n\tMask = 16#37fa213d,\n\tMaskedHello = do_mask(<<\"Hello\">>, Mask, <<>>),\n\t[begin\n\t\tok = gen_tcp:send(Socket, << 1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),\n\t\t{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\" >>} = gen_tcp:recv(Socket, 0, 6000),\n\t\ttimer:sleep(500)\n\tend || _ <- [1, 2, 3, 4]],\n\t%% Timeout will occur after we stop sending data.\n\t{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_webkit_deflate(Config) ->\n\tdoc(\"x-webkit-deflate-frame compression.\"),\n\t{ok, Socket, Headers} = do_handshake(\"/ws_echo\",\n\t\t\"Sec-WebSocket-Extensions: x-webkit-deflate-frame\\r\\n\", Config),\n\t{_, \"x-webkit-deflate-frame\"} = lists:keyfind(\"sec-websocket-extensions\", 1, Headers),\n\t%% Send and receive a compressed \"Hello\" frame.\n\tMask = 16#11223344,\n\tCompressedHello = << 242, 72, 205, 201, 201, 7, 0 >>,\n\tMaskedHello = do_mask(CompressedHello, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32, MaskedHello/binary >>),\n\t{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, CompressedHello/binary >>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Client-initiated close.\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>),\n\t{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_webkit_deflate_fragments(Config) ->\n\tdoc(\"Client sends an x-webkit-deflate-frame compressed and fragmented text frame.\"),\n\t{ok, Socket, Headers} = do_handshake(\"/ws_echo\",\n\t\t\"Sec-WebSocket-Extensions: x-webkit-deflate-frame\\r\\n\", Config),\n\t{_, \"x-webkit-deflate-frame\"} = lists:keyfind(\"sec-websocket-extensions\", 1, Headers),\n\t%% Send a compressed \"Hello\" over two fragments and two sends.\n\tMask = 16#11223344,\n\tCompressedHello = << 242, 72, 205, 201, 201, 7, 0 >>,\n\tMaskedHello1 = do_mask(binary:part(CompressedHello, 0, 4), Mask, <<>>),\n\tMaskedHello2 = do_mask(binary:part(CompressedHello, 4, 3), Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 0:1, 1:1, 0:2, 1:4, 1:1, 4:7, Mask:32, MaskedHello1/binary >>),\n\tok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 0:4, 1:1, 3:7, Mask:32, MaskedHello2/binary >>),\n\t{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, CompressedHello/binary >>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nws_webkit_deflate_single_bytes(Config) ->\n\tdoc(\"Client sends an x-webkit-deflate-frame compressed text frame one byte at a time.\"),\n\t{ok, Socket, Headers} = do_handshake(\"/ws_echo\",\n\t\t\"Sec-WebSocket-Extensions: x-webkit-deflate-frame\\r\\n\", Config),\n\t{_, \"x-webkit-deflate-frame\"} = lists:keyfind(\"sec-websocket-extensions\", 1, Headers),\n\t%% We sleep between sends to make sure only one byte is sent.\n\tMask = 16#11223344,\n\tCompressedHello = << 242, 72, 205, 201, 201, 7, 0 >>,\n\tMaskedHello = do_mask(CompressedHello, Mask, <<>>),\n\tok = gen_tcp:send(Socket, << 16#c1 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#87 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#11 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#22 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#33 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, << 16#44 >>), timer:sleep(100),\n\tok = gen_tcp:send(Socket, [binary:at(MaskedHello, 0)]), timer:sleep(100),\n\tok = gen_tcp:send(Socket, [binary:at(MaskedHello, 1)]), timer:sleep(100),\n\tok = gen_tcp:send(Socket, [binary:at(MaskedHello, 2)]), timer:sleep(100),\n\tok = gen_tcp:send(Socket, [binary:at(MaskedHello, 3)]), timer:sleep(100),\n\tok = gen_tcp:send(Socket, [binary:at(MaskedHello, 4)]), timer:sleep(100),\n\tok = gen_tcp:send(Socket, [binary:at(MaskedHello, 5)]), timer:sleep(100),\n\tok = gen_tcp:send(Socket, [binary:at(MaskedHello, 6)]),\n\t{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, CompressedHello/binary >>} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\n%% Internal.\n\ndo_handshake(Path, Config) ->\n\tdo_handshake(Path, \"\", \"\", Config).\n\ndo_handshake(Path, ExtraHeaders, Config) ->\n\tdo_handshake(Path, ExtraHeaders, \"\", Config).\n\ndo_handshake(Path, ExtraHeaders, ExtraData, Config) ->\n\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config),\n\t\t[binary, {active, false}]),\n\tok = gen_tcp:send(Socket, [\n\t\t\"GET \", Path, \" HTTP/1.1\\r\\n\"\n\t\t\"Host: localhost\\r\\n\"\n\t\t\"Connection: Upgrade\\r\\n\"\n\t\t\"Origin: http://localhost\\r\\n\"\n\t\t\"Sec-WebSocket-Version: 13\\r\\n\"\n\t\t\"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\\r\\n\"\n\t\t\"Upgrade: websocket\\r\\n\",\n\t\tExtraHeaders,\n\t\t\"\\r\\n\",\n\t\tExtraData]),\n\t{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),\n\t{ok, {http_response, {1, 1}, 101, _}, Rest} = erlang:decode_packet(http, Handshake, []),\n\t[Headers, Data] = do_decode_headers(erlang:decode_packet(httph, Rest, []), []),\n\t%% Queue extra data back, if any. We don't want to receive it yet.\n\tcase Data of\n\t\t<<>> -> ok;\n\t\t_ -> gen_tcp:unrecv(Socket, Data)\n\tend,\n\t{_, \"Upgrade\"} = lists:keyfind('Connection', 1, Headers),\n\t{_, \"websocket\"} = lists:keyfind('Upgrade', 1, Headers),\n\t{_, \"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\"} = lists:keyfind(\"sec-websocket-accept\", 1, Headers),\n\t{ok, Socket, Headers}.\n\ndo_decode_headers({ok, http_eoh, Rest}, Acc) ->\n\t[Acc, Rest];\ndo_decode_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->\n\tF = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,\n\tdo_decode_headers(erlang:decode_packet(httph, Rest, []), [{F(Key), Value}|Acc]).\n\ndo_mask(<<>>, _, Acc) ->\n\tAcc;\ndo_mask(<< O:32, Rest/bits >>, MaskKey, Acc) ->\n\tT = O bxor MaskKey,\n\tdo_mask(Rest, MaskKey, << Acc/binary, T:32 >>);\ndo_mask(<< O:24 >>, MaskKey, Acc) ->\n\t<< MaskKey2:24, _:8 >> = << MaskKey:32 >>,\n\tT = O bxor MaskKey2,\n\t<< Acc/binary, T:24 >>;\ndo_mask(<< O:16 >>, MaskKey, Acc) ->\n\t<< MaskKey2:16, _:16 >> = << MaskKey:32 >>,\n\tT = O bxor MaskKey2,\n\t<< Acc/binary, T:16 >>;\ndo_mask(<< O:8 >>, MaskKey, Acc) ->\n\t<< MaskKey2:8, _:24 >> = << MaskKey:32 >>,\n\tT = O bxor MaskKey2,\n\t<< Acc/binary, T:8 >>.\n"
  },
  {
    "path": "test/ws_SUITE_data/ws_echo.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n-module(ws_echo).\n\n-export([init/2]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, _) ->\n\t{cowboy_websocket, Req, undefined, #{\n\t\tdata_delivery => relay,\n\t\tcompress => true\n\t}}.\n\nwebsocket_handle({text, Data}, State) ->\n\t{[{text, Data}], State};\nwebsocket_handle({binary, Data}, State) ->\n\t{[{binary, Data}], State};\nwebsocket_handle(_Frame, State) ->\n\t{[], State}.\n\nwebsocket_info(_Info, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/ws_SUITE_data/ws_echo_timer.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n-module(ws_echo_timer).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, _) ->\n\t{cowboy_websocket, Req, undefined}.\n\nwebsocket_init(State) ->\n\terlang:start_timer(1000, self(), <<\"websocket_init\">>),\n\t{[], State}.\n\nwebsocket_handle({text, Data}, State) ->\n\t{[{text, Data}], State};\nwebsocket_handle({binary, Data}, State) ->\n\t{[{binary, Data}], State};\nwebsocket_handle(_Frame, State) ->\n\t{[], State}.\n\nwebsocket_info({timeout, _Ref, Msg}, State) ->\n\terlang:start_timer(1000, self(), <<\"websocket_handle\">>),\n\t{[{text, Msg}], State};\nwebsocket_info(_Info, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/ws_SUITE_data/ws_init_shutdown.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n-module(ws_init_shutdown).\n\n-export([init/2]).\n\ninit(Req, Opts) ->\n\t{ok, cowboy_req:reply(403, Req), Opts}.\n"
  },
  {
    "path": "test/ws_SUITE_data/ws_max_frame_size.erl",
    "content": "-module(ws_max_frame_size).\n\n-export([init/2]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, State) ->\n\t{cowboy_websocket, Req, State, #{max_frame_size => 8, compress => true}}.\n\nwebsocket_handle({text, Data}, State) ->\n\t{[{text, Data}], State};\nwebsocket_handle({binary, Data}, State) ->\n\t{[{binary, Data}], State};\nwebsocket_handle(_Frame, State) ->\n\t{[], State}.\n\nwebsocket_info(_Info, State) ->\n\t{[], State}.\n"
  },
  {
    "path": "test/ws_SUITE_data/ws_send_many.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n-module(ws_send_many).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\t{cowboy_websocket, Req, Opts}.\n\nwebsocket_init(State) ->\n\terlang:send_after(10, self(), send_many),\n\t{[], State}.\n\nwebsocket_handle(_Frame, State) ->\n\t{[], State}.\n\nwebsocket_info(send_many, State = [{sequence, Sequence}]) ->\n\t{Sequence, State}.\n"
  },
  {
    "path": "test/ws_SUITE_data/ws_subprotocol.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n-module(ws_subprotocol).\n\n-export([init/2]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, Opts) ->\n\t[Protocol | _] = cowboy_req:parse_header(<<\"sec-websocket-protocol\">>, Req),\n\tReq2 = cowboy_req:set_resp_header(<<\"sec-websocket-protocol\">>, Protocol, Req),\n\t{cowboy_websocket, Req2, Opts, #{\n\t\tidle_timeout => 1000\n\t}}.\n\nwebsocket_handle(_Frame, State) ->\n\t{ok, State}.\n\nwebsocket_info(_Info, State) ->\n\t{ok, State}.\n"
  },
  {
    "path": "test/ws_SUITE_data/ws_timeout_cancel.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n-module(ws_timeout_cancel).\n\n-export([init/2]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, _) ->\n\terlang:start_timer(500, self(), should_not_cancel_timer),\n\t{cowboy_websocket, Req, undefined, #{\n\t\tidle_timeout => 1000\n\t}}.\n\nwebsocket_handle({text, Data}, State) ->\n\t{[{text, Data}], State};\nwebsocket_handle({binary, Data}, State) ->\n\t{[{binary, Data}], State}.\n\nwebsocket_info(_Info, State) ->\n\terlang:start_timer(500, self(), should_not_cancel_timer),\n\t{[], State}.\n"
  },
  {
    "path": "test/ws_SUITE_data/ws_timeout_hibernate.erl",
    "content": "%% Feel free to use, reuse and abuse the code in this file.\n\n-module(ws_timeout_hibernate).\n\n-export([init/2]).\n-export([websocket_init/1]).\n-export([websocket_handle/2]).\n-export([websocket_info/2]).\n\ninit(Req, _) ->\n\t{cowboy_websocket, Req, undefined, #{\n\t\tidle_timeout => 1000\n\t}}.\n\nwebsocket_init(State) ->\n\t{ok, State, hibernate}.\n\nwebsocket_handle(_Frame, State) ->\n\t{ok, State, hibernate}.\n\nwebsocket_info(_Info, State) ->\n\t{ok, State, hibernate}.\n"
  },
  {
    "path": "test/ws_autobahn_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(ws_autobahn_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n\n%% ct.\n\nall() ->\n\t[{group, autobahn}].\n\ngroups() ->\n\t[{autobahn, [], [autobahn_fuzzingclient]}].\n\ninit_per_group(Name, Config) ->\n\t%% Some systems have it named pip2.\n\tOut = os:cmd(\"pip show autobahntestsuite ; pip2 show autobahntestsuite\"),\n\tcase string:str(Out, \"autobahntestsuite\") of\n\t\t0 ->\n\t\t\tct:print(\"Skipping the autobahn group because the \"\n\t\t\t\t\"Autobahn Test Suite is not installed.~nTo install it, \"\n\t\t\t\t\"please follow the instructions on this page:~n~n    \"\n\t\t\t\t\"http://autobahn.ws/testsuite/installation.html\"),\n\t\t\t{skip, \"Autobahn Test Suite not installed.\"};\n\t\t_ ->\n\t\t\t{ok, _} = cowboy:start_clear(Name, [{port, 33080}], #{\n\t\t\t\tenv => #{dispatch => init_dispatch()}\n\t\t\t}),\n\t\t\tConfig\n\tend.\n\nend_per_group(Listener, _Config) ->\n\tcowboy:stop_listener(Listener).\n\n%% Dispatch configuration.\n\ninit_dispatch() ->\n\tcowboy_router:compile([\n\t\t{\"host.docker.internal\", [\n\t\t\t{\"/ws_echo\", ws_echo, []}\n\t\t]}\n\t]).\n\n%% Tests.\n\nautobahn_fuzzingclient(Config) ->\n\tdoc(\"Autobahn test suite for the Websocket protocol.\"),\n\tSelf = self(),\n\tspawn_link(fun() -> do_start_port(Config, Self) end),\n\treceive autobahn_exit -> ok end,\n\tct:log(\"<h2><a href=\\\"log_private/reports/servers/index.html\\\">Full report</a></h2>~n\"),\n\tReport = config(priv_dir, Config) ++ \"reports/servers/index.html\",\n\tct:print(\"Autobahn Test Suite report: file://~s~n\", [Report]),\n\t{ok, HTML} = file:read_file(Report),\n\tcase length(binary:matches(HTML, <<\"case_failed\">>)) > 2 of\n\t\ttrue -> error(failed);\n\t\tfalse -> ok\n\tend.\n\ndo_start_port(Config, Pid) ->\n%\tCmd = \"wstest -m fuzzingclient -s \" ++ config(data_dir, Config) ++ \"client.json\",\n\tCmd = \"sudo docker run --rm \"\n\t\t\"-v \" ++ config(data_dir, Config) ++ \"/client.json:/client.json \"\n\t\t\"-v \" ++ config(priv_dir, Config) ++ \"/reports:/reports \"\n\t\t\"--add-host=host.docker.internal:host-gateway \"\n\t\t\"--name fuzzingclient \"\n\t\t\"crossbario/autobahn-testsuite \"\n\t\t\"wstest -m fuzzingclient -s client.json\",\n\tPort = open_port({spawn, Cmd},\n\t\t[{line, 10000}, {cd, config(priv_dir, Config)}, binary, eof]),\n\tdo_receive_infinity(Port, Pid).\n\ndo_receive_infinity(Port, Pid) ->\n\treceive\n\t\t{Port, {data, {eol, Line}}} ->\n\t\t\tio:format(user, \"~s~n\", [Line]),\n\t\t\tdo_receive_infinity(Port, Pid);\n\t\t{Port, eof} ->\n\t\t\tPid ! autobahn_exit\n\tend.\n"
  },
  {
    "path": "test/ws_autobahn_SUITE_data/client.json",
    "content": "{\n\t\"options\": {\"failByDrop\": false},\n\t\"enable-ssl\": false,\n\n\t\"servers\": [{\n\t\t\"agent\": \"Cowboy\",\n\t\t\"url\": \"ws://host.docker.internal:33080/ws_echo\",\n\t\t\"options\": {\"version\": 18}\n\t}],\n\n\t\"cases\": [\"*\"],\n\t\"exclude-cases\": [],\n\t\"exclude-agent-cases\": {}\n} \n"
  },
  {
    "path": "test/ws_handler_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(ws_handler_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/1]).\n-import(cowboy_test, [gun_open/2]).\n-import(cowboy_test, [gun_down/1]).\n\n%% ct.\n\nall() ->\n\t[\n\t\t{group, h1},\n\t\t{group, h1_hibernate},\n\t\t{group, h2},\n\t\t{group, h2_relay}\n\t].\n\n%% @todo Test against HTTP/2 too.\ngroups() ->\n\tAllTests = ct_helper:all(?MODULE),\n\t[\n\t\t{h1, [parallel], AllTests},\n\t\t{h1_hibernate, [parallel], AllTests},\n\t\t%% The websocket_deflate_false test isn't compatible with HTTP/2.\n\t\t{h2, [parallel], AllTests -- [websocket_deflate_false]},\n\t\t{h2_relay, [parallel], AllTests -- [websocket_deflate_false]}\n\t].\n\ninit_per_group(Name, Config)\n\t\twhen Name =:= h1; Name =:= h1_hibernate ->\n\tcowboy_test:init_http(Name, #{\n\t\tenv => #{dispatch => init_dispatch(Name)}\n\t}, Config);\ninit_per_group(Name, Config)\n\t\twhen Name =:= h2; Name =:= h2_relay ->\n\tcowboy_test:init_http2(Name, #{\n\t\tenable_connect_protocol => true,\n\t\tenv => #{dispatch => init_dispatch(Name)}\n\t}, Config).\n\nend_per_group(Name, _) ->\n\tcowboy:stop_listener(Name).\n\n%% Dispatch configuration.\n\ninit_dispatch(Name) ->\n\tInitialState = case Name of\n\t\th1_hibernate -> #{run_or_hibernate => hibernate};\n\t\th2_relay -> #{run_or_hibernate => run, data_delivery => relay};\n\t\t_ -> #{run_or_hibernate => run}\n\tend,\n\tcowboy_router:compile([{'_', [\n\t\t{\"/init\", ws_init_commands_h, InitialState},\n\t\t{\"/handle\", ws_handle_commands_h, InitialState},\n\t\t{\"/info\", ws_info_commands_h, InitialState},\n\t\t{\"/trap_exit\", ws_init_h, InitialState},\n\t\t{\"/active\", ws_active_commands_h, InitialState},\n\t\t{\"/deflate\", ws_deflate_commands_h, InitialState},\n\t\t{\"/set_options\", ws_set_options_commands_h, InitialState},\n\t\t{\"/shutdown_reason\", ws_shutdown_reason_commands_h, InitialState},\n\t\t{\"/terminate\", ws_terminate_h, InitialState}\n\t]}]).\n\n%% Support functions for testing using Gun.\n\ngun_open_ws(Config, Path, Commands) ->\n\tConnPid = gun_open(Config, #{http2_opts => #{notify_settings_changed => true}}),\n\tdo_await_enable_connect_protocol(config(protocol, Config), ConnPid),\n\tStreamRef = gun:ws_upgrade(ConnPid, Path, [\n\t\t{<<\"x-commands\">>, base64:encode(term_to_binary(Commands))}\n\t]),\n\treceive\n\t\t{gun_upgrade, ConnPid, StreamRef, [<<\"websocket\">>], _} ->\n\t\t\t{ok, ConnPid, StreamRef};\n\t\t{gun_response, ConnPid, _, _, Status, Headers} ->\n\t\t\texit({ws_upgrade_failed, Status, Headers});\n\t\t{gun_error, ConnPid, StreamRef, Reason} ->\n\t\t\texit({ws_upgrade_failed, Reason})\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\ndo_await_enable_connect_protocol(http, _) ->\n\tok;\ndo_await_enable_connect_protocol(http2, ConnPid) ->\n\t{notify, settings_changed, #{enable_connect_protocol := true}}\n\t\t= gun:await(ConnPid, undefined), %% @todo Maybe have a gun:await/1?\n\tok.\n\nreceive_ws(ConnPid, StreamRef) ->\n\treceive\n\t\t{gun_ws, ConnPid, StreamRef, Frame} ->\n\t\t\t{ok, Frame}\n\tafter 1000 ->\n\t\t{error, timeout}\n\tend.\n\nensure_handle_is_called(ConnPid, StreamRef, \"/handle\") ->\n\tgun:ws_send(ConnPid, StreamRef, {text, <<\"Necessary to trigger websocket_handle/2.\">>});\nensure_handle_is_called(_, _, _) ->\n\tok.\n\ndo_receive(Tag) ->\n\treceive\n\t\tMsg when element(1, Msg) =:= Tag ->\n\t\t\tMsg\n\tafter 1000 ->\n\t\tct:pal(\"do_receive(~p): ~p\", [Tag, process_info(self(), messages)]),\n\t\terror(timeout)\n\tend.\n\n%% Tests.\n\nwebsocket_init_nothing(Config) ->\n\tdoc(\"Nothing happens when websocket_init/1 returns no commands.\"),\n\tdo_nothing(Config, \"/init\").\n\nwebsocket_handle_nothing(Config) ->\n\tdoc(\"Nothing happens when websocket_handle/2 returns no commands.\"),\n\tdo_nothing(Config, \"/handle\").\n\nwebsocket_info_nothing(Config) ->\n\tdoc(\"Nothing happens when websocket_info/2 returns no commands.\"),\n\tdo_nothing(Config, \"/info\").\n\ndo_nothing(Config, Path) ->\n\t{ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, []),\n\tensure_handle_is_called(ConnPid, StreamRef, Path),\n\t{error, timeout} = receive_ws(ConnPid, StreamRef),\n\tgun:close(ConnPid).\n\nwebsocket_init_invalid(Config) ->\n\tdoc(\"The connection must be closed when websocket_init/1 returns an invalid command.\"),\n\tdo_invalid(Config, \"/init\").\n\nwebsocket_handle_invalid(Config) ->\n\tdoc(\"The connection must be closed when websocket_handle/2 returns an invalid command.\"),\n\tdo_invalid(Config, \"/init\").\n\nwebsocket_info_invalid(Config) ->\n\tdoc(\"The connection must be closed when websocket_info/2 returns an invalid command.\"),\n\tdo_invalid(Config, \"/info\").\n\ndo_invalid(Config, Path) ->\n\t{ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, bad),\n\tensure_handle_is_called(ConnPid, StreamRef, Path),\n\tcase config(protocol, Config) of\n\t\t%% HTTP/1.1 closes the connection.\n\t\thttp -> gun_down(ConnPid);\n\t\t%% HTTP/2 terminates the stream.\n\t\thttp2 ->\n\t\t\treceive {gun_error, ConnPid, StreamRef, {stream_error, internal_error, _}} -> ok\n\t\t\tafter 500 -> error(timeout) end\n\tend.\n\nwebsocket_init_one_frame(Config) ->\n\tdoc(\"A single frame is received when websocket_init/1 returns it as a command.\"),\n\tdo_one_frame(Config, \"/init\").\n\nwebsocket_handle_one_frame(Config) ->\n\tdoc(\"A single frame is received when websocket_handle/2 returns it as a command.\"),\n\tdo_one_frame(Config, \"/handle\").\n\nwebsocket_info_one_frame(Config) ->\n\tdoc(\"A single frame is received when websocket_info/2 returns it as a command.\"),\n\tdo_one_frame(Config, \"/info\").\n\ndo_one_frame(Config, Path) ->\n\t{ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, [\n\t\t{text, <<\"One frame!\">>}\n\t]),\n\tensure_handle_is_called(ConnPid, StreamRef, Path),\n\t{ok, {text, <<\"One frame!\">>}} = receive_ws(ConnPid, StreamRef),\n\tgun:close(ConnPid).\n\nwebsocket_init_many_frames(Config) ->\n\tdoc(\"Multiple frames are received when websocket_init/1 returns them as commands.\"),\n\tdo_many_frames(Config, \"/init\").\n\nwebsocket_handle_many_frames(Config) ->\n\tdoc(\"Multiple frames are received when websocket_handle/2 returns them as commands.\"),\n\tdo_many_frames(Config, \"/handle\").\n\nwebsocket_info_many_frames(Config) ->\n\tdoc(\"Multiple frames are received when websocket_info/2 returns them as commands.\"),\n\tdo_many_frames(Config, \"/info\").\n\ndo_many_frames(Config, Path) ->\n\t{ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, [\n\t\t{text, <<\"One frame!\">>},\n\t\t{binary, <<\"Two frames!\">>}\n\t]),\n\tensure_handle_is_called(ConnPid, StreamRef, Path),\n\t{ok, {text, <<\"One frame!\">>}} = receive_ws(ConnPid, StreamRef),\n\t{ok, {binary, <<\"Two frames!\">>}} = receive_ws(ConnPid, StreamRef),\n\tgun:close(ConnPid).\n\nwebsocket_init_close_frame(Config) ->\n\tdoc(\"A single close frame is received when websocket_init/1 returns it as a command.\"),\n\tdo_close_frame(Config, \"/init\").\n\nwebsocket_handle_close_frame(Config) ->\n\tdoc(\"A single close frame is received when websocket_handle/2 returns it as a command.\"),\n\tdo_close_frame(Config, \"/handle\").\n\nwebsocket_info_close_frame(Config) ->\n\tdoc(\"A single close frame is received when websocket_info/2 returns it as a command.\"),\n\tdo_close_frame(Config, \"/info\").\n\ndo_close_frame(Config, Path) ->\n\t{ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, [close]),\n\tensure_handle_is_called(ConnPid, StreamRef, Path),\n\t{ok, close} = receive_ws(ConnPid, StreamRef),\n\tgun_down(ConnPid).\n\nwebsocket_init_many_frames_then_close_frame(Config) ->\n\tdoc(\"Multiple frames are received followed by a close frame \"\n\t\t\"when websocket_init/1 returns them as commands.\"),\n\tdo_many_frames_then_close_frame(Config, \"/init\").\n\nwebsocket_handle_many_frames_then_close_frame(Config) ->\n\tdoc(\"Multiple frames are received followed by a close frame \"\n\t\t\"when websocket_handle/2 returns them as commands.\"),\n\tdo_many_frames_then_close_frame(Config, \"/handle\").\n\nwebsocket_info_many_frames_then_close_frame(Config) ->\n\tdoc(\"Multiple frames are received followed by a close frame \"\n\t\t\"when websocket_info/2 returns them as commands.\"),\n\tdo_many_frames_then_close_frame(Config, \"/info\").\n\ndo_many_frames_then_close_frame(Config, Path) ->\n\t{ok, ConnPid, StreamRef} = gun_open_ws(Config, Path, [\n\t\t{text, <<\"One frame!\">>},\n\t\t{binary, <<\"Two frames!\">>},\n\t\tclose\n\t]),\n\tensure_handle_is_called(ConnPid, StreamRef, Path),\n\t{ok, {text, <<\"One frame!\">>}} = receive_ws(ConnPid, StreamRef),\n\t{ok, {binary, <<\"Two frames!\">>}} = receive_ws(ConnPid, StreamRef),\n\t{ok, close} = receive_ws(ConnPid, StreamRef),\n\tgun_down(ConnPid).\n\nwebsocket_init_trap_exit_false(Config) ->\n\tdoc(\"The trap_exit process flag must be set back to false before \"\n\t\t\"the connection is taken over by Websocket.\"),\n\t{ok, ConnPid, StreamRef} = gun_open_ws(Config, \"/trap_exit?reply_trap_exit\", []),\n\t{ok, {text, <<\"trap_exit: false\">>}} = receive_ws(ConnPid, StreamRef),\n\tok.\n\nwebsocket_active_false(Config) ->\n\tdoc(\"The {active, false} command stops receiving data from the socket. \"\n\t\t\"The {active, true} command reenables it.\"),\n\t{ok, ConnPid, StreamRef} = gun_open_ws(Config, \"/active\", []),\n\t%% We must exhaust the HTTP/2 flow control window\n\t%% otherwise the frame will be received even if active mode is disabled.\n\tgun:ws_send(ConnPid, StreamRef, {binary, <<0:100000/unit:8>>}),\n\tgun:ws_send(ConnPid, StreamRef, {text, <<\"Not received until the handler enables active again.\">>}),\n\t{error, timeout} = receive_ws(ConnPid, StreamRef),\n\t{ok, {binary, _}} = receive_ws(ConnPid, StreamRef),\n\t{ok, {text, <<\"Not received until the handler enables active again.\">>}}\n\t\t= receive_ws(ConnPid, StreamRef),\n\tgun:close(ConnPid).\n\nwebsocket_deflate_false(Config) ->\n\tdoc(\"The {deflate, false} command temporarily disables compression. \"\n\t\t\"The {deflate, true} command reenables it.\"),\n\t%% We disable context takeover so that the compressed data\n\t%% does not change across all frames.\n\t{ok, Socket, Headers} = ws_SUITE:do_handshake(\"/deflate\",\n\t\t\"Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover\\r\\n\", Config),\n\t{_, \"permessage-deflate; server_no_context_takeover\"}\n\t\t= lists:keyfind(\"sec-websocket-extensions\", 1, Headers),\n\t%% The handler receives a compressed \"Hello\" frame and\n\t%% sends back a compressed or uncompressed echo intermittently.\n\tMask = 16#11223344,\n\tCompressedHello = <<242, 72, 205, 201, 201, 7, 0>>,\n\tMaskedHello = ws_SUITE:do_mask(CompressedHello, Mask, <<>>),\n\t%% First echo is compressed.\n\tok = gen_tcp:send(Socket, <<1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32, MaskedHello/binary>>),\n\t{ok, <<1:1, 1:1, 0:2, 1:4, 0:1, 7:7, CompressedHello/binary>>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Second echo is not compressed when it is received back.\n\tok = gen_tcp:send(Socket, <<1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32, MaskedHello/binary>>),\n\t{ok, <<1:1, 0:3, 1:4, 0:1, 5:7, \"Hello\">>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Third echo is compressed again.\n\tok = gen_tcp:send(Socket, <<1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32, MaskedHello/binary>>),\n\t{ok, <<1:1, 1:1, 0:2, 1:4, 0:1, 7:7, CompressedHello/binary>>} = gen_tcp:recv(Socket, 0, 6000),\n\t%% Client-initiated close.\n\tok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>),\n\t{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),\n\t{error, closed} = gen_tcp:recv(Socket, 0, 6000),\n\tok.\n\nwebsocket_deflate_ignore_if_not_negotiated(Config) ->\n\tdoc(\"The {deflate, boolean()} commands are ignored \"\n\t\t\"when compression was not negotiated.\"),\n\t{ok, ConnPid, StreamRef} = gun_open_ws(Config, \"/deflate\", []),\n\t_ = [begin\n\t\tgun:ws_send(ConnPid, StreamRef, {text, <<\"Hello.\">>}),\n\t\t{ok, {text, <<\"Hello.\">>}} = receive_ws(ConnPid, StreamRef)\n\tend || _ <- lists:seq(1, 10)],\n\tgun:close(ConnPid).\n\nwebsocket_set_options_idle_timeout(Config) ->\n\tdoc(\"The idle_timeout option can be modified using the \"\n\t\t\"command {set_options, Opts} at runtime.\"),\n\tConnPid = gun_open(Config, #{http2_opts => #{notify_settings_changed => true}}),\n\tdo_await_enable_connect_protocol(config(protocol, Config), ConnPid),\n\tStreamRef = gun:ws_upgrade(ConnPid, \"/set_options\"),\n\treceive\n\t\t{gun_upgrade, ConnPid, StreamRef, [<<\"websocket\">>], _} ->\n\t\t\tok;\n\t\t{gun_response, ConnPid, _, _, Status, Headers} ->\n\t\t\texit({ws_upgrade_failed, Status, Headers});\n\t\t{gun_error, ConnPid, StreamRef, Reason} ->\n\t\t\texit({ws_upgrade_failed, Reason})\n\tafter 1000 ->\n\t\terror(timeout)\n\tend,\n\t%% We don't send anything for a short while and confirm\n\t%% that idle_timeout does not trigger.\n\t{error, timeout} = gun:await(ConnPid, StreamRef, 2000),\n\t%% Trigger the change in idle_timeout and confirm that\n\t%% the connection gets closed soon after.\n\tgun:ws_send(ConnPid, StreamRef, {text, <<\"idle_timeout_short\">>}),\n\treceive\n\t\t{gun_down, ConnPid, _, _, _} ->\n\t\t\tok\n\tafter 2000 ->\n\t\terror(timeout)\n\tend.\n\nwebsocket_set_options_max_frame_size(Config) ->\n\tdoc(\"The max_frame_size option can be modified using the \"\n\t\t\"command {set_options, Opts} at runtime.\"),\n\tConnPid = gun_open(Config, #{http2_opts => #{notify_settings_changed => true}}),\n\tdo_await_enable_connect_protocol(config(protocol, Config), ConnPid),\n\tStreamRef = gun:ws_upgrade(ConnPid, \"/set_options\"),\n\treceive\n\t\t{gun_upgrade, ConnPid, StreamRef, [<<\"websocket\">>], _} ->\n\t\t\tok;\n\t\t{gun_response, ConnPid, _, _, Status, Headers} ->\n\t\t\texit({ws_upgrade_failed, Status, Headers});\n\t\t{gun_error, ConnPid, StreamRef, Reason} ->\n\t\t\texit({ws_upgrade_failed, Reason})\n\tafter 1000 ->\n\t\terror(timeout)\n\tend,\n\t%% We first send a 1MB frame to confirm that yes, we can\n\t%% send a frame that large. The default max_frame_size is infinity.\n\tgun:ws_send(ConnPid, StreamRef, {binary, <<0:8000000>>}),\n\t{ws, {binary, <<0:8000000>>}} = gun:await(ConnPid, StreamRef),\n\t%% Trigger the change in max_frame_size. From now on we will\n\t%% only allow frames of up to 1000 bytes.\n\tgun:ws_send(ConnPid, StreamRef, {text, <<\"max_frame_size_small\">>}),\n\t%% Confirm that we can send frames of up to 1000 bytes.\n\tgun:ws_send(ConnPid, StreamRef, {binary, <<0:8000>>}),\n\t{ws, {binary, <<0:8000>>}} = gun:await(ConnPid, StreamRef),\n\t%% Confirm that sending frames larger than 1000 bytes\n\t%% results in the closing of the connection.\n\tgun:ws_send(ConnPid, StreamRef, {binary, <<0:8008>>}),\n\treceive\n\t\t{gun_down, ConnPid, _, _, _} ->\n\t\t\tok\n\tafter 2000 ->\n\t\terror(timeout)\n\tend.\n\nwebsocket_shutdown_reason(Config) ->\n\tdoc(\"The command {shutdown_reason, any()} can be used to \"\n\t\t\"change the shutdown reason of a Websocket connection.\"),\n\tConnPid = gun_open(Config, #{http2_opts => #{notify_settings_changed => true}}),\n\tdo_await_enable_connect_protocol(config(protocol, Config), ConnPid),\n\tStreamRef = gun:ws_upgrade(ConnPid, \"/shutdown_reason\", [\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\t{upgrade, [<<\"websocket\">>], _} = gun:await(ConnPid, StreamRef),\n\tWsPid = receive {ws_pid, P} -> P after 1000 -> error(timeout) end,\n\tMRef = monitor(process, WsPid),\n\tWsPid ! {self(), {?MODULE, ?FUNCTION_NAME}},\n\treceive\n\t\t{'DOWN', MRef, process, WsPid, {shutdown, {?MODULE, ?FUNCTION_NAME}}} ->\n\t\t\tok\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\nwebsocket_terminate_close_normal(Config) ->\n\tdoc(\"Receiving a close frame results in a terminate/3 call. \"\n\t\t\"The Req object is kept in a more compact form by default.\"),\n\tConnPid = gun_open(Config, #{http2_opts => #{notify_settings_changed => true}}),\n\tdo_await_enable_connect_protocol(config(protocol, Config), ConnPid),\n\tStreamRef = gun:ws_upgrade(ConnPid, \"/terminate\", [\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\t{upgrade, [<<\"websocket\">>], _} = gun:await(ConnPid, StreamRef),\n\t{ws_pid, WsPid} = do_receive(ws_pid),\n\tMRef = monitor(process, WsPid),\n\tgun:ws_send(ConnPid, StreamRef, close),\n\t{terminate, remote, Req} = do_receive(terminate),\n\t{'DOWN', MRef, process, WsPid, normal} = do_receive('DOWN'),\n\t%% Confirm terminate/3 was called with a compacted Req.\n\ttrue = maps:is_key(path, Req),\n\tfalse = maps:is_key(headers, Req),\n\tok.\n\nwebsocket_terminate_close_reason(Config) ->\n\tdoc(\"Receiving a close frame results in a terminate/3 call. \"\n\t\t\"The Req object is kept in a more compact form by default.\"),\n\tConnPid = gun_open(Config, #{http2_opts => #{notify_settings_changed => true}}),\n\tdo_await_enable_connect_protocol(config(protocol, Config), ConnPid),\n\tStreamRef = gun:ws_upgrade(ConnPid, \"/terminate\", [\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\t{upgrade, [<<\"websocket\">>], _} = gun:await(ConnPid, StreamRef),\n\t{ws_pid, WsPid} = do_receive(ws_pid),\n\tMRef = monitor(process, WsPid),\n\tgun:ws_send(ConnPid, StreamRef, {close, 4000, <<\"test-close\">>}),\n\t{terminate, {remote, 4000, <<\"test-close\">>}, Req} = do_receive(terminate),\n\t{'DOWN', MRef, process, WsPid, normal} = do_receive('DOWN'),\n\t%% Confirm terminate/3 was called with a compacted Req.\n\ttrue = maps:is_key(path, Req),\n\tfalse = maps:is_key(headers, Req),\n\tok.\n\nwebsocket_terminate_socket_close(Config) ->\n\tdoc(\"The socket getting closed results in a terminate/3 call. \"\n\t\t\"The Req object is kept in a more compact form by default.\"),\n\tProtocol = config(protocol, Config),\n\tConnPid = gun_open(Config, #{http2_opts => #{notify_settings_changed => true}}),\n\tdo_await_enable_connect_protocol(Protocol, ConnPid),\n\tStreamRef = gun:ws_upgrade(ConnPid, \"/terminate\", [\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\t{upgrade, [<<\"websocket\">>], _} = gun:await(ConnPid, StreamRef),\n\t{ws_pid, WsPid} = do_receive(ws_pid),\n\tMRef = monitor(process, WsPid),\n\tgun:close(ConnPid),\n\t%% Terminate reasons differ depending on the protocol.\n\t{terminate, Reason, Req} = do_receive(terminate),\n\tcase Reason of\n\t\t{error, closed} when Protocol =:= http -> ok;\n\t\tshutdown when Protocol =:= http2 -> ok\n\tend,\n\t{'DOWN', MRef, process, WsPid, normal} = do_receive('DOWN'),\n\t%% Confirm terminate/3 was called with a compacted Req.\n\ttrue = maps:is_key(path, Req),\n\tfalse = maps:is_key(headers, Req),\n\tok.\n\nwebsocket_terminate_req_filter(Config) ->\n\tdoc(\"Receiving a close frame results in a terminate/3 call. \"\n\t\t\"A function can be given to filter the Req object.\"),\n\tConnPid = gun_open(Config, #{http2_opts => #{notify_settings_changed => true}}),\n\tdo_await_enable_connect_protocol(config(protocol, Config), ConnPid),\n\tStreamRef = gun:ws_upgrade(ConnPid, \"/terminate?req_filter\", [\n\t\t{<<\"x-test-pid\">>, pid_to_list(self())}\n\t]),\n\t{upgrade, [<<\"websocket\">>], _} = gun:await(ConnPid, StreamRef),\n\t{ws_pid, WsPid} = do_receive(ws_pid),\n\tMRef = monitor(process, WsPid),\n\tgun:ws_send(ConnPid, StreamRef, close),\n\t{terminate, remote, Req} = do_receive(terminate),\n\t{'DOWN', MRef, process, WsPid, normal} = do_receive('DOWN'),\n\t%% Confirm terminate/3 was called with a filtered Req.\n\tfiltered = Req,\n\tok.\n"
  },
  {
    "path": "test/ws_perf_SUITE.erl",
    "content": "%% Copyright (c) Loïc Hoguin <essen@ninenines.eu>\n%%\n%% Permission to use, copy, modify, and/or distribute this software for any\n%% purpose with or without fee is hereby granted, provided that the above\n%% copyright notice and this permission notice appear in all copies.\n%%\n%% THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES\n%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF\n%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR\n%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES\n%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN\n%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF\n%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\n\n-module(ws_perf_SUITE).\n-compile(export_all).\n-compile(nowarn_export_all).\n\n-import(ct_helper, [config/2]).\n-import(ct_helper, [doc/1]).\n-import(cowboy_test, [gun_open/2]).\n-import(cowboy_test, [gun_down/1]).\n\n%% ct.\n\nall() ->\n\t[{group, binary}, {group, ascii}, {group, mixed}, {group, japanese}].\n\ngroups() ->\n\tCommonGroups = cowboy_test:common_groups(ct_helper:all(?MODULE), no_parallel),\n\tSubGroups = [G || G = {GN, _, _} <- CommonGroups,\n\t\tGN =:= http orelse GN =:= h2c orelse GN =:= http_compress orelse GN =:= h2c_compress],\n\t[\n\t\t{binary, [], SubGroups},\n\t\t{ascii, [], SubGroups},\n\t\t{mixed, [], SubGroups},\n\t\t{japanese, [], SubGroups}\n\t].\n\ninit_per_suite(Config) ->\n\t%% Optionally enable `perf` for the current node.\n%\tspawn(fun() -> ct:pal(os:cmd(\"perf record -g -F 9999 -o /tmp/ws_perf.data -p \" ++ os:getpid() ++ \" -- sleep 60\")) end),\n\tConfig.\n\nend_per_suite(_Config) ->\n\tok.\n\ninit_per_group(Name, Config) when Name =:= http; Name =:= http_compress ->\n\tinit_info(Name, Config),\n\tcowboy_test:init_common_groups(Name, Config, ?MODULE);\ninit_per_group(Name, Config) when Name =:= h2c; Name =:= h2c_compress ->\n\tinit_info(Name, Config),\n\t{Flavor, Opts} = case Name of\n\t\th2c -> {vanilla, #{}};\n\t\th2c_compress -> {compress, #{stream_handlers => [cowboy_compress_h, cowboy_stream_h]}}\n\tend,\n\tConfig1 = cowboy_test:init_http(Name, Opts#{\n\t\t%% The margin sizes must be larger than the larger test message for plain sockets.\n\t\tconnection_window_margin_size => 128*1024,\n\t\tenable_connect_protocol => true,\n\t\tenv => #{dispatch => init_dispatch(Config)},\n\t\tmax_frame_size_sent => 64*1024,\n\t\tmax_frame_size_received => 16384 * 1024 - 1,\n\t\tmax_received_frame_rate => {10_000_000, 1},\n\t\tstream_window_data_threshold => 1024,\n\t\tstream_window_margin_size => 128*1024\n\t}, [{flavor, Flavor}|Config]),\n\tlists:keyreplace(protocol, 1, Config1, {protocol, http2});\ninit_per_group(ascii, Config) ->\n\tinit_text_data(\"ascii.txt\", Config);\ninit_per_group(mixed, Config) ->\n\tinit_text_data(\"grok_segond.txt\", Config);\ninit_per_group(japanese, Config) ->\n\tinit_text_data(\"japanese.txt\", Config);\ninit_per_group(binary, Config) ->\n\t[{frame_type, binary}|Config].\n\ninit_info(Name, Config) ->\n\tDataInfo = case config(frame_type, Config) of\n\t\ttext -> config(text_data_filename, Config);\n\t\tbinary -> binary\n\tend,\n\tConnInfo = case Name of\n\t\thttp -> \"cleartext HTTP/1.1\";\n\t\thttp_compress -> \"cleartext HTTP/1.1 with compression\";\n\t\th2c -> \"cleartext HTTP/2\";\n\t\th2c_compress -> \"cleartext HTTP/2 with compression\"\n\tend,\n\tct:pal(\"Websocket over ~s (~s)\", [ConnInfo, DataInfo]).\n\ninit_text_data(Filename, Config) ->\n\t{ok, Text} = file:read_file(filename:join(config(data_dir, Config), Filename)),\n\t[\n\t\t{frame_type, text},\n\t\t{text_data, Text},\n\t\t{text_data_filename, Filename}\n\t|Config].\n\nend_per_group(Name, _Config) ->\n\tcowboy_test:stop_group(Name).\n\n%% Dispatch configuration.\n\ninit_dispatch(_Config) ->\n\tcowboy_router:compile([\n\t\t{\"localhost\", [\n\t\t\t{\"/ws_echo\", ws_echo, []},\n\t\t\t{\"/ws_ignore\", ws_ignore, []}\n\t\t]}\n\t]).\n\n%% Support functions for testing using Gun.\n\ndo_gun_open_ws(Path, Config) ->\n\tConnPid = gun_open(Config, #{\n\t\thttp2_opts => #{\n\t\t\tconnection_window_margin_size => 64*1024,\n\t\t\tmax_frame_size_sent => 64*1024,\n\t\t\tmax_frame_size_received => 16384 * 1024 - 1,\n\t\t\tnotify_settings_changed => true,\n\t\t\tstream_window_data_threshold => 1024,\n\t\t\tstream_window_margin_size => 64*1024\n\t\t},\n\t\ttcp_opts => [{nodelay, true}],\n\t\tws_opts => #{compress => config(flavor, Config) =:= compress}\n\t}),\n\tcase config(protocol, Config) of\n\t\thttp -> ok;\n\t\thttp2 ->\n\t\t\t{notify, settings_changed, #{enable_connect_protocol := true}}\n\t\t\t\t= gun:await(ConnPid, undefined) %% @todo Maybe have a gun:await/1?\n\tend,\n\tStreamRef = gun:ws_upgrade(ConnPid, Path),\n\treceive\n\t\t{gun_upgrade, ConnPid, StreamRef, [<<\"websocket\">>], _} ->\n\t\t\t{ok, ConnPid, StreamRef};\n\t\t{gun_response, ConnPid, _, _, Status, Headers} ->\n\t\t\texit({ws_upgrade_failed, Status, Headers});\n\t\t{gun_error, ConnPid, StreamRef, Reason} ->\n\t\t\texit({ws_upgrade_failed, Reason})\n\tafter 1000 ->\n\t\terror(timeout)\n\tend.\n\nreceive_ws(ConnPid, StreamRef) ->\n\treceive\n\t\t{gun_ws, ConnPid, StreamRef, Frame} ->\n\t\t\t{ok, Frame}\n\tafter 30000 ->\n\t\t{error, timeout}\n\tend.\n\n%% Tests.\n\necho_1_00064KiB(Config) ->\n\tdoc(\"Send and receive a 64KiB frame.\"),\n\tdo_echo(Config, echo_1, 1, 64 * 1024).\n\necho_1_00256KiB(Config) ->\n\tdoc(\"Send and receive a 256KiB frame.\"),\n\tdo_echo(Config, echo_1, 1, 256 * 1024).\n\necho_1_01024KiB(Config) ->\n\tdoc(\"Send and receive a 1024KiB frame.\"),\n\tdo_echo(Config, echo_1, 1, 1024 * 1024).\n\necho_1_04096KiB(Config) ->\n\tdoc(\"Send and receive a 4096KiB frame.\"),\n\tdo_echo(Config, echo_1, 1, 4096 * 1024).\n\n%% Minus one because frames can only get so big.\necho_1_16384KiB(Config) ->\n\tdoc(\"Send and receive a 16384KiB - 1 frame.\"),\n\tdo_echo(Config, echo_1, 1, 16384 * 1024 - 1).\n\necho_N_00000B(Config) ->\n\tdoc(\"Send and receive a 0B frame 1000 times.\"),\n\tdo_echo(Config, echo_N, 1000, 0).\n\necho_N_00256B(Config) ->\n\tdoc(\"Send and receive a 256B frame 1000 times.\"),\n\tdo_echo(Config, echo_N, 1000, 256).\n\necho_N_01024B(Config) ->\n\tdoc(\"Send and receive a 1024B frame 1000 times.\"),\n\tdo_echo(Config, echo_N, 1000, 1024).\n\necho_N_04096B(Config) ->\n\tdoc(\"Send and receive a 4096B frame 1000 times.\"),\n\tdo_echo(Config, echo_N, 1000, 4096).\n\necho_N_16384B(Config) ->\n\tdoc(\"Send and receive a 16384B frame 1000 times.\"),\n\tdo_echo(Config, echo_N, 1000, 16384).\n\n%echo_N_16384B_10K(Config) ->\n%\tdoc(\"Send and receive a 16384B frame 10000 times.\"),\n%\tdo_echo(Config, echo_N, 10000, 16384).\n\ndo_echo(Config, What, Num, FrameSize) ->\n\t{ok, ConnPid, StreamRef} = do_gun_open_ws(\"/ws_echo\", Config),\n\tFrameType = config(frame_type, Config),\n\tFrameData = case FrameType of\n\t\ttext -> do_text_data(Config, FrameSize);\n\t\tbinary -> rand:bytes(FrameSize)\n\tend,\n\t%% Heat up the processes before doing the real run.\n%\tdo_echo_loop(ConnPid, StreamRef, Num, FrameType, FrameData),\n\t{Time, _} = timer:tc(?MODULE, do_echo_loop, [ConnPid, StreamRef, Num, FrameType, FrameData]),\n\tdo_log(\"~-6s ~-6s ~6s: ~8bµs\", [What, FrameType, do_format_size(FrameSize), Time]),\n\tgun:ws_send(ConnPid, StreamRef, close),\n\t{ok, close} = receive_ws(ConnPid, StreamRef),\n\tgun_down(ConnPid).\n\ndo_echo_loop(_, _, 0, _, _) ->\n\tok;\ndo_echo_loop(ConnPid, StreamRef, Num, FrameType, FrameData) ->\n\tgun:ws_send(ConnPid, StreamRef, {FrameType, FrameData}),\n\t{ok, {FrameType, FrameData}} = receive_ws(ConnPid, StreamRef),\n\tdo_echo_loop(ConnPid, StreamRef, Num - 1, FrameType, FrameData).\n\nsend_1_00064KiB(Config) ->\n\tdoc(\"Send a 64KiB frame.\"),\n\tdo_send(Config, send_1, 1, 64 * 1024).\n\nsend_1_00256KiB(Config) ->\n\tdoc(\"Send a 256KiB frame.\"),\n\tdo_send(Config, send_1, 1, 256 * 1024).\n\nsend_1_01024KiB(Config) ->\n\tdoc(\"Send a 1024KiB frame.\"),\n\tdo_send(Config, send_1, 1, 1024 * 1024).\n\nsend_1_04096KiB(Config) ->\n\tdoc(\"Send a 4096KiB frame.\"),\n\tdo_send(Config, send_1, 1, 4096 * 1024).\n\n%% Minus one because frames can only get so big.\nsend_1_16384KiB(Config) ->\n\tdoc(\"Send a 16384KiB - 1 frame.\"),\n\tdo_send(Config, send_1, 1, 16384 * 1024 - 1).\n\nsend_N_00000B(Config) ->\n\tdoc(\"Send a 0B frame 10000 times.\"),\n\tdo_send(Config, send_N, 10000, 0).\n\nsend_N_00256B(Config) ->\n\tdoc(\"Send a 256B frame 10000 times.\"),\n\tdo_send(Config, send_N, 10000, 256).\n\nsend_N_01024B(Config) ->\n\tdoc(\"Send a 1024B frame 10000 times.\"),\n\tdo_send(Config, send_N, 10000, 1024).\n\nsend_N_04096B(Config) ->\n\tdoc(\"Send a 4096B frame 10000 times.\"),\n\tdo_send(Config, send_N, 10000, 4096).\n\nsend_N_16384B(Config) ->\n\tdoc(\"Send a 16384B frame 10000 times.\"),\n\tdo_send(Config, send_N, 10000, 16384).\n\n%send_N_16384B_10K(Config) ->\n%\tdoc(\"Send and receive a 16384B frame 10000 times.\"),\n%\tdo_send(Config, send_N, 10000, 16384).\n\ndo_send(Config, What, Num, FrameSize) ->\n\t{ok, ConnPid, StreamRef} = do_gun_open_ws(\"/ws_ignore\", Config),\n\t%% Prepare the frame data.\n\tFrameType = config(frame_type, Config),\n\tFrameData = case FrameType of\n\t\ttext -> do_text_data(Config, FrameSize);\n\t\tbinary -> rand:bytes(FrameSize)\n\tend,\n\t%% Heat up the processes before doing the real run.\n%\tdo_send_loop(Socket, Num, FrameType, Mask, MaskedFrameData),\n\t{Time, _} = timer:tc(?MODULE, do_send_loop, [ConnPid, StreamRef, Num, FrameType, FrameData]),\n\tdo_log(\"~-6s ~-6s ~6s: ~8bµs\", [What, FrameType, do_format_size(FrameSize), Time]),\n\tgun:ws_send(ConnPid, StreamRef, close),\n\t{ok, close} = receive_ws(ConnPid, StreamRef),\n\tgun_down(ConnPid).\n\ndo_send_loop(ConnPid, StreamRef, 0, _, _) ->\n   gun:ws_send(ConnPid, StreamRef, {text, <<\"CHECK\">>}),\n   {ok, {text, <<\"CHECK\">>}} = receive_ws(ConnPid, StreamRef),\n   ok;\ndo_send_loop(ConnPid, StreamRef, Num, FrameType, FrameData) ->\n   gun:ws_send(ConnPid, StreamRef, {FrameType, FrameData}),\n   do_send_loop(ConnPid, StreamRef, Num - 1, FrameType, FrameData).\n\ntcp_send_N_00000B(Config) ->\n\tdoc(\"Send a 0B frame 10000 times.\"),\n\tdo_tcp_send(Config, tcps_N, 10000, 0).\n\ntcp_send_N_00256B(Config) ->\n\tdoc(\"Send a 256B frame 10000 times.\"),\n\tdo_tcp_send(Config, tcps_N, 10000, 256).\n\ntcp_send_N_01024B(Config) ->\n\tdoc(\"Send a 1024B frame 10000 times.\"),\n\tdo_tcp_send(Config, tcps_N, 10000, 1024).\n\ntcp_send_N_04096B(Config) ->\n\tdoc(\"Send a 4096B frame 10000 times.\"),\n\tdo_tcp_send(Config, tcps_N, 10000, 4096).\n\ntcp_send_N_16384B(Config) ->\n\tdoc(\"Send a 16384B frame 10000 times.\"),\n\tdo_tcp_send(Config, tcps_N, 10000, 16384).\n\ndo_tcp_send(Config, What, Num, FrameSize) ->\n\t{ok, Socket} = do_tcp_handshake(Config, #{}),\n\t%% Prepare the frame data.\n\tFrameType = config(frame_type, Config),\n\tFrameData = case FrameType of\n\t\ttext -> do_text_data(Config, FrameSize);\n\t\tbinary -> rand:bytes(FrameSize)\n\tend,\n\t%% Mask the data outside the benchmark to avoid influencing the results.\n\tMask = 16#37fa213d,\n\tMaskedFrameData = ws_SUITE:do_mask(FrameData, Mask, <<>>),\n\tFrameSizeWithHeader = FrameSize + case FrameSize of\n\t\tN when N =< 125 -> 6;\n\t\tN when N =< 16#ffff -> 8;\n\t\tN when N =< 16#7fffffffffffffff -> 14\n\tend,\n\t%% Run the benchmark; different function for h1 and h2.\n\t{Time, _} = case config(protocol, Config) of\n\t\thttp -> timer:tc(?MODULE, do_tcp_send_loop_h1,\n\t\t\t[Socket, Num, FrameType, Mask, MaskedFrameData]);\n\t\thttp2 -> timer:tc(?MODULE, do_tcp_send_loop_h2,\n\t\t\t[Socket, 65535, Num, FrameType, Mask, MaskedFrameData, FrameSizeWithHeader])\n\tend,\n\tdo_log(\"~-6s ~-6s ~6s: ~8bµs\", [What, FrameType, do_format_size(FrameSize), Time]),\n\tgen_tcp:close(Socket).\n\n%% Do a prior knowledge handshake.\ndo_tcp_handshake(Config, LocalSettings) ->\n\tProtocol = config(protocol, Config),\n\tSocket1 = case Protocol of\n\t\thttp ->\n\t\t\t{ok, Socket, _} = ws_SUITE:do_handshake(<<\"/ws_ignore\">>, Config),\n\t\t\tSocket;\n\t\thttp2 ->\n\t\t\t{ok, Socket} = gen_tcp:connect(\"localhost\", config(port, Config), [binary, {active, false}]),\n\t\t\t%% Send a valid preface.\n\t\t\tok = gen_tcp:send(Socket, [\n\t\t\t\t\"PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n\",\n\t\t\t\tcow_http2:settings(LocalSettings)\n\t\t\t]),\n\t\t\t%% Receive the server preface.\n\t\t\t{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),\n\t\t\t{ok, << 4:8, 0:40, SettingsPayload:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),\n\t\t\tRemoteSettings = cow_http2:parse_settings_payload(SettingsPayload),\n\t\t\t#{enable_connect_protocol := true} = RemoteSettings,\n\t\t\t%% Send the SETTINGS ack.\n\t\t\tok = gen_tcp:send(Socket, cow_http2:settings_ack()),\n\t\t\t%% Receive the SETTINGS ack.\n\t\t\t{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t\t\t%% Send a CONNECT :protocol request to upgrade the stream to Websocket.\n\t\t\t{ReqHeadersBlock, _} = cow_hpack:encode([\n\t\t\t\t{<<\":method\">>, <<\"CONNECT\">>},\n\t\t\t\t{<<\":protocol\">>, <<\"websocket\">>},\n\t\t\t\t{<<\":scheme\">>, <<\"http\">>},\n\t\t\t\t{<<\":path\">>, <<\"/ws_ignore\">>},\n\t\t\t\t{<<\":authority\">>, <<\"localhost\">>}, %% @todo Correct port number.\n\t\t\t\t{<<\"sec-websocket-version\">>, <<\"13\">>},\n\t\t\t\t{<<\"origin\">>, <<\"http://localhost\">>}\n\t\t\t]),\n\t\t\tok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, ReqHeadersBlock)),\n\t\t\t%% Receive a 200 response.\n\t\t\t{ok, << Len1:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),\n\t\t\t{ok, RespHeadersBlock} = gen_tcp:recv(Socket, Len1, 1000),\n\t\t\t{RespHeaders, _} = cow_hpack:decode(RespHeadersBlock),\n\t\t\t{_, <<\"200\">>} = lists:keyfind(<<\":status\">>, 1, RespHeaders),\n\t\t\tSocket\n\tend,\n\t%% Enable active mode to avoid delays in receiving data at the end of benchmark.\n\tok = inet:setopts(Socket1, [{active, true}]),\n\t%% Stream number 1 is ready.\n\t{ok, Socket1}.\n\ndo_tcp_send_loop_h1(Socket, 0, _, Mask, _) ->\n\tMaskedFrameData = ws_SUITE:do_mask(<<\"CHECK\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket,\n\t\t<<1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedFrameData/binary>>),\n\tdo_tcp_wait_for_check(Socket);\ndo_tcp_send_loop_h1(Socket, Num, FrameType, Mask, MaskedFrameData) ->\n\tLen = byte_size(MaskedFrameData),\n\tLenBits = case Len of\n\t\tN when N =< 125 -> << N:7 >>;\n\t\tN when N =< 16#ffff -> << 126:7, N:16 >>;\n\t\tN when N =< 16#7fffffffffffffff -> << 127:7, N:64 >>\n\tend,\n\tFrameHeader = <<1:1, 0:3, 2:4, 1:1, LenBits/bits, Mask:32>>,\n\tok = gen_tcp:send(Socket, [\n\t\tFrameHeader,\n\t\tMaskedFrameData\n\t]),\n\tdo_tcp_send_loop_h1(Socket, Num - 1, FrameType, Mask, MaskedFrameData).\n\ndo_tcp_send_loop_h2(Socket, _, 0, _, Mask, _, _) ->\n\tMaskedFrameData = ws_SUITE:do_mask(<<\"CHECK\">>, Mask, <<>>),\n\tok = gen_tcp:send(Socket, cow_http2:data(1, nofin,\n\t\t<<1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedFrameData/binary>>)),\n\tdo_tcp_wait_for_check(Socket);\ndo_tcp_send_loop_h2(Socket, Window0, Num, FrameType, Mask, MaskedFrameData, FrameSizeWithHeader)\n\t\twhen Window0 < FrameSizeWithHeader ->\n\t%% The remaining window isn't large enough so\n\t%% we wait for WINDOW_UPDATE frames.\n\tWindow = do_tcp_wait_window_updates(Socket, Window0),\n\tdo_tcp_send_loop_h2(Socket, Window, Num, FrameType, Mask, MaskedFrameData, FrameSizeWithHeader);\ndo_tcp_send_loop_h2(Socket, Window, Num, FrameType, Mask, MaskedFrameData, FrameSizeWithHeader) ->\n\tLen = byte_size(MaskedFrameData),\n\tLenBits = case Len of\n\t\tN when N =< 125 -> << N:7 >>;\n\t\tN when N =< 16#ffff -> << 126:7, N:16 >>;\n\t\tN when N =< 16#7fffffffffffffff -> << 127:7, N:64 >>\n\tend,\n\tFrameHeader = <<1:1, 0:3, 2:4, 1:1, LenBits/bits, Mask:32>>,\n\tok = gen_tcp:send(Socket, cow_http2:data(1, nofin, [\n\t\tFrameHeader,\n\t\tMaskedFrameData\n\t])),\n\tdo_tcp_send_loop_h2(Socket, Window - FrameSizeWithHeader,\n\t\tNum - 1, FrameType, Mask, MaskedFrameData, FrameSizeWithHeader).\n\ndo_tcp_wait_window_updates(Socket, Window) ->\n\treceive\n\t\t{tcp, Socket, Data} ->\n\t\t\tdo_tcp_wait_window_updates_parse(Socket, Window, Data)\n\tafter 0 ->\n\t\tWindow\n\tend.\n\ndo_tcp_wait_window_updates_parse(Socket, Window, Data) ->\n\tcase Data of\n\t\t%% Ignore the connection-wide WINDOW_UPDATE.\n\t\t<<4:24, 8:8, 0:8, 0:32, 0:1, _:31, Rest/bits>> ->\n\t\t\tdo_tcp_wait_window_updates_parse(Socket, Window, Rest);\n\t\t%% Use the stream-specific WINDOW_UPDATE to increment our window.\n\t\t<<4:24, 8:8, 0:8, 1:32, 0:1, Inc:31, Rest/bits>> ->\n\t\t\tdo_tcp_wait_window_updates_parse(Socket, Window + Inc, Rest);\n\t\t%% Other frames are not expected.\n\t\t<<>> ->\n\t\t\tdo_tcp_wait_window_updates(Socket, Window)\n\tend.\n\ndo_tcp_wait_for_check(Socket) ->\n\treceive\n\t\t{tcp, Socket, Data} ->\n\t\t\tcase binary:match(Data, <<\"CHECK\">>) of\n\t\t\t\tnomatch -> do_tcp_wait_for_check(Socket);\n\t\t\t\t_ -> ok\n\t\t\tend\n\tafter 5000 ->\n\t\terror(timeout)\n\tend.\n\n%% Internal.\n\ndo_text_data(Config, FrameSize) ->\n\tdo_text_data1(config(text_data, Config), FrameSize).\n\ndo_text_data1(LargeText, FrameSize) when byte_size(LargeText) >= FrameSize ->\n\tbinary:part(LargeText, 0, FrameSize);\ndo_text_data1(LargeText, FrameSize) ->\n\tdo_text_data1(<<LargeText/binary, LargeText/binary>>, FrameSize).\n\ndo_format_size(Size) when Size < 1024 ->\n\tinteger_to_list(Size) ++ \"B\";\ndo_format_size(Size) when Size < (1024*1024) ->\n\tinteger_to_list(Size div 1024) ++ \"KiB\";\ndo_format_size(Size) ->\n\tinteger_to_list(Size div (1024*1024)) ++ \"MiB\".\n\ndo_log(Str, Args) ->\n\tct:log(Str, Args),\n\tio:format(ct_default_gl, Str ++ \"~n\", Args).\n"
  },
  {
    "path": "test/ws_perf_SUITE_data/ascii.txt",
    "content": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper. Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit.\n\nUt velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue. Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna.\n\nAliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet. Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet.\n"
  },
  {
    "path": "test/ws_perf_SUITE_data/grok_segond.txt",
    "content": "Some filler content to ensure the split doesn't happen in the middle of a multi-byte character.\n\n- Extrait de session Grok 2 (license MIT).\n\nQuestion : Merci de générer un texte de 32000 caractères dont le but sera de tester un algorithme UTF-8\n\nJe vais générer un texte de 32000 caractères qui inclura une variété de caractères pour tester un algorithme UTF-8. Ce texte contiendra des caractères de différents scripts, des symboles spéciaux, des emojis, et des caractères combinés pour couvrir une large gamme de points de code :\n\nÀ mesure que la technologie évolue, les systèmes d'encodage des caractères deviennent de plus en plus sophistiqués. L'encodage UTF-8, par exemple, est conçu pour représenter chaque caractère Unicode en utilisant une séquence d'octets. Cela permet de supporter des milliers de langues et symboles. Voici quelques exemples de ce que l'UTF-8 peut encoder :\n\nLatin : Bonjour le monde! Hello world!\nCyrillique : Привет, мир!\nGrec : Γεια σου κόσμε!\nChinois : 你好世界!\nJaponais : こんにちは世界！\nCoréen : 안녕 세계!\nArabe : مرحبًا بالعالم!\nHébreu : שלום עולם!\nIndien (Devanagari) : नमस्ते दुनिया!\nThaï : สวัสดีโลก!\n\nEn plus des scripts de base, UTF-8 peut également encoder des symboles mathématiques, des signes de ponctuation spécifiques, des lettres accentuées, et même des emojis :\n\nSymboles mathématiques : ∫∑∏×÷√∞\nEmojis : 😀👍🌍🎉🔥\nCaractères combinés : ä, ê, ï, õ, ú (a avec tréma, e avec accent circonflexe, etc.)\n\nPour tester l'encodage, on peut utiliser des combinaisons complexes :\n\nCombinaison de symboles : こんにちは! 😃😄😁🤣👌👍👏🙌🇫🇷🇺🇸🌟✨\nTextes multilingues : La vie est belle en français, 美しい日本の桜, Der Sommer ist schön in Deutschland.\n\nPour tester la gestion des caractères spéciaux, voici quelques exemples supplémentaires :\n\nPonctuation : .,;:!?¿¡…—–„“«»\nSymboles divers : $¢€£¥₩₽₹¤§¶†‡\nLettres accentuées : àáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ\nLigatures : ﬁﬂ\n\nEnfin, pour s'assurer que les séquences d'échappement et les caractères de contrôle sont bien gérés :\n\nCaractères de contrôle : \\n\\t\\r\\f\\v (nouvelle ligne, tabulation, retour chariot, nouvelle page, tabulation verticale)\nSéries de répétition : `abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabc`\n\nCe texte devrait fournir un bon test pour un algorithme UTF-8 en raison de sa diversité et de sa longueur. \n\nCe texte atteint environ 32000 caractères, incluant une variété de cas pour tester un algorithme d'encodage UTF-8.\n\n- Extrait de La Bible en français, version Louis Segond 1910 (domaine publique).\n\nRomains 1\n\n1.1\nPaul, serviteur de Jésus Christ, appelé à être apôtre, mis à part pour annoncer l'Évangile de Dieu, -\n1.2\nqui avait été promis auparavant de la part de Dieu par ses prophètes dans les saintes Écritures,\n1.3\net qui concerne son Fils (né de la postérité de David, selon la chair,\n1.4\net déclaré Fils de Dieu avec puissance, selon l'Esprit de sainteté, par sa résurrection d'entre les morts), Jésus Christ notre Seigneur,\n1.5\npar qui nous avons reçu la grâce et l'apostolat, pour amener en son nom à l'obéissance de la foi tous les païens,\n1.6\nparmi lesquels vous êtes aussi, vous qui avez été appelés par Jésus Christ-\n1.7\nà tous ceux qui, à Rome, sont bien-aimés de Dieu, appelés à être saints: que la grâce et la paix vous soient données de la part de Dieu notre Père et du Seigneur Jésus Christ!\n1.8\nJe rends d'abord grâces à mon Dieu par Jésus Christ, au sujet de vous tous, de ce que votre foi est renommée dans le monde entier.\n1.9\nDieu, que je sers en mon esprit dans l'Évangile de son Fils, m'est témoin que je fais sans cesse mention de vous,\n1.10\ndemandant continuellement dans mes prières d'avoir enfin, par sa volonté, le bonheur d'aller vers vous.\n1.11\nCar je désire vous voir, pour vous communiquer quelque don spirituel, afin que vous soyez affermis,\n1.12\nou plutôt, afin que nous soyons encouragés ensemble au milieu de vous par la foi qui nous est commune, à vous et à moi.\n1.13\nJe ne veux pas vous laisser ignorer, frères, que j'ai souvent formé le projet d'aller vous voir, afin de recueillir quelque fruit parmi vous, comme parmi les autres nations; mais j'en ai été empêché jusqu'ici.\n1.14\nJe me dois aux Grecs et aux barbares, aux savants et aux ignorants.\n1.15\nAinsi j'ai un vif désir de vous annoncer aussi l'Évangile, à vous qui êtes à Rome.\n1.16\nCar je n'ai point honte de l'Évangile: c'est une puissance de Dieu pour le salut de quiconque croit, du Juif premièrement, puis du Grec,\n1.17\nparce qu'en lui est révélée la justice de Dieu par la foi et pour la foi, selon qu'il est écrit: Le juste vivra par la foi.\n1.18\nLa colère de Dieu se révèle du ciel contre toute impiété et toute injustice des hommes qui retiennent injustement la vérité captive,\n1.19\ncar ce qu'on peut connaître de Dieu est manifeste pour eux, Dieu le leur ayant fait connaître.\n1.20\nEn effet, les perfections invisibles de Dieu, sa puissance éternelle et sa divinité, se voient comme à l'oeil, depuis la création du monde, quand on les considère dans ses ouvrages. Ils sont donc inexcusables,\n1.21\npuisque ayant connu Dieu, ils ne l'ont point glorifié comme Dieu, et ne lui ont point rendu grâces; mais ils se sont égarés dans leurs pensées, et leur coeur sans intelligence a été plongé dans les ténèbres.\n1.22\nSe vantant d'être sages, ils sont devenus fous;\n1.23\net ils ont changé la gloire du Dieu incorruptible en images représentant l'homme corruptible, des oiseaux, des quadrupèdes, et des reptiles.\n1.24\nC'est pourquoi Dieu les a livrés à l'impureté, selon les convoitises de leurs coeurs; en sorte qu'ils déshonorent eux-mêmes leurs propres corps;\n1.25\neux qui ont changé la vérité de Dieu en mensonge, et qui ont adoré et servi la créature au lieu du Créateur, qui est béni éternellement. Amen!\n1.26\nC'est pourquoi Dieu les a livrés à des passions infâmes: car leurs femmes ont changé l'usage naturel en celui qui est contre nature;\n1.27\net de même les hommes, abandonnant l'usage naturel de la femme, se sont enflammés dans leurs désirs les uns pour les autres, commettant homme avec homme des choses infâmes, et recevant en eux-mêmes le salaire que méritait leur égarement.\n1.28\nComme ils ne se sont pas souciés de connaître Dieu, Dieu les a livrés à leur sens réprouvé, pour commettre des choses indignes,\n1.29\nétant remplis de toute espèce d'injustice, de méchanceté, de cupidité, de malice; pleins d'envie, de meurtre, de querelle, de ruse, de malignité;\n1.30\nrapporteurs, médisants, impies, arrogants, hautains, fanfarons, ingénieux au mal, rebelles à leurs parents, dépourvus d'intelligence,\n1.31\nde loyauté, d'affection naturelle, de miséricorde.\n1.32\nEt, bien qu'ils connaissent le jugement de Dieu, déclarant dignes de mort ceux qui commettent de telles choses, non seulement ils les font, mais ils approuvent ceux qui les font.\n\nRomains 2\n\n2.1\nO homme, qui que tu sois, toi qui juges, tu es donc inexcusable; car, en jugeant les autres, tu te condamnes toi-même, puisque toi qui juges, tu fais les mêmes choses.\n2.2\nNous savons, en effet, que le jugement de Dieu contre ceux qui commettent de telles choses est selon la vérité.\n2.3\nEt penses-tu, ô homme, qui juges ceux qui commettent de telles choses, et qui les fais, que tu échapperas au jugement de Dieu?\n2.4\nOu méprises-tu les richesses de sa bonté, de sa patience et de sa longanimité, ne reconnaissant pas que la bonté de Dieu te pousse à la repentance?\n2.5\nMais, par ton endurcissement et par ton coeur impénitent, tu t'amasses un trésor de colère pour le jour de la colère et de la manifestation du juste jugement de Dieu,\n2.6\nqui rendra à chacun selon ses oeuvres;\n2.7\nréservant la vie éternelle à ceux qui, par la persévérance à bien faire, cherchent l'honneur, la gloire et l'immortalité;\n2.8\nmais l'irritation et la colère à ceux qui, par esprit de dispute, sont rebelles à la vérité et obéissent à l'injustice.\n2.9\nTribulation et angoisse sur toute âme d'homme qui fait le mal, sur le Juif premièrement, puis sur le Grec!\n2.10\nGloire, honneur et paix pour quiconque fait le bien, pour le Juif premièrement, puis pour le Grec!\n2.11\nCar devant Dieu il n'y a point d'acception de personnes.\n2.12\nTous ceux qui ont péché sans la loi périront aussi sans la loi, et tous ceux qui ont péché avec la loi seront jugés par la loi.\n2.13\nCe ne sont pas, en effet, ceux qui écoutent la loi qui sont justes devant Dieu, mais ce sont ceux qui la mettent en pratique qui seront justifiés.\n2.14\nQuand les païens, qui n'ont point la loi, font naturellement ce que prescrit la loi, ils sont, eux qui n'ont point la loi, une loi pour eux-mêmes;\n2.15\nils montrent que l'oeuvre de la loi est écrite dans leurs coeurs, leur conscience en rendant témoignage, et leurs pensées s'accusant ou se défendant tour à tour.\n2.16\nC'est ce qui paraîtra au jour où, selon mon Évangile, Dieu jugera par Jésus Christ les actions secrètes des hommes.\n2.17\nToi qui te donnes le nom de Juif, qui te reposes sur la loi, qui te glorifies de Dieu,\n2.18\nqui connais sa volonté, qui apprécies la différence des choses, étant instruit par la loi;\n2.19\ntoi qui te flattes d'être le conducteur des aveugles, la lumière de ceux qui sont dans les ténèbres,\n2.20\nle docteur des insensés, le maître des ignorants, parce que tu as dans la loi la règle de la science et de la vérité;\n2.21\ntoi donc, qui enseignes les autres, tu ne t'enseignes pas toi-même! Toi qui prêches de ne pas dérober, tu dérobes!\n2.22\nToi qui dis de ne pas commettre d'adultère, tu commets l'adultère! Toi qui as en abomination les idoles, tu commets des sacrilèges!\n2.23\nToi qui te fais une gloire de la loi, tu déshonores Dieu par la transgression de la loi!\n2.24\nCar le nom de Dieu est à cause de vous blasphémé parmi les païens, comme cela est écrit.\n2.25\nLa circoncision est utile, si tu mets en pratique la loi; mais si tu transgresses la loi, ta circoncision devient incirconcision.\n2.26\nSi donc l'incirconcis observe les ordonnances de la loi, son incirconcision ne sera-t-elle pas tenue pour circoncision?\n2.27\nL'incirconcis de nature, qui accomplit la loi, ne te condamnera-t-il pas, toi qui la transgresses, tout en ayant la lettre de la loi et la circoncision?\n2.28\nLe Juif, ce n'est pas celui qui en a les dehors; et la circoncision, ce n'est pas celle qui est visible dans la chair.\n2.29\nMais le Juif, c'est celui qui l'est intérieurement; et la circoncision, c'est celle du coeur, selon l'esprit et non selon la lettre. La louange de ce Juif ne vient pas des hommes, mais de Dieu.\n\nRomains 3\n\n3.1\nQuel est donc l'avantage des Juifs, ou quelle est l'utilité de la circoncision?\n3.2\nIl est grand de toute manière, et tout d'abord en ce que les oracles de Dieu leur ont été confiés.\n3.3\nEh quoi! si quelques-uns n'ont pas cru, leur incrédulité anéantira-t-elle la fidélité de Dieu?\n3.4\nLoin de là! Que Dieu, au contraire, soit reconnu pour vrai, et tout homme pour menteur, selon qu'il est écrit: Afin que tu sois trouvé juste dans tes paroles, Et que tu triomphes lorsqu'on te juge.\n3.5\nMais si notre injustice établit la justice de Dieu, que dirons-nous? Dieu est-il injuste quand il déchaîne sa colère? (Je parle à la manière des hommes.)\n3.6\nLoin de là! Autrement, comment Dieu jugerait-il le monde?\n3.7\nEt si, par mon mensonge, la vérité de Dieu éclate davantage pour sa gloire, pourquoi suis-je moi-même encore jugé comme pécheur?\n3.8\nEt pourquoi ne ferions-nous pas le mal afin qu'il en arrive du bien, comme quelques-uns, qui nous calomnient, prétendent que nous le disons? La condamnation de ces gens est juste.\n3.9\nQuoi donc! sommes-nous plus excellents? Nullement. Car nous avons déjà prouvé que tous, Juifs et Grecs, sont sous l'empire du péché,\n3.10\nselon qu'il est écrit: Il n'y a point de juste, Pas même un seul;\n3.11\nNul n'est intelligent, Nul ne cherche Dieu; Tous sont égarés, tous sont pervertis;\n3.12\nIl n'en est aucun qui fasse le bien, Pas même un seul;\n3.13\nLeur gosier est un sépulcre ouvert; Ils se servent de leurs langues pour tromper; Ils ont sous leurs lèvres un venin d'aspic;\n3.14\nLeur bouche est pleine de malédiction et d'amertume;\n3.15\nIls ont les pieds légers pour répandre le sang;\n3.16\nLa destruction et le malheur sont sur leur route;\n3.17\nIls ne connaissent pas le chemin de la paix;\n3.18\nLa crainte de Dieu n'est pas devant leurs yeux.\n3.19\nOr, nous savons que tout ce que dit la loi, elle le dit à ceux qui sont sous la loi, afin que toute bouche soit fermée, et que tout le monde soit reconnu coupable devant Dieu.\n3.20\nCar nul ne sera justifié devant lui par les oeuvres de la loi, puisque c'est par la loi que vient la connaissance du péché.\n3.21\nMais maintenant, sans la loi est manifestée la justice de Dieu, à laquelle rendent témoignage la loi et les prophètes,\n3.22\njustice de Dieu par la foi en Jésus Christ pour tous ceux qui croient. Il n'y a point de distinction.\n3.23\nCar tous ont péché et sont privés de la gloire de Dieu;\n3.24\net ils sont gratuitement justifiés par sa grâce, par le moyen de la rédemption qui est en Jésus Christ.\n3.25\nC'est lui que Dieu a destiné, par son sang, à être, pour ceux qui croiraient victime propitiatoire, afin de montrer sa justice, parce qu'il avait laissé impunis les péchés commis auparavant, au temps de sa patience, afin, dis-je,\n3.26\nde montrer sa justice dans le temps présent, de manière à être juste tout en justifiant celui qui a la foi en Jésus.\n3.27\nOù donc est le sujet de se glorifier? Il est exclu. Par quelle loi? Par la loi des oeuvres? Non, mais par la loi de la foi.\n3.28\nCar nous pensons que l'homme est justifié par la foi, sans les oeuvres de la loi.\n3.29\nOu bien Dieu est-il seulement le Dieu des Juifs? Ne l'est-il pas aussi des païens? Oui, il l'est aussi des païens,\n3.30\npuisqu'il y a un seul Dieu, qui justifiera par la foi les circoncis, et par la foi les incirconcis.\n3.31\nAnéantissons-nous donc la loi par la foi? Loin de là! Au contraire, nous confirmons la loi.\n\nRomains 4\n\n4.1\nQue dirons-nous donc qu'Abraham, notre père, a obtenu selon la chair?\n4.2\nSi Abraham a été justifié par les oeuvres, il a sujet de se glorifier, mais non devant Dieu.\n4.3\nCar que dit l'Écriture? Abraham crut à Dieu, et cela lui fut imputé à justice.\n4.4\nOr, à celui qui fait une oeuvre, le salaire est imputé, non comme une grâce, mais comme une chose due;\n4.5\net à celui qui ne fait point d'oeuvre, mais qui croit en celui qui justifie l'impie, sa foi lui est imputée à justice.\n4.6\nDe même David exprime le bonheur de l'homme à qui Dieu impute la justice sans les oeuvres:\n4.7\nHeureux ceux dont les iniquités sont pardonnées, Et dont les péchés sont couverts!\n4.8\nHeureux l'homme à qui le Seigneur n'impute pas son péché!\n4.9\nCe bonheur n'est-il que pour les circoncis, ou est-il également pour les incirconcis? Car nous disons que la foi fut imputée à justice à Abraham.\n4.10\nComment donc lui fut-elle imputée? Était-ce après, ou avant sa circoncision? Il n'était pas encore circoncis, il était incirconcis.\n4.11\nEt il reçut le signe de la circoncision, comme sceau de la justice qu'il avait obtenue par la foi quand il était incirconcis, afin d'être le père de tous les incirconcis qui croient, pour que la justice leur fût aussi imputée,\n4.12\net le père des circoncis, qui ne sont pas seulement circoncis, mais encore qui marchent sur les traces de la foi de notre père Abraham quand il était incirconcis.\n4.13\nEn effet, ce n'est pas par la loi que l'héritage du monde a été promis à Abraham ou à sa postérité, c'est par la justice de la foi.\n4.14\nCar, si les héritiers le sont par la loi, la foi est vaine, et la promesse est anéantie,\n4.15\nparce que la loi produit la colère, et que là où il n'y a point de loi il n'y a point non plus de transgression.\n4.16\nC'est pourquoi les héritiers le sont par la foi, pour que ce soit par grâce, afin que la promesse soit assurée à toute la postérité, non seulement à celle qui est sous la loi, mais aussi à celle qui a la foi d'Abraham, notre père à tous, selon qu'il est écrit:\n4.17\nJe t'ai établi père d'un grand nombre de nations. Il est notre père devant celui auquel il a cru, Dieu, qui donne la vie aux morts, et qui appelle les choses qui ne sont point comme si elles étaient.\n4.18\nEspérant contre toute espérance, il crut, en sorte qu'il devint père d'un grand nombre de nations, selon ce qui lui avait été dit: Telle sera ta postérité.\n4.19\nEt, sans faiblir dans la foi, il ne considéra point que son corps était déjà usé, puisqu'il avait près de cent ans, et que Sara n'était plus en état d'avoir des enfants.\n4.20\nIl ne douta point, par incrédulité, au sujet de la promesse de Dieu; mais il fut fortifié par la foi, donnant gloire à Dieu,\n4.21\net ayant la pleine conviction que ce qu'il promet il peut aussi l'accomplir.\n4.22\nC'est pourquoi cela lui fut imputé à justice.\n4.23\nMais ce n'est pas à cause de lui seul qu'il est écrit que cela lui fut imputé;\n4.24\nc'est encore à cause de nous, à qui cela sera imputé, à nous qui croyons en celui qui a ressuscité des morts Jésus notre Seigneur,\n4.25\nlequel a été livré pour nos offenses, et est ressuscité pour notre justification.\n\nRomains 5\n\n5.1\nÉtant donc justifiés par la foi, nous avons la paix avec Dieu par notre Seigneur Jésus Christ,\n5.2\nà qui nous devons d'avoir eu par la foi accès à cette grâce, dans laquelle nous demeurons fermes, et nous nous glorifions dans l'espérance de la gloire de Dieu.\n5.3\nBien plus, nous nous glorifions même des afflictions, sachant que l'affliction produit la persévérance,\n5.4\nla persévérance la victoire dans l'épreuve, et cette victoire l'espérance.\n5.5\nOr, l'espérance ne trompe point, parce que l'amour de Dieu est répandu dans nos coeurs par le Saint Esprit qui nous a été donné.\n5.6\nCar, lorsque nous étions encore sans force, Christ, au temps marqué, est mort pour des impies.\n5.7\nA peine mourrait-on pour un juste; quelqu'un peut-être mourrait-il pour un homme de bien.\n5.8\nMais Dieu prouve son amour envers nous, en ce que, lorsque nous étions encore des pécheurs, Christ est mort pour nous.\n5.9\nA plus forte raison donc, maintenant que nous sommes justifiés par son sang, serons-nous sauvés par lui de la colère.\n5.10\nCar si, lorsque nous étions ennemis, nous avons été réconciliés avec Dieu par la mort de son Fils, à plus forte raison, étant réconciliés, serons-nous sauvés par sa vie.\n5.11\nEt non seulement cela, mais encore nous nous glorifions en Dieu par notre Seigneur Jésus Christ, par qui maintenant nous avons obtenu la réconciliation.\n5.12\nC'est pourquoi, comme par un seul homme le péché est entré dans le monde, et par le péché la mort, et qu'ainsi la mort s'est étendue sur tous les hommes, parce que tous ont péché,...\n5.13\ncar jusqu'à la loi le péché était dans le monde. Or, le péché n'est pas imputé, quand il n'y a point de loi.\n5.14\nCependant la mort a régné depuis Adam jusqu'à Moïse, même sur ceux qui n'avaient pas péché par une transgression semblable à celle d'Adam, lequel est la figure de celui qui devait venir.\n5.15\nMais il n'en est pas du don gratuit comme de l'offense; car, si par l'offense d'un seul il en est beaucoup qui sont morts, à plus forte raison la grâce de Dieu et le don de la grâce venant d'un seul homme, Jésus Christ, ont-ils été abondamment répandus sur beaucoup.\n5.16\nEt il n'en est pas du don comme de ce qui est arrivé par un seul qui a péché; car c'est après une seule offense que le jugement est devenu condamnation, tandis que le don gratuit devient justification après plusieurs offenses.\n5.17\nSi par l'offense d'un seul la mort a régné par lui seul, à plus forte raison ceux qui reçoivent l'abondance de la grâce et du don de la justice régneront-ils dans la vie par Jésus Christ lui seul.\n5.18\nAinsi donc, comme par une seule offense la condamnation a atteint tous les hommes, de même par un seul acte de justice la justification qui donne la vie s'étend à tous les hommes.\n5.19\nCar, comme par la désobéissance d'un seul homme beaucoup ont été rendus pécheurs, de même par l'obéissance d'un seul beaucoup seront rendus justes.\n5.20\nOr, la loi est intervenue pour que l'offense abondât, mais là où le péché a abondé, la grâce a surabondé,\n5.21\nafin que, comme le péché a régné par la mort, ainsi la grâce régnât par la justice pour la vie éternelle, par Jésus Christ notre Seigneur.\n\nRomains 6\n\n6.1\nQue dirons-nous donc? Demeurerions-nous dans le péché, afin que la grâce abonde?\n6.2\nLoin de là! Nous qui sommes morts au péché, comment vivrions-nous encore dans le péché?\n6.3\nIgnorez-vous que nous tous qui avons été baptisés en Jésus Christ, c'est en sa mort que nous avons été baptisés?\n6.4\nNous avons donc été ensevelis avec lui par le baptême en sa mort, afin que, comme Christ est ressuscité des morts par la gloire du Père, de même nous aussi nous marchions en nouveauté de vie.\n6.5\nEn effet, si nous sommes devenus une même plante avec lui par la conformité à sa mort, nous le serons aussi par la conformité à sa résurrection,\n6.6\nsachant que notre vieil homme a été crucifié avec lui, afin que le corps du péché fût détruit, pour que nous ne soyons plus esclaves du péché;\n6.7\ncar celui qui est mort est libre du péché.\n6.8\nOr, si nous sommes morts avec Christ, nous croyons que nous vivrons aussi avec lui,\n6.9\nsachant que Christ ressuscité des morts ne meurt plus; la mort n'a plus de pouvoir sur lui.\n6.10\nCar il est mort, et c'est pour le péché qu'il est mort une fois pour toutes; il est revenu à la vie, et c'est pour Dieu qu'il vit.\n6.11\nAinsi vous-mêmes, regardez-vous comme morts au péché, et comme vivants pour Dieu en Jésus Christ.\n6.12\nQue le péché ne règne donc point dans votre corps mortel, et n'obéissez pas à ses convoitises.\n6.13\nNe livrez pas vos membres au péché, comme des instruments d'iniquité; mais donnez-vous vous-mêmes à Dieu, comme étant vivants de morts que vous étiez, et offrez à Dieu vos membres, comme des instruments de justice.\n6.14\nCar le péché n'aura point de pouvoir sur vous, puisque vous êtes, non sous la loi, mais sous la grâce.\n6.15\nQuoi donc! Pécherions-nous, parce que nous sommes, non sous la loi, mais sous la grâce? Loin de là!\n6.16\nNe savez-vous pas qu'en vous livrant à quelqu'un comme esclaves pour lui obéir, vous êtes esclaves de celui à qui vous obéissez, soit du péché qui conduit à la mort, soit de l'obéissance qui conduit à la justice?\n6.17\nMais grâces soient rendues à Dieu de ce que, après avoir été esclaves du péché, vous avez obéi de coeur à la règle de doctrine dans laquelle vous avez été instruits.\n6.18\nAyant été affranchis du péché, vous êtes devenus esclaves de la justice. -\n6.19\nJe parle à la manière des hommes, à cause de la faiblesse de votre chair. -De même donc que vous avez livré vos membres comme esclaves à l'impureté et à l'iniquité, pour arriver à l'iniquité, ainsi maintenant livrez vos membres comme esclaves à la justice, pour arriver à la sainteté.\n6.20\nCar, lorsque vous étiez esclaves du péché, vous étiez libres à l'égard de la justice.\n6.21\nQuels fruits portiez-vous alors? Des fruits dont vous rougissez aujourd'hui. Car la fin de ces choses, c'est la mort.\n6.22\nMais maintenant, étant affranchis du péché et devenus esclaves de Dieu, vous avez pour fruit la sainteté et pour fin la vie éternelle.\n6.23\nCar le salaire du péché, c'est la mort; mais le don gratuit de Dieu, c'est la vie éternelle en Jésus Christ notre Seigneur.\n\nRomains 7\n\n7.1\nIgnorez-vous, frères, -car je parle à des gens qui connaissent la loi, -que la loi exerce son pouvoir sur l'homme aussi longtemps qu'il vit?\n7.2\nAinsi, une femme mariée est liée par la loi à son mari tant qu'il est vivant; mais si le mari meurt, elle est dégagée de la loi qui la liait à son mari.\n7.3\nSi donc, du vivant de son mari, elle devient la femme d'un autre homme, elle sera appelée adultère; mais si le mari meurt, elle est affranchie de la loi, de sorte qu'elle n'est point adultère en devenant la femme d'un autre.\n7.4\nDe même, mes frères, vous aussi vous avez été, par le corps de Christ, mis à mort en ce qui concerne la loi, pour que vous apparteniez à un autre, à celui qui est ressuscité des morts, afin que nous portions des fruits pour Dieu.\n7.5\nCar, lorsque nous étions dans la chair, les passions des péchés provoquées par la loi agissaient dans nos membres, de sorte que nous portions des fruits pour la mort.\n7.6\nMais maintenant, nous avons été dégagés de la loi, étant morts à cette loi sous laquelle nous étions retenus, de sorte que nous servons dans un esprit nouveau, et non selon la lettre qui a vieilli.\n7.7\nQue dirons-nous donc? La loi est-elle péché? Loin de là! Mais je n'ai connu le péché que par la loi. Car je n'aurais pas connu la convoitise, si la loi n'eût dit: Tu ne convoiteras point.\n7.8\nEt le péché, saisissant l'occasion, produisit en moi par le commandement toutes sortes de convoitises; car sans loi le péché est mort.\n7.9\nPour moi, étant autrefois sans loi, je vivais; mais quand le commandement vint, le péché reprit vie, et moi je mourus.\n7.10\nAinsi, le commandement qui conduit à la vie se trouva pour moi conduire à la mort.\n7.11\nCar le péché saisissant l'occasion, me séduisit par le commandement, et par lui me fit mourir.\n7.12\nLa loi donc est sainte, et le commandement est saint, juste et bon.\n7.13\nCe qui est bon a-t-il donc été pour moi une cause de mort? Loin de là! Mais c'est le péché, afin qu'il se manifestât comme péché en me donnant la mort par ce qui est bon, et que, par le commandement, il devînt condamnable au plus haut point.\n7.14\nNous savons, en effet, que la loi est spirituelle; mais moi, je suis charnel, vendu au péché.\n7.15\nCar je ne sais pas ce que je fais: je ne fais point ce que je veux, et je fais ce que je hais.\n7.16\nOr, si je fais ce que je ne veux pas, je reconnais par là que la loi est bonne.\n7.17\nEt maintenant ce n'est plus moi qui le fais, mais c'est le péché qui habite en moi.\n7.18\nCe qui est bon, je le sais, n'habite pas en moi, c'est-à-dire dans ma chair: j'ai la volonté, mais non le pouvoir de faire le bien.\n7.19\nCar je ne fais pas le bien que je veux, et je fais le mal que je ne veux pas.\n7.20\nEt si je fais ce que je ne veux pas, ce n'est plus moi qui le fais, c'est le péché qui habite en moi.\n7.21\nJe trouve donc en moi cette loi: quand je veux faire le bien, le mal est attaché à moi.\n7.22\nCar je prends plaisir à la loi de Dieu, selon l'homme intérieur;\n7.23\nmais je vois dans mes membres une autre loi, qui lutte contre la loi de mon entendement, et qui me rend captif de la loi du péché, qui est dans mes membres.\n7.24\nMisérable que je suis! Qui me délivrera du corps de cette mort?...\n7.25\nGrâces soient rendues à Dieu par Jésus Christ notre Seigneur!... Ainsi donc, moi-même, je suis par l'entendement esclave de la loi de Dieu, et je suis par la chair esclave de la loi du péché.\n\nRomains 8\n\n8.1\nIl n'y a donc maintenant aucune condamnation pour ceux qui sont en Jésus Christ.\n8.2\nEn effet, la loi de l'esprit de vie en Jésus Christ m'a affranchi de la loi du péché et de la mort.\n8.3\nCar-chose impossible à la loi, parce que la chair la rendait sans force, -Dieu a condamné le péché dans la chair, en envoyant, à cause du péché, son propre Fils dans une chair semblable à celle du péché,\n8.4\net cela afin que la justice de la loi fût accomplie en nous, qui marchons, non selon la chair, mais selon l'esprit.\n8.5\nCeux, en effet, qui vivent selon la chair, s'affectionnent aux choses de la chair, tandis que ceux qui vivent selon l'esprit s'affectionnent aux choses de l'esprit.\n8.6\nEt l'affection de la chair, c'est la mort, tandis que l'affection de l'esprit, c'est la vie et la paix;\n8.7\ncar l'affection de la chair est inimitié contre Dieu, parce qu'elle ne se soumet pas à la loi de Dieu, et qu'elle ne le peut même pas.\n8.8\nOr ceux qui vivent selon la chair ne sauraient plaire à Dieu.\n8.9\nPour vous, vous ne vivez pas selon la chair, mais selon l'esprit, si du moins l'Esprit de Dieu habite en vous. Si quelqu'un n'a pas l'Esprit de Christ, il ne lui appartient pas.\n8.10\nEt si Christ est en vous, le corps, il est vrai, est mort à cause du péché, mais l'esprit est vie à cause de la justice.\n8.11\nEt si l'Esprit de celui qui a ressuscité Jésus d'entre les morts habite en vous, celui qui a ressuscité Christ d'entre les morts rendra aussi la vie à vos corps mortels par son Esprit qui habite en vous.\n8.12\nAinsi donc, frères, nous ne sommes point redevables à la chair, pour vivre selon la chair.\n8.13\nSi vous vivez selon la chair, vous mourrez; mais si par l'Esprit vous faites mourir les actions du corps, vous vivrez,\n8.14\ncar tous ceux qui sont conduits par l'Esprit de Dieu sont fils de Dieu.\n8.15\nEt vous n'avez point reçu un esprit de servitude, pour être encore dans la crainte; mais vous avez reçu un Esprit d'adoption, par lequel nous crions: Abba! Père!\n8.16\nL'Esprit lui-même rend témoignage à notre esprit que nous sommes enfants de Dieu.\n8.17\nOr, si nous sommes enfants, nous sommes aussi héritiers: héritiers de Dieu, et cohéritiers de Christ, si toutefois nous souffrons avec lui, afin d'être glorifiés avec lui.\n8.18\nJ'estime que les souffrances du temps présent ne sauraient être comparées à la gloire à venir qui sera révélée pour nous.\n8.19\nAussi la création attend-elle avec un ardent désir la révélation des fils de Dieu.\n8.20\nCar la création a été soumise à la vanité, -non de son gré, mais à cause de celui qui l'y a soumise, -\n8.21\navec l'espérance qu'elle aussi sera affranchie de la servitude de la corruption, pour avoir part à la liberté de la gloire des enfants de Dieu.\n8.22\nOr, nous savons que, jusqu'à ce jour, la création tout entière soupire et souffre les douleurs de l'enfantement.\n8.23\nEt ce n'est pas elle seulement; mais nous aussi, qui avons les prémices de l'Esprit, nous aussi nous soupirons en nous-mêmes, en attendant l'adoption, la rédemption de notre corps.\n8.24\nCar c'est en espérance que nous sommes sauvés. Or, l'espérance qu'on voit n'est plus espérance: ce qu'on voit, peut-on l'espérer encore?\n8.25\nMais si nous espérons ce que nous ne voyons pas, nous l'attendons avec persévérance.\n8.26\nDe même aussi l'Esprit nous aide dans notre faiblesse, car nous ne savons pas ce qu'il nous convient de demander dans nos prières. Mais l'Esprit lui-même intercède par des soupirs inexprimables;\n8.27\net celui qui sonde les coeurs connaît quelle est la pensée de l'Esprit, parce que c'est selon Dieu qu'il intercède en faveur des saints.\n8.28\nNous savons, du reste, que toutes choses concourent au bien de ceux qui aiment Dieu, de ceux qui sont appelés selon son dessein.\n8.29\nCar ceux qu'il a connus d'avance, il les a aussi prédestinés à être semblables à l'image de son Fils, afin que son Fils fût le premier-né entre plusieurs frères.\n8.30\nEt ceux qu'il a prédestinés, il les a aussi appelés; et ceux qu'il a appelés, il les a aussi justifiés; et ceux qu'il a justifiés, il les a aussi glorifiés.\n8.31\nQue dirons-nous donc à l'égard de ces choses? Si Dieu est pour nous, qui sera contre nous?\n8.32\nLui, qui n'a point épargné son propre Fils, mais qui l'a livré pour nous tous, comment ne nous donnera-t-il pas aussi toutes choses avec lui?\n8.33\nQui accusera les élus de Dieu? C'est Dieu qui justifie!\n8.34\nQui les condamnera? Christ est mort; bien plus, il est ressuscité, il est à la droite de Dieu, et il intercède pour nous!\n8.35\nQui nous séparera de l'amour de Christ? Sera-ce la tribulation, ou l'angoisse, ou la persécution, ou la faim, ou la nudité, ou le péril, ou l'épée?\n8.36\nselon qu'il est écrit: C'est à cause de toi qu'on nous met à mort tout le jour, Qu'on nous regarde comme des brebis destinées à la boucherie.\n8.37\nMais dans toutes ces choses nous sommes plus que vainqueurs par celui qui nous a aimés.\n8.38\nCar j'ai l'assurance que ni la mort ni la vie, ni les anges ni les dominations, ni les choses présentes ni les choses à venir,\n8.39\nni les puissances, ni la hauteur, ni la profondeur, ni aucune autre créature ne pourra nous séparer de l'amour de Dieu manifesté en Jésus Christ notre Seigneur.\n\nRomains 9\n\n9.1\nJe dis la vérité en Christ, je ne mens point, ma conscience m'en rend témoignage par le Saint Esprit:\n9.2\nJ'éprouve une grande tristesse, et j'ai dans le coeur un chagrin continuel.\n9.3\nCar je voudrais moi-même être anathème et séparé de Christ pour mes frères, mes parents selon la chair,\n9.4\nqui sont Israélites, à qui appartiennent l'adoption, et la gloire, et les alliances, et la loi, et le culte,\n9.5\net les promesses, et les patriarches, et de qui est issu, selon la chair, le Christ, qui est au-dessus de toutes choses, Dieu béni éternellement. Amen!\n9.6\nCe n'est point à dire que la parole de Dieu soit restée sans effet. Car tous ceux qui descendent d'Israël ne sont pas Israël,\n9.7\net, pour être la postérité d'Abraham, ils ne sont pas tous ses enfants; mais il est dit: En Isaac sera nommée pour toi une postérité,\n9.8\nc'est-à-dire que ce ne sont pas les enfants de la chair qui sont enfants de Dieu, mais que ce sont les enfants de la promesse qui sont regardés comme la postérité.\n9.9\nVoici, en effet, la parole de la promesse: Je reviendrai à cette même époque, et Sara aura un fils.\n9.10\nEt, de plus, il en fut ainsi de Rébecca, qui conçut du seul Isaac notre père;\n9.11\ncar, quoique les enfants ne fussent pas encore nés et ils n'eussent fait ni bien ni mal, -afin que le dessein d'élection de Dieu subsistât, sans dépendre des oeuvres, et par la seule volonté de celui qui appelle, -\n9.12\nil fut dit à Rébecca: L'aîné sera assujetti au plus jeune; selon qu'il est écrit:\n9.13\nJ'ai aimé Jacob Et j'ai haï Ésaü.\n9.14\nQue dirons-nous donc? Y a-t-il en Dieu de l'injustice? Loin de là!\n9.15\nCar il dit à Moïse: Je ferai miséricorde à qui je fais miséricorde, et j'aurai compassion de qui j'ai compassion.\n9.16\nAinsi donc, cela ne dépend ni de celui qui veut, ni de celui qui court, mais de Dieu qui fait miséricorde.\n9.17\nCar l'Écriture dit à Pharaon: Je t'ai suscité à dessein pour montrer en toi ma puissance, et afin que mon nom soit publié par toute la terre.\n9.18\nAinsi, il fait miséricorde à qui il veut, et il endurcit qui il veut.\n9.19\nTu me diras: Pourquoi blâme-t-il encore? Car qui est-ce qui résiste à sa volonté?\n9.20\nO homme, toi plutôt, qui es-tu pour contester avec Dieu? Le vase d'argile dira-t-il à celui qui l'a formé: Pourquoi m'as-tu fait ainsi?\n9.21\nLe potier n'est-il pas maître de l'argile, pour faire avec la même masse un vase d'honneur et un vase d'un usage vil?\n9.22\nEt que dire, si Dieu, voulant montrer sa colère et faire connaître sa puissance, a supporté avec une grande patience des vases de colère formés pour la perdition,\n9.23\net s'il a voulu faire connaître la richesse de sa gloire envers des vases de miséricorde qu'il a d'avance préparés pour la gloire?\n9.24\nAinsi nous a-t-il appelés, non seulement d'entre les Juifs, mais encore d'entre les païens,\n9.25\nselon qu'il le dit dans Osée: J'appellerai mon peuple celui qui n'était pas mon peuple, et bien-aimée celle qui n'était pas la bien-aimée;\n9.26\net là où on leur disait: Vous n'êtes pas mon peuple! ils seront appelés fils du Dieu vivant.\n9.27\nÉsaïe, de son côté, s'écrie au sujet d'Israël: Quand le nombre des fils d'Israël serait comme le sable de la mer, Un reste seulement sera sauvé.\n9.28\nCar le Seigneur exécutera pleinement et promptement sur la terre ce qu'il a résolu.\n9.29\nEt, comme Ésaïe l'avait dit auparavant: Si le Seigneur des armées Ne nous eût laissé une postérité, Nous serions devenus comme Sodome, Nous aurions été semblables à Gomorrhe.\n9.30\nQue dirons-nous donc? Les païens, qui ne cherchaient pas la justice, ont obtenu la justice, la justice qui vient de la foi,\n9.31\ntandis qu'Israël, qui cherchait une loi de justice, n'est pas parvenu à cette loi.\n9.32\nPourquoi? Parce qu'Israël l'a cherchée, non par la foi, mais comme provenant des oeuvres. Ils se sont heurtés contre la pierre d'achoppement,\n9.33\nselon qu'il est écrit: Voici, je mets en Sion une pierre d'achoppement Et un rocher de scandale, Et celui qui croit en lui ne sera point confus.\n\nRomains 10\n\n10.1\nFrères, le voeu de mon coeur et ma prière à Dieu pour eux, c'est qu'ils soient sauvés.\n10.2\nJe leur rends le témoignage qu'ils ont du zèle pour Dieu, mais sans intelligence:\n10.3\nne connaissant pas la justice de Dieu, et cherchant à établir leur propre justice, ils ne se sont pas soumis à la justice de Dieu;\n10.4\ncar Christ est la fin de la loi, pour la justification de tous ceux qui croient.\n10.5\nEn effet, Moïse définit ainsi la justice qui vient de la loi: L'homme qui mettra ces choses en pratique vivra par elles.\n10.6\nMais voici comment parle la justice qui vient de la foi: Ne dis pas en ton coeur: Qui montera au ciel? c'est en faire descendre Christ;\n10.7\nou: Qui descendra dans l'abîme? c'est faire remonter Christ d'entre les morts.\n10.8\nQue dit-elle donc? La parole est près de toi, dans ta bouche et dans ton coeur. Or, c'est la parole de la foi, que nous prêchons.\n10.9\nSi tu confesses de ta bouche le Seigneur Jésus, et si tu crois dans ton coeur que Dieu l'a ressuscité des morts, tu seras sauvé.\n10.10\nCar c'est en croyant du coeur qu'on parvient à la justice, et c'est en confessant de la bouche qu'on parvient au salut, selon ce que dit l'Écriture:\n10.11\nQuiconque croit en lui ne sera point confus.\n10.12\nIl n'y a aucune différence, en effet, entre le Juif et le Grec, puisqu'ils ont tous un même Seigneur, qui est riche pour tous ceux qui l'invoquent.\n10.13\nCar quiconque invoquera le nom du Seigneur sera sauvé.\n10.14\nComment donc invoqueront-ils celui en qui ils n'ont pas cru? Et comment croiront-ils en celui dont ils n'ont pas entendu parler? Et comment en entendront-ils parler, s'il n'y a personne qui prêche?\n10.15\nEt comment y aura-t-il des prédicateurs, s'ils ne sont pas envoyés? selon qu'il est écrit: Qu'ils sont beaux Les pieds de ceux qui annoncent la paix, De ceux qui annoncent de bonnes nouvelles!\n10.16\nMais tous n'ont pas obéi à la bonne nouvelle. Aussi Ésaïe dit-il: Seigneur, Qui a cru à notre prédication?\n10.17\nAinsi la foi vient de ce qu'on entend, et ce qu'on entend vient de la parole de Christ.\n10.18\nMais je dis: N'ont-ils pas entendu? Au contraire! Leur voix est allée par toute la terre, Et leurs paroles jusqu'aux extrémités du monde.\n10.19\nMais je dis: Israël ne l'a-t-il pas su? Moïse le premier dit: J'exciterai votre jalousie par ce qui n'est point une nation, je provoquerai votre colère par une nation sans intelligence.\n10.20\nEt Ésaïe pousse la hardiesse jusqu'à dire: J'ai été trouvé par ceux qui ne me cherchaient pas, Je me suis manifesté à ceux qui ne me demandaient pas.\n10.21\nMais au sujet d'Israël, il dit: J'ai tendu mes mains tout le jour vers un peuple rebelle Et contredisant.\n\nRomains 11\n\n11.1\nJe dis donc: Dieu a-t-il rejeté son peuple? Loin de là! Car moi aussi je suis Israélite, de la postérité d'Abraham, de la tribu de Benjamin.\n11.2\nDieu n'a point rejeté son peuple, qu'il a connu d'avance. Ne savez-vous pas ce que l'Écriture rapporte d'Élie, comment il adresse à Dieu cette plainte contre Israël:\n11.3\nSeigneur, ils ont tué tes prophètes, ils ont renversé tes autels; je suis resté moi seul, et ils cherchent à m'ôter la vie?\n11.4\nMais quelle réponse Dieu lui fait-il? Je me suis réservé sept mille hommes, qui n'ont point fléchi le genou devant Baal.\n11.5\nDe même aussi dans le temps présent il y un reste, selon l'élection de la grâce.\n11.6\nOr, si c'est par grâce, ce n'est plus par les oeuvres; autrement la grâce n'est plus une grâce. Et si c'est par les oeuvres, ce n'est plus une grâce; autrement l'oeuvre n'est plus une oeuvre.\n11.7\nQuoi donc? Ce qu'Israël cherche, il ne l'a pas obtenu, mais l'élection l'a obtenu, tandis que les autres ont été endurcis,\n11.8\nselon qu'il est écrit: Dieu leur a donné un esprit d'assoupissement, Des yeux pour ne point voir, Et des oreilles pour ne point entendre, Jusqu'à ce jour. Et David dit:\n11.9\nQue leur table soit pour eux un piège, Un filet, une occasion de chute, et une rétribution!\n11.10\nQue leurs yeux soient obscurcis pour ne point voir, Et tiens leur dos continuellement courbé!\n11.11\nJe dis donc: Est-ce pour tomber qu'ils ont bronché? Loin de là! Mais, par leur chute, le salut est devenu accessible aux païens, afin qu'ils fussent excités à la jalousie.\n11.12\nOr, si leur chute a été la richesse du monde, et leur amoindrissement la richesse des païens, combien plus en sera-t-il ainsi quand ils se convertiront tous.\n11.13\nJe vous le dis à vous, païens: en tant que je suis apôtre des païens, je glorifie mon ministère,\n11.14\nafin, s'il est possible, d'exciter la jalousie de ceux de ma race, et d'en sauver quelques-uns.\n11.15\nCar si leur rejet a été la réconciliation du monde, que sera leur réintégration, sinon une vie d'entre les morts?\n11.16\nOr, si les prémices sont saintes, la masse l'est aussi; et si la racine est sainte, les branches le sont aussi.\n11.17\nMais si quelques-unes des branches ont été retranchées, et si toi, qui était un olivier sauvage, tu as été enté à leur place, et rendu participant de la racine et de la graisse de l'olivier,\n11.18\nne te glorifie pas aux dépens de ces branches. Si tu te glorifies, sache que ce n'est pas toi qui portes la racine, mais que c'est la racine qui te porte.\n11.19\nTu diras donc: Les branches ont été retranchées, afin que moi je fusse enté.\n11.20\nCela est vrai; elles ont été retranchées pour cause d'incrédulité, et toi, tu subsistes par la foi. Ne t'abandonne pas à l'orgueil, mais crains;\n11.21\ncar si Dieu n'a pas épargné les branches naturelles, il ne t'épargnera pas non plus.\n11.22\nConsidère donc la bonté et la sévérité de Dieu: sévérité envers ceux qui sont tombés, et bonté de Dieu envers toi, si tu demeures ferme dans cette bonté; autrement, tu seras aussi retranché.\n11.23\nEux de même, s'ils ne persistent pas dans l'incrédulité, ils seront entés; car Dieu est puissant pour les enter de nouveau.\n11.24\nSi toi, tu as été coupé de l'olivier naturellement sauvage, et enté contrairement à ta nature sur l'olivier franc, à plus forte raison eux seront-ils entés selon leur nature sur leur propre olivier.\n11.25\nCar je ne veux pas, frères, que vous ignoriez ce mystère, afin que vous ne vous regardiez point comme sages, c'est qu'une partie d'Israël est tombée dans l'endurcissement, jusqu'à ce que la totalité des païens soit entrée.\n11.26\nEt ainsi tout Israël sera sauvé, selon qu'il est écrit: Le libérateur viendra de Sion, Et il détournera de Jacob les impiétés;\n11.27\nEt ce sera mon alliance avec eux, Lorsque j'ôterai leurs péchés.\n11.28\nEn ce qui concerne l'Évangile, ils sont ennemis à cause de vous; mais en ce qui concerne l'élection, ils sont aimés à cause de leurs pères.\n11.29\nCar Dieu ne se repent pas de ses dons et de son appel.\n11.30\nDe même que vous avez autrefois désobéi à Dieu et que par leur désobéissance vous avez maintenant obtenu miséricorde,\n11.31\nde même ils ont maintenant désobéi, afin que, par la miséricorde qui vous a été faite, ils obtiennent aussi miséricorde.\n11.32\nCar Dieu a renfermé tous les hommes dans la désobéissance, pour faire miséricorde à tous.\n11.33\nO profondeur de la richesse, de la sagesse et de la science de Dieu! Que ses jugements sont insondables, et ses voies incompréhensibles! Car\n11.34\nQui a connu la pensée du Seigneur, Ou qui a été son conseiller?\n11.35\nQui lui a donné le premier, pour qu'il ait à recevoir en retour?\n11.36\nC'est de lui, par lui, et pour lui que sont toutes choses. A lui la gloire dans tous les siècles! Amen!\n\nRomains 12\n\n12.1\nJe vous exhorte donc, frères, par les compassions de Dieu, à offrir vos corps comme un sacrifice vivant, saint, agréable à Dieu, ce qui sera de votre part un culte raisonnable.\n12.2\nNe vous conformez pas au siècle présent, mais soyez transformés par le renouvellement de l'intelligence, afin que vous discerniez quelle est la volonté de Dieu, ce qui est bon, agréable et parfait.\n12.3\nPar la grâce qui m'a été donnée, je dis à chacun de vous de n'avoir pas de lui-même une trop haute opinion, mais de revêtir des sentiments modestes, selon la mesure de foi que Dieu a départie à chacun.\n12.4\nCar, comme nous avons plusieurs membres dans un seul corps, et que tous les membres n'ont pas la même fonction,\n12.5\nainsi, nous qui sommes plusieurs, nous formons un seul corps en Christ, et nous sommes tous membres les uns des autres.\n12.6\nPuisque nous avons des dons différents, selon la grâce qui nous a été accordée, que celui qui a le don de prophétie l'exerce selon l'analogie de la foi;\n12.7\nque celui qui est appelé au ministère s'attache à son ministère; que celui qui enseigne s'attache à son enseignement,\n12.8\net celui qui exhorte à l'exhortation. Que celui qui donne le fasse avec libéralité; que celui qui préside le fasse avec zèle; que celui qui pratique la miséricorde le fasse avec joie.\n12.9\nQue la charité soit sans hypocrisie. Ayez le mal en horreur; attachez-vous fortement au bien.\n12.10\nPar amour fraternel, soyez pleins d'affection les uns pour les autres; par honneur, usez de prévenances réciproques.\n12.11\nAyez du zèle, et non de la paresse. Soyez fervents d'esprit. Servez le Seigneur.\n12.12\nRéjouissez-vous en espérance. Soyez patients dans l'affliction. Persévérez dans la prière.\n12.13\nPourvoyez aux besoins des saints. Exercez l'hospitalité.\n12.14\nBénissez ceux qui vous persécutent, bénissez et ne maudissez pas.\n12.15\nRéjouissez-vous avec ceux qui se réjouissent; pleurez avec ceux qui pleurent.\n12.16\nAyez les mêmes sentiments les uns envers les autres. N'aspirez pas à ce qui est élevé, mais laissez-vous attirer par ce qui est humble. Ne soyez point sages à vos propres yeux.\n12.17\nNe rendez à personne le mal pour le mal. Recherchez ce qui est bien devant tous les hommes.\n12.18\nS'il est possible, autant que cela dépend de vous, soyez en paix avec tous les hommes.\n12.19\nNe vous vengez point vous-mêmes, bien-aimés, mais laissez agir la colère; car il est écrit: A moi la vengeance, à moi la rétribution, dit le Seigneur.\n12.20\nMais si ton ennemi a faim, donne-lui à manger; s'il a soif, donne-lui à boire; car en agissant ainsi, ce sont des charbons ardents que tu amasseras sur sa tête.\n12.21\nNe te laisse pas vaincre par le mal, mais surmonte le mal par le bien.\n\nRomains 13\n\n13.1\nQue toute personne soit soumise aux autorités supérieures; car il n'y a point d'autorité qui ne vienne de Dieu, et les autorités qui existent ont été instituées de Dieu.\n13.2\nC'est pourquoi celui qui s'oppose à l'autorité résiste à l'ordre que Dieu a établi, et ceux qui résistent attireront une condamnation sur eux-mêmes.\n13.3\nCe n'est pas pour une bonne action, c'est pour une mauvaise, que les magistrats sont à redouter. Veux-tu ne pas craindre l'autorité? Fais-le bien, et tu auras son approbation.\n13.4\nLe magistrat est serviteur de Dieu pour ton bien. Mais si tu fais le mal, crains; car ce n'est pas en vain qu'il porte l'épée, étant serviteur de Dieu pour exercer la vengeance et punir celui qui fait le mal.\n13.5\nIl est donc nécessaire d'être soumis, non seulement par crainte de la punition, mais encore par motif de conscience.\n13.6\nC'est aussi pour cela que vous payez les impôts. Car les magistrats sont des ministres de Dieu entièrement appliqués à cette fonction.\n13.7\nRendez à tous ce qui leur est dû: l'impôt à qui vous devez l'impôt, le tribut à qui vous devez le tribut, la crainte à qui vous devez la crainte, l'honneur à qui vous devez l'honneur.\n13.8\nNe devez rien à personne, si ce n'est de vous aimer les uns les autres; car celui qui aime les autres a accompli la loi.\n13.9\nEn effet, les commandements: Tu ne commettras point d'adultère, tu ne tueras point, tu ne déroberas point, tu ne convoiteras point, et ceux qu'il peut encore y avoir, se résument dans cette parole: Tu aimeras ton prochain comme toi-même.\n13.10\nL'amour ne fait point de mal au prochain: l'amour est donc l'accomplissement de la loi.\n13.11\nCela importe d'autant plus que vous savez en quel temps nous sommes: c'est l'heure de vous réveiller enfin du sommeil, car maintenant le salut est plus près de nous que lorsque nous avons cru.\n13.12\nLa nuit est avancée, le jour approche. Dépouillons-nous donc des oeuvres des ténèbres, et revêtons les armes de la lumière.\n13.13\nMarchons honnêtement, comme en plein jour, loin des excès et de l'ivrognerie, de la luxure et de l'impudicité, des querelles et des jalousies.\n13.14\nMais revêtez-vous du Seigneur Jésus Christ, et n'ayez pas soin de la chair pour en satisfaire les convoitises.\n\nRomains 14\n\n14.1\nFaites accueil à celui qui est faible dans la foi, et ne discutez pas sur les opinions.\n14.2\nTel croit pouvoir manger de tout: tel autre, qui est faible, ne mange que des légumes.\n14.3\nQue celui qui mange ne méprise point celui qui ne mange pas, et que celui qui ne mange pas ne juge point celui qui mange, car Dieu l'a accueilli.\n14.4\nQui es-tu, toi qui juges un serviteur d'autrui? S'il se tient debout, ou s'il tombe, cela regarde son maître. Mais il se tiendra debout, car le Seigneur a le pouvoir de l'affermir.\n14.5\nTel fait une distinction entre les jours; tel autre les estime tous égaux. Que chacun ait en son esprit une pleine conviction.\n14.6\nCelui qui distingue entre les jours agit ainsi pour le Seigneur. Celui qui mange, c'est pour le Seigneur qu'il mange, car il rend grâces à Dieu; celui qui ne mange pas, c'est pour le Seigneur qu'il ne mange pas, et il rend grâces à Dieu.\n14.7\nEn effet, nul de nous ne vit pour lui-même, et nul ne meurt pour lui-même.\n14.8\nCar si nous vivons, nous vivons pour le Seigneur; et si nous mourons, nous mourons pour le Seigneur. Soit donc que nous vivions, soit que nous mourions, nous sommes au Seigneur.\n14.9\nCar Christ est mort et il a vécu, afin de dominer sur les morts et sur les vivants.\n14.10\nMais toi, pourquoi juges-tu ton frère? ou toi, pourquoi méprises-tu ton frère? puisque nous comparaîtrons tous devant le tribunal de Dieu.\n14.11\nCar il est écrit: Je suis vivant, dit le Seigneur, Tout genou fléchira devant moi, Et toute langue donnera gloire à Dieu.\n14.12\nAinsi chacun de nous rendra compte à Dieu pour lui-même.\n14.13\nNe nous jugeons donc plus les uns les autres; mais pensez plutôt à ne rien faire qui soit pour votre frère une pierre d'achoppement ou une occasion de chute.\n14.14\nJe sais et je suis persuadé par le Seigneur Jésus que rien n'est impur en soi, et qu'une chose n'est impure que pour celui qui la croit impure.\n14.15\nMais si, pour un aliment, ton frère est attristé, tu ne marches plus selon l'amour: ne cause pas, par ton aliment, la perte de celui pour lequel Christ est mort.\n14.16\nQue votre privilège ne soit pas un sujet de calomnie.\n14.17\nCar le royaume de Dieu, ce n'est pas le manger et le boire, mais la justice, la paix et la joie, par le Saint Esprit.\n14.18\nCelui qui sert Christ de cette manière est agréable à Dieu et approuvé des hommes.\n14.19\nAinsi donc, recherchons ce qui contribue à la paix et à l'édification mutuelle.\n14.20\nPour un aliment, ne détruis pas l'oeuvre de Dieu. A la vérité toutes choses sont pures; mais il est mal à l'homme, quand il mange, de devenir une pierre d'achoppement.\n14.21\nIl est bien de ne pas manger de viande, de ne pas boire de vin, et de s'abstenir de ce qui peut être pour ton frère une occasion de chute, de scandale ou de faiblesse.\n14.22\nCette foi que tu as, garde-la pour toi devant Dieu. Heureux celui qui ne se condamne pas lui-même dans ce qu'il approuve!\n14.23\nMais celui qui a des doutes au sujet de ce qu'il mange est condamné, parce qu'il n'agit pas par conviction. Tout ce qui n'est pas le produit d'une conviction est péché.\n\nRomains 15\n\n15.1\nNous qui sommes forts, nous devons supporter les faiblesses de ceux qui ne le sont pas, et ne pas nous complaire en nous-mêmes.\n15.2\nQue chacun de nous complaise au prochain pour ce qui est bien en vue de l'édification.\n15.3\nCar Christ ne s'est point complu en lui-même, mais, selon qu'il est écrit: Les outrages de ceux qui t'insultent sont tombés sur moi.\n15.4\nOr, tout ce qui a été écrit d'avance l'a été pour notre instruction, afin que, par la patience, et par la consolation que donnent les Écritures, nous possédions l'espérance.\n15.5\nQue le Dieu de la persévérance et de la consolation vous donne d'avoir les mêmes sentiments les uns envers les autres selon Jésus Christ,\n15.6\nafin que tous ensemble, d'une seule bouche, vous glorifiiez le Dieu et Père de notre Seigneur Jésus Christ.\n15.7\nAccueillez-vous donc les uns les autres, comme Christ vous a accueillis, pour la gloire de Dieu.\n15.8\nJe dis, en effet, que Christ a été serviteur des circoncis, pour prouver la véracité de Dieu en confirmant les promesses faites aux pères,\n15.9\ntandis que les païens glorifient Dieu à cause de sa miséricorde, selon qu'il est écrit: C'est pourquoi je te louerai parmi les nations, Et je chanterai à la gloire de ton nom. Il est dit encore:\n15.10\nNations, réjouissez-vous avec son peuple!\n15.11\nEt encore: Louez le Seigneur, vous toutes les nations, Célébrez-le, vous tous les peuples!\n15.12\nÉsaïe dit aussi: Il sortira d'Isaï un rejeton, Qui se lèvera pour régner sur les nations; Les nations espéreront en lui.\n15.13\nQue le Dieu de l'espérance vous remplisse de toute joie et de toute paix dans la foi, pour que vous abondiez en espérance, par la puissance du Saint Esprit!\n15.14\nPour ce qui vous concerne, mes frères, je suis moi-même persuadé que vous êtes pleins de bonnes dispositions, remplis de toute connaissance, et capables de vous exhorter les uns les autres.\n15.15\nCependant, à certains égards, je vous ai écrit avec une sorte de hardiesse, comme pour réveiller vos souvenirs, à cause de la grâce que Dieu m'a faite\n15.16\nd'être ministre de Jésus Christ parmi les païens, m'acquittant du divin service de l'Évangile de Dieu, afin que les païens lui soient une offrande agréable, étant sanctifiée par l'Esprit Saint.\n15.17\nJ'ai donc sujet de me glorifier en Jésus Christ, pour ce qui regarde les choses de Dieu.\n15.18\nCar je n'oserais mentionner aucune chose que Christ n'ait pas faite par moi pour amener les païens à l'obéissance, par la parole et par les actes,\n15.19\npar la puissance des miracles et des prodiges, par la puissance de l'Esprit de Dieu, en sorte que, depuis Jérusalem et les pays voisins jusqu'en Illyrie, j'ai abondamment répandu l'Évangile de Christ.\n15.20\nEt je me suis fait honneur d'annoncer l'Évangile là où Christ n'avait point été nommé, afin de ne pas bâtir sur le fondement d'autrui, selon qu'il est écrit:\n15.21\nCeux à qui il n'avait point été annoncé verront, Et ceux qui n'en avaient point entendu parler comprendront.\n15.22\nC'est ce qui m'a souvent empêché d'aller vers vous.\n15.23\nMais maintenant, n'ayant plus rien qui me retienne dans ces contrées, et ayant depuis plusieurs années le désir d'aller vers vous,\n15.24\nj'espère vous voir en passant, quand je me rendrai en Espagne, et y être accompagné par vous, après que j'aurai satisfait en partie mon désir de me trouver chez vous.\n15.25\nPrésentement je vais à Jérusalem, pour le service des saints.\n15.26\nCar la Macédoine et l'Achaïe ont bien voulu s'imposer une contribution en faveur des pauvres parmi les saints de Jérusalem.\n15.27\nElles l'ont bien voulu, et elles le leur devaient; car si les païens ont eu part à leurs avantages spirituels, ils doivent aussi les assister dans les choses temporelles.\n15.28\nDès que j'aurai terminé cette affaire et que je leur aurai remis ces dons, je partirai pour l'Espagne et passerai chez vous.\n15.29\nJe sais qu'en allant vers vous, c'est avec une pleine bénédiction de Christ que j'irai.\n15.30\nJe vous exhorte, frères, par notre Seigneur Jésus Christ et par l'amour de l'Esprit, à combattre avec moi, en adressant à Dieu des prières en ma faveur,\n15.31\nafin que je sois délivré des incrédules de la Judée, et que les dons que je porte à Jérusalem soient agréés des saints,\n15.32\nen sorte que j'arrive chez vous avec joie, si c'est la volonté de Dieu, et que je jouisse au milieu de vous de quelque repos.\n15.33\nQue le Dieu de paix soit avec vous tous! Amen!\n\nRomains 16\n\n16.1\nJe vous recommande Phoebé, notre soeur, qui est diaconesse de l'Église de Cenchrées,\n16.2\nafin que vous la receviez en notre Seigneur d'une manière digne des saints, et que vous l'assistiez dans les choses où elle aurait besoin de vous, car elle en a donné aide à plusieurs et à moi-même.\n16.3\nSaluez Prisca et Aquilas, mes compagnons d'oeuvre en Jésus Christ,\n16.4\nqui ont exposé leur tête pour sauver ma vie; ce n'est pas moi seul qui leur rends grâces, ce sont encore toutes les Églises des païens.\n16.5\nSaluez aussi l'Église qui est dans leur maison. Saluez Épaïnète, mon bien-aimé, qui a été pour Christ les prémices de l'Asie.\n16.6\nSaluez Marie, qui a pris beaucoup de peine pour vous.\n16.7\nSaluez Andronicus et Junias, mes parents et mes compagnons de captivité, qui jouissent d'une grande considération parmi les apôtres, et qui même ont été en Christ avant moi.\n16.8\nSaluez Amplias, mon bien-aimé dans le Seigneur.\n16.9\nSaluez Urbain, notre compagnon d'oeuvre en Christ, et Stachys, mon bien-aimé.\n16.10\nSaluez Apellès, qui est éprouvé en Christ. Saluez ceux de la maison d'Aristobule.\n16.11\nSaluez Hérodion, mon parent. Saluez ceux de la maison de Narcisse qui sont dans le Seigneur.\n16.12\nSaluez Tryphène et Tryphose, qui travaillent pour le Seigneur. Saluez Perside, la bien-aimée, qui a beaucoup travaillé pour le Seigneur.\n16.13\nSaluez Rufus, l'élu du Seigneur, et sa mère, qui est aussi la mienne.\n16.14\nSaluez Asyncrite, Phlégon, Hermès, Patrobas, Hermas, et les frères qui sont avec eux.\n16.15\nSaluez Philologue et Julie, Nérée et sa soeur, et Olympe, et tous les saints qui sont avec eux.\n16.16\nSaluez-vous les uns les autres par un saint baiser. Toutes les Églises de Christ vous saluent.\n16.17\nJe vous exhorte, frères, à prendre garde à ceux qui causent des divisions et des scandales, au préjudice de l'enseignement que vous avez reçu. Éloignez-vous d'eux.\n16.18\nCar de tels hommes ne servent point Christ notre Seigneur, mais leur propre ventre; et, par des paroles douces et flatteuses, ils séduisent les coeurs des simples.\n16.19\nPour vous, votre obéissance est connue de tous; je me réjouis donc à votre sujet, et je désire que vous soyez sages en ce qui concerne le bien et purs en ce qui concerne le mal.\n16.20\nLe Dieu de paix écrasera bientôt Satan sous vos pieds. Que la grâce de notre Seigneur Jésus Christ soit avec vous!\n16.21\nTimothée, mon compagnon d'oeuvre, vous salue, ainsi que Lucius, Jason et Sosipater, mes parents.\n16.22\nJe vous salue dans le Seigneur, moi Tertius, qui ai écrit cette lettre.\n16.23\nGaïus, mon hôte et celui de toute l'Église, vous salue. Éraste, le trésorier de la ville, vous salue, ainsi que le frère Quartus.\n16.24\nQue la grâce de notre Seigneur Jésus Christ soit avec vous tous! Amen!\n16.25\nA celui qui peut vous affermir selon mon Évangile et la prédication de Jésus Christ, conformément à la révélation du mystère caché pendant des siècles,\n16.26\nmais manifesté maintenant par les écrits des prophètes, d'après l'ordre du Dieu éternel, et porté à la connaissance de toutes les nations, afin qu'elles obéissent à la foi,\n16.27\nà Dieu, seul sage, soit la gloire aux siècles des siècles, par Jésus Christ! Amen!\n"
  },
  {
    "path": "test/ws_perf_SUITE_data/japanese.txt",
    "content": "JAP\n\n1\n\n天と地の創造\n\n1まだ何もなかった時、神は天と地を造りました。 2地は形も定まらず、闇に包まれた水の上を、さらに神の霊が覆っていました。\n\n3「光よ、輝き出よ。」神が言われると、光がさっとさしてきました。 4-5それを見て、神は大いに満足し、光と闇とを区別しました。しばらくの間、光は輝き続け、やがて、もう一度闇に覆われました。神は光を「昼」、闇を「夜」と名づけました。こうして昼と夜ができて、一日目が終わりました。\n\n\n6「もやは上下に分かれ、空と海になれ」と神が言われると、 7-8そのとおり水蒸気が二つに分かれ、空ができました。こうして二日目も終わりました。\n\n9-10「空の下の水は集まって海となり、乾いた地が現れ出よ。」こう神が言われると、そのとおりになりました。神は乾いた地を「陸地」、水の部分を「海」と名づけました。それを見て満足すると、 11-12神はまた言われました。「陸地には、あらゆる種類の草、種のある植物、実のなる木が生えよ。それぞれの種から同じ種類の草や木が生えるようになれ。」すると、そのとおりになり、神は満足しました。 13これが三日目です。\n\n14-15神のことばはさらに続きます。「空に光が輝き、地を照らせ。その光で、昼と夜の区別、季節の変化、一日や一年の区切りをつけよ。」すると、そのとおりになりました。 16こうして、地を照らす太陽と月ができました。太陽は大きく明るいので昼を、月は夜を治めました。このほかにも、星々が造られました。 17神はそれをみな空にちりばめ、地を照らすようにしました。 18こうして昼と夜を分け終えると、神は満足しました。 19ここまでが四日目の出来事です。\n\n20神は再び言われました。「海は魚やその他の生き物であふれ、空はあらゆる種類の鳥で満ちよ。」 21-22神は海に住む大きな生き物をはじめ、あらゆる種類の魚と鳥を造りました。みなすばらしいものばかりです。神はそれを見て、「海いっぱいに満ちよ。鳥たちは地を覆うまでに増えよ」と祝福しました。 23これが五日目です。\n\n24次に神は言われました。「地は、家畜や地をはうもの、野の獣など、あらゆる種類の生き物を生み出せ。」そのとおりになりました。 25神が造った生き物は、どれも満足のいくものばかりでした。\n\n26そして最後に、神はこう言われました。「さあ、人間を造ろう。地と空と海のあらゆる生き物を治めさせるために、われわれに最も近い、われわれのかたちに似せて人間を造ろう。」 27このように人間は、天地を造った神の特性を持つ者として、男と女とに創造されました。\n\n28神は人間を祝福して言われました。「地に増え広がり、大地を治めよ。あなたがたは、魚と鳥とすべての動物の主人なのだ。 29全地に生える種のある植物を見てみなさい。みなあなたがたのものだ。実のなる木もすべて与えるから、好きなだけ食べるがいい。 30また、動物や鳥にも、あらゆる草と植物を彼らの食物として与える。」 31神はでき上がった世界を隅から隅まで見渡しました。とてもすばらしい世界が広がっていました。こうして六日目が終わりました。\n\n\n2\n\n1ついに全世界が完成しました。 2すべてを創造し終えると、神は七日目には休まれ、 3この日を祝福して、聖なる日と定めました。この日、天地創造の働きが完了したからです。\n\n人間の創造\n\n人間の創造\n\n人間の創造\n\n人間の創造\n\n人間の創造\n\n人間の創造.\n"
  }
]